/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-store.c : Abstract class for an email store
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* 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.
*
* 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, see .
*
* Authors: Bertrand Guiheneuf
* Dan Winship
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include "camel-async-closure.h"
#include "camel-db.h"
#include "camel-debug.h"
#include "camel-folder.h"
#include "camel-network-service.h"
#include "camel-offline-store.h"
#include "camel-session.h"
#include "camel-store.h"
#include "camel-store-settings.h"
#include "camel-subscribable.h"
#include "camel-vtrash-folder.h"
#define d(x)
#define w(x)
#define CAMEL_STORE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_STORE, CamelStorePrivate))
typedef struct _AsyncContext AsyncContext;
typedef struct _SignalClosure SignalClosure;
struct _CamelStorePrivate {
GMutex signal_emission_lock;
gboolean folder_info_stale_scheduled;
volatile gint maintenance_lock;
};
struct _AsyncContext {
gchar *folder_name_1;
gchar *folder_name_2;
gboolean expunge;
guint32 flags;
};
struct _SignalClosure {
GWeakRef store;
CamelFolder *folder;
CamelFolderInfo *folder_info;
gchar *folder_name;
};
enum {
FOLDER_CREATED,
FOLDER_DELETED,
FOLDER_INFO_STALE,
FOLDER_OPENED,
FOLDER_RENAMED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
static GInitableIface *parent_initable_interface;
/* Forward Declarations */
static void camel_store_initable_init (GInitableIface *iface);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
CamelStore, camel_store, CAMEL_TYPE_SERVICE,
G_IMPLEMENT_INTERFACE (
G_TYPE_INITABLE, camel_store_initable_init))
static void
async_context_free (AsyncContext *async_context)
{
g_free (async_context->folder_name_1);
g_free (async_context->folder_name_2);
g_slice_free (AsyncContext, async_context);
}
static void
signal_closure_free (SignalClosure *signal_closure)
{
g_weak_ref_clear (&signal_closure->store);
if (signal_closure->folder != NULL)
g_object_unref (signal_closure->folder);
if (signal_closure->folder_info != NULL)
camel_folder_info_free (signal_closure->folder_info);
g_free (signal_closure->folder_name);
g_slice_free (SignalClosure, signal_closure);
}
static gboolean
store_emit_folder_created_cb (gpointer user_data)
{
SignalClosure *signal_closure = user_data;
CamelStore *store;
store = g_weak_ref_get (&signal_closure->store);
if (store != NULL) {
g_signal_emit (
store,
signals[FOLDER_CREATED], 0,
signal_closure->folder_info);
g_object_unref (store);
}
return FALSE;
}
static gboolean
store_emit_folder_deleted_cb (gpointer user_data)
{
SignalClosure *signal_closure = user_data;
CamelStore *store;
store = g_weak_ref_get (&signal_closure->store);
if (store != NULL) {
g_signal_emit (
store,
signals[FOLDER_DELETED], 0,
signal_closure->folder_info);
g_object_unref (store);
}
return FALSE;
}
static gboolean
store_emit_folder_opened_cb (gpointer user_data)
{
SignalClosure *signal_closure = user_data;
CamelStore *store;
store = g_weak_ref_get (&signal_closure->store);
if (store != NULL) {
g_signal_emit (
store,
signals[FOLDER_OPENED], 0,
signal_closure->folder);
g_object_unref (store);
}
return FALSE;
}
static gboolean
store_emit_folder_renamed_cb (gpointer user_data)
{
SignalClosure *signal_closure = user_data;
CamelStore *store;
store = g_weak_ref_get (&signal_closure->store);
if (store != NULL) {
g_signal_emit (
store,
signals[FOLDER_RENAMED], 0,
signal_closure->folder_name,
signal_closure->folder_info);
g_object_unref (store);
}
return FALSE;
}
static gboolean
store_emit_folder_info_stale_cb (gpointer user_data)
{
SignalClosure *signal_closure = user_data;
CamelStore *store;
store = g_weak_ref_get (&signal_closure->store);
if (store != NULL) {
g_mutex_lock (&store->priv->signal_emission_lock);
store->priv->folder_info_stale_scheduled = FALSE;
g_mutex_unlock (&store->priv->signal_emission_lock);
g_signal_emit (store, signals[FOLDER_INFO_STALE], 0);
g_object_unref (store);
}
return FALSE;
}
/*
* ignore_no_such_table_exception:
* Clears the error 'error' when it's the 'no such table' error.
*/
static void
ignore_no_such_table_exception (GError **error)
{
if (error == NULL || *error == NULL)
return;
if (g_ascii_strncasecmp ((*error)->message, "no such table", 13) == 0)
g_clear_error (error);
}
/* deletes folder/removes it from the folder cache, if it's there */
static void
cs_delete_cached_folder (CamelStore *store,
const gchar *folder_name)
{
CamelFolder *folder;
CamelVeeFolder *vfolder;
if (store->folders == NULL)
return;
folder = camel_object_bag_get (store->folders, folder_name);
if (folder == NULL)
return;
if (store->flags & CAMEL_STORE_VTRASH) {
vfolder = camel_object_bag_get (
store->folders, CAMEL_VTRASH_NAME);
if (vfolder != NULL) {
camel_vee_folder_remove_folder (vfolder, folder, NULL);
g_object_unref (vfolder);
}
}
if (store->flags & CAMEL_STORE_VJUNK) {
vfolder = camel_object_bag_get (
store->folders, CAMEL_VJUNK_NAME);
if (vfolder != NULL) {
camel_vee_folder_remove_folder (vfolder, folder, NULL);
g_object_unref (vfolder);
}
}
camel_folder_delete (folder);
camel_object_bag_remove (store->folders, folder);
g_object_unref (folder);
}
static CamelFolder *
store_get_special (CamelStore *store,
camel_vtrash_folder_t type)
{
CamelFolder *folder;
GPtrArray *folders;
gint i;
folder = camel_vtrash_folder_new (store, type);
folders = camel_object_bag_list (store->folders);
for (i = 0; i < folders->len; i++) {
if (!CAMEL_IS_VTRASH_FOLDER (folders->pdata[i]))
camel_vee_folder_add_folder ((CamelVeeFolder *) folder, (CamelFolder *) folders->pdata[i], NULL);
g_object_unref (folders->pdata[i]);
}
g_ptr_array_free (folders, TRUE);
return folder;
}
static gboolean
store_maybe_connect_sync (CamelStore *store,
GCancellable *cancellable,
GError **error)
{
CamelService *service;
CamelServiceConnectionStatus status;
CamelSession *session;
gboolean connect = FALSE;
gboolean success = TRUE;
/* This is meant to recover from dropped connections
* when the CamelService is online but disconnected. */
service = CAMEL_SERVICE (store);
session = camel_service_ref_session (service);
status = camel_service_get_connection_status (service);
connect = session && camel_session_get_online (session) && (status != CAMEL_SERVICE_CONNECTED);
g_clear_object (&session);
if (connect && CAMEL_IS_NETWORK_SERVICE (store)) {
/* Disregard errors here. Just want to
* know whether to attempt a connection. */
connect = camel_network_service_can_reach_sync (
CAMEL_NETWORK_SERVICE (service), cancellable, NULL);
}
if (connect && CAMEL_IS_OFFLINE_STORE (store)) {
CamelOfflineStore *offline_store;
offline_store = CAMEL_OFFLINE_STORE (store);
if (!camel_offline_store_get_online (offline_store))
connect = FALSE;
}
if (connect) {
success = camel_service_connect_sync (
service, cancellable, error);
}
return success;
}
static void
store_finalize (GObject *object)
{
CamelStore *store = CAMEL_STORE (object);
if (store->folders != NULL)
camel_object_bag_destroy (store->folders);
if (store->cdb_r != NULL) {
camel_db_close (store->cdb_r);
store->cdb_r = NULL;
store->cdb_w = NULL;
}
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_store_parent_class)->finalize (object);
}
static void
store_constructed (GObject *object)
{
CamelStore *store;
CamelStoreClass *class;
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (camel_store_parent_class)->constructed (object);
store = CAMEL_STORE (object);
class = CAMEL_STORE_GET_CLASS (store);
g_return_if_fail (class->hash_folder_name != NULL);
g_return_if_fail (class->equal_folder_name != NULL);
store->folders = camel_object_bag_new (
class->hash_folder_name,
class->equal_folder_name,
(CamelCopyFunc) g_strdup, g_free);
}
static gboolean
store_can_refresh_folder (CamelStore *store,
CamelFolderInfo *info,
GError **error)
{
return ((info->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX);
}
static CamelFolder *
store_get_inbox_folder_sync (CamelStore *store,
GCancellable *cancellable,
GError **error)
{
CamelStoreClass *class;
CamelFolder *folder;
class = CAMEL_STORE_GET_CLASS (store);
g_return_val_if_fail (class->get_folder_sync != NULL, NULL);
/* Assume the inbox's name is "inbox" and open with default flags. */
folder = class->get_folder_sync (store, "inbox", 0, cancellable, error);
CAMEL_CHECK_GERROR (store, get_folder_sync, folder != NULL, error);
return folder;
}
static CamelFolder *
store_get_junk_folder_sync (CamelStore *store,
GCancellable *cancellable,
GError **error)
{
return store_get_special (store, CAMEL_VTRASH_FOLDER_JUNK);
}
static CamelFolder *
store_get_trash_folder_sync (CamelStore *store,
GCancellable *cancellable,
GError **error)
{
return store_get_special (store, CAMEL_VTRASH_FOLDER_TRASH);
}
static gboolean
store_synchronize_sync (CamelStore *store,
gboolean expunge,
GCancellable *cancellable,
GError **error)
{
GPtrArray *folders;
gboolean success = TRUE;
gint ii;
GError *local_error = NULL;
if (expunge) {
/* ensure all folders are used when expunging */
CamelFolderInfo *root, *fi;
g_atomic_int_add (&store->priv->maintenance_lock, 1);
folders = g_ptr_array_new ();
root = camel_store_get_folder_info_sync (
store, NULL,
CAMEL_STORE_FOLDER_INFO_RECURSIVE |
CAMEL_STORE_FOLDER_INFO_SUBSCRIBED |
CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL,
NULL, NULL);
fi = root;
while (fi != NULL) {
CamelFolderInfo *next;
if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0) {
CamelFolder *folder;
folder = camel_store_get_folder_sync (
store, fi->full_name, 0, NULL, NULL);
if (folder != NULL)
g_ptr_array_add (folders, folder);
}
/* pick the next */
next = fi->child;
if (next == NULL)
next = fi->next;
if (next == NULL) {
next = fi->parent;
while (next != NULL) {
if (next->next != NULL) {
next = next->next;
break;
}
next = next->parent;
}
}
fi = next;
}
camel_folder_info_free (root);
} else {
/* sync only folders opened until now */
folders = camel_object_bag_list (store->folders);
}
/* We don't sync any vFolders, that is used to update certain
* vfolder queries mainly, and we're really only interested in
* storing/expunging the physical mails. */
for (ii = 0; ii < folders->len; ii++) {
CamelFolder *folder = folders->pdata[ii];
if (folder->summary)
camel_folder_summary_save_to_db (folder->summary, NULL);
if (!CAMEL_IS_VEE_FOLDER (folder) && local_error == NULL) {
camel_folder_synchronize_sync (
folder, expunge, cancellable, &local_error);
ignore_no_such_table_exception (&local_error);
}
g_object_unref (folder);
}
/* Unlock it before the call, thus it's actually done. */
if (expunge)
g_atomic_int_add (&store->priv->maintenance_lock, -1);
if (!local_error && expunge) {
camel_store_maybe_run_db_maintenance (store, &local_error);
}
if (local_error != NULL) {
g_propagate_error (error, local_error);
success = FALSE;
}
g_ptr_array_free (folders, TRUE);
return success;
}
static gboolean
store_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
CamelStore *store;
CamelService *service;
const gchar *user_dir;
gchar *filename;
store = CAMEL_STORE (initable);
/* Chain up to parent interface's init() method. */
if (!parent_initable_interface->init (initable, cancellable, error))
return FALSE;
service = CAMEL_SERVICE (initable);
if ((store->flags & CAMEL_STORE_USE_CACHE_DIR) != 0)
user_dir = camel_service_get_user_cache_dir (service);
else
user_dir = camel_service_get_user_data_dir (service);
if (g_mkdir_with_parents (user_dir, S_IRWXU) == -1) {
g_set_error_literal (
error, G_FILE_ERROR,
g_file_error_from_errno (errno),
g_strerror (errno));
return FALSE;
}
/* This is for reading from the store */
filename = g_build_filename (user_dir, CAMEL_DB_FILE, NULL);
store->cdb_r = camel_db_open (filename, error);
g_free (filename);
if (store->cdb_r == NULL)
return FALSE;
if (camel_db_create_folders_table (store->cdb_r, error))
return FALSE;
/* keep cb_w to not break the ABI */
store->cdb_w = store->cdb_r;
return TRUE;
}
static void
camel_store_class_init (CamelStoreClass *class)
{
GObjectClass *object_class;
CamelServiceClass *service_class;
g_type_class_add_private (class, sizeof (CamelStorePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->finalize = store_finalize;
object_class->constructed = store_constructed;
service_class = CAMEL_SERVICE_CLASS (class);
service_class->settings_type = CAMEL_TYPE_STORE_SETTINGS;
class->hash_folder_name = g_str_hash;
class->equal_folder_name = g_str_equal;
class->can_refresh_folder = store_can_refresh_folder;
class->get_inbox_folder_sync = store_get_inbox_folder_sync;
class->get_junk_folder_sync = store_get_junk_folder_sync;
class->get_trash_folder_sync = store_get_trash_folder_sync;
class->synchronize_sync = store_synchronize_sync;
signals[FOLDER_CREATED] = g_signal_new (
"folder-created",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CamelStoreClass, folder_created),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
signals[FOLDER_DELETED] = g_signal_new (
"folder-deleted",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CamelStoreClass, folder_deleted),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
/**
* CamelStore::folder-info-stale:
* @store: the #CamelStore that received the signal
*
* This signal indicates significant changes have occurred to
* the folder hierarchy of @store, and that previously fetched
* #CamelFolderInfo data should be considered stale.
*
* Applications should handle this signal by replacing cached
* #CamelFolderInfo data for @store with fresh data by way of
* camel_store_get_folder_info().
*
* More often than not this signal will be emitted as a result of
* user preference changes rather than actual server-side changes.
* For example, a user may change a preference that reveals a set
* of folders previously hidden from view, or that alters whether
* to augment the @store with virtual Junk and Trash folders.
**/
signals[FOLDER_INFO_STALE] = g_signal_new (
"folder-info-stale",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CamelStoreClass, folder_info_stale),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[FOLDER_OPENED] = g_signal_new (
"folder-opened",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CamelStoreClass, folder_opened),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
CAMEL_TYPE_FOLDER);
signals[FOLDER_RENAMED] = g_signal_new (
"folder-renamed",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CamelStoreClass, folder_renamed),
NULL, NULL, NULL,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_POINTER);
}
static void
camel_store_initable_init (GInitableIface *iface)
{
parent_initable_interface = g_type_interface_peek_parent (iface);
iface->init = store_initable_init;
}
static void
camel_store_init (CamelStore *store)
{
store->priv = CAMEL_STORE_GET_PRIVATE (store);
/* Default CamelStore capabilities:
*
* - Include a virtual Junk folder.
* - Include a virtual Trash folder.
* - Allow creating/deleting/renaming folders.
*/
store->flags =
CAMEL_STORE_VJUNK |
CAMEL_STORE_VTRASH |
CAMEL_STORE_CAN_EDIT_FOLDERS;
store->mode = CAMEL_STORE_READ | CAMEL_STORE_WRITE;
store->priv->maintenance_lock = 0;
}
G_DEFINE_QUARK (camel-store-error-quark, camel_store_error)
/**
* camel_store_folder_created:
* @store: a #CamelStore
* @folder_info: information about the created folder
*
* Emits the #CamelStore::folder-created signal from an idle source on
* the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
*
* This function is only intended for Camel providers.
*
* Since: 2.32
**/
void
camel_store_folder_created (CamelStore *store,
CamelFolderInfo *folder_info)
{
CamelSession *session;
SignalClosure *signal_closure;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (folder_info != NULL);
session = camel_service_ref_session (CAMEL_SERVICE (store));
signal_closure = g_slice_new0 (SignalClosure);
g_weak_ref_init (&signal_closure->store, store);
signal_closure->folder_info = camel_folder_info_clone (folder_info);
/* Prioritize ahead of GTK+ redraws. */
camel_session_idle_add (
session, G_PRIORITY_HIGH_IDLE,
store_emit_folder_created_cb,
signal_closure,
(GDestroyNotify) signal_closure_free);
g_object_unref (session);
}
/**
* camel_store_folder_deleted:
* @store: a #CamelStore
* @folder_info: information about the deleted folder
*
* Emits the #CamelStore::folder-deleted signal from an idle source on
* the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
*
* This function is only intended for Camel providers.
*
* Since: 2.32
**/
void
camel_store_folder_deleted (CamelStore *store,
CamelFolderInfo *folder_info)
{
CamelSession *session;
SignalClosure *signal_closure;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (folder_info != NULL);
session = camel_service_ref_session (CAMEL_SERVICE (store));
signal_closure = g_slice_new0 (SignalClosure);
g_weak_ref_init (&signal_closure->store, store);
signal_closure->folder_info = camel_folder_info_clone (folder_info);
/* Prioritize ahead of GTK+ redraws. */
camel_session_idle_add (
session, G_PRIORITY_HIGH_IDLE,
store_emit_folder_deleted_cb,
signal_closure,
(GDestroyNotify) signal_closure_free);
g_object_unref (session);
}
/**
* camel_store_folder_opened:
* @store: a #CamelStore
* @folder: the #CamelFolder that was opened
*
* Emits the #CamelStore::folder-opened signal from an idle source on
* the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
*
* This function is only intended for Camel providers.
*
* Since: 3.0
**/
void
camel_store_folder_opened (CamelStore *store,
CamelFolder *folder)
{
CamelSession *session;
SignalClosure *signal_closure;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (CAMEL_IS_FOLDER (folder));
session = camel_service_ref_session (CAMEL_SERVICE (store));
signal_closure = g_slice_new0 (SignalClosure);
g_weak_ref_init (&signal_closure->store, store);
signal_closure->folder = g_object_ref (folder);
/* Prioritize ahead of GTK+ redraws. */
camel_session_idle_add (
session, G_PRIORITY_HIGH_IDLE,
store_emit_folder_opened_cb,
signal_closure,
(GDestroyNotify) signal_closure_free);
g_object_unref (session);
}
/**
* camel_store_folder_renamed:
* @store: a #CamelStore
* @old_name: the old name of the folder
* @folder_info: information about the renamed folder
*
* Emits the #CamelStore::folder-renamed signal from an idle source on
* the main loop. The idle source's priority is #G_PRIORITY_HIGH_IDLE.
*
* This function is only intended for Camel providers.
*
* Since: 2.32
**/
void
camel_store_folder_renamed (CamelStore *store,
const gchar *old_name,
CamelFolderInfo *folder_info)
{
CamelSession *session;
SignalClosure *signal_closure;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (old_name != NULL);
g_return_if_fail (folder_info != NULL);
session = camel_service_ref_session (CAMEL_SERVICE (store));
signal_closure = g_slice_new0 (SignalClosure);
g_weak_ref_init (&signal_closure->store, store);
signal_closure->folder_info = camel_folder_info_clone (folder_info);
signal_closure->folder_name = g_strdup (old_name);
/* Prioritize ahead of GTK+ redraws. */
camel_session_idle_add (
session, G_PRIORITY_HIGH_IDLE,
store_emit_folder_renamed_cb,
signal_closure,
(GDestroyNotify) signal_closure_free);
g_object_unref (session);
}
/**
* camel_store_folder_info_stale:
* @store: a #CamelStore
*
* Emits the #CamelStore::folder-info-stale signal from an idle source
* on the main loop. The idle source's priority is #G_PRIORITY_LOW.
*
* See the #CamelStore::folder-info-stale documentation for details on
* when to use this signal.
*
* This function is only intended for Camel providers.
*
* Since: 3.10
**/
void
camel_store_folder_info_stale (CamelStore *store)
{
CamelSession *session;
g_return_if_fail (CAMEL_IS_STORE (store));
session = camel_service_ref_session (CAMEL_SERVICE (store));
g_mutex_lock (&store->priv->signal_emission_lock);
/* Handling this signal is probably going to be expensive for
* applications so try and accumulate multiple calls into one
* signal emission if we can. Hence the G_PRIORITY_LOW. */
if (!store->priv->folder_info_stale_scheduled) {
SignalClosure *signal_closure;
signal_closure = g_slice_new0 (SignalClosure);
g_weak_ref_init (&signal_closure->store, store);
camel_session_idle_add (
session, G_PRIORITY_LOW,
store_emit_folder_info_stale_cb,
signal_closure,
(GDestroyNotify) signal_closure_free);
store->priv->folder_info_stale_scheduled = TRUE;
}
g_mutex_unlock (&store->priv->signal_emission_lock);
g_object_unref (session);
}
static void
add_special_info (CamelStore *store,
CamelFolderInfo *info,
const gchar *name,
const gchar *translated,
gboolean unread_count,
CamelFolderInfoFlags flags)
{
CamelFolderInfo *fi, *vinfo, *parent;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (info != NULL);
parent = NULL;
for (fi = info; fi; fi = fi->next) {
if (!strcmp (fi->full_name, name))
break;
parent = fi;
}
if (fi) {
/* We're going to replace the physical Trash/Junk
* folder with our vTrash/vJunk folder. */
vinfo = fi;
g_free (vinfo->full_name);
g_free (vinfo->display_name);
} else {
g_return_if_fail (parent != NULL);
/* There wasn't a Trash/Junk folder so create a new
* folder entry. */
vinfo = camel_folder_info_new ();
vinfo->flags |=
CAMEL_FOLDER_NOINFERIORS |
CAMEL_FOLDER_SUBSCRIBED;
/* link it into the right spot */
vinfo->next = parent->next;
parent->next = vinfo;
}
/* Fill in the new fields */
vinfo->flags |= flags;
vinfo->full_name = g_strdup (name);
vinfo->display_name = g_strdup (translated);
if (!unread_count)
vinfo->unread = -1;
}
static void
dump_fi (CamelFolderInfo *fi,
gint depth)
{
gchar *s;
s = g_alloca (depth + 1);
memset (s, ' ', depth);
s[depth] = 0;
while (fi) {
printf ("%sfull_name: %s\n", s, fi->full_name);
printf ("%sflags: %08x\n", s, fi->flags);
dump_fi (fi->child, depth + 2);
fi = fi->next;
}
}
/**
* camel_folder_info_free:
* @fi: a #CamelFolderInfo
*
* Frees @fi.
**/
void
camel_folder_info_free (CamelFolderInfo *fi)
{
if (fi != NULL) {
camel_folder_info_free (fi->next);
camel_folder_info_free (fi->child);
g_free (fi->full_name);
g_free (fi->display_name);
g_slice_free (CamelFolderInfo, fi);
}
}
/**
* camel_folder_info_new:
*
* Allocates a new #CamelFolderInfo instance. Free it with
* camel_folder_info_free().
*
* Returns: a new #CamelFolderInfo instance
*
* Since: 2.22
**/
CamelFolderInfo *
camel_folder_info_new (void)
{
return g_slice_new0 (CamelFolderInfo);
}
static gint
folder_info_cmp (gconstpointer ap,
gconstpointer bp)
{
const CamelFolderInfo *a = ((CamelFolderInfo **) ap)[0];
const CamelFolderInfo *b = ((CamelFolderInfo **) bp)[0];
return strcmp (a->full_name, b->full_name);
}
/**
* camel_folder_info_build:
* @folders: an array of #CamelFolderInfo
* @namespace_: an ignorable prefix on the folder names
* @separator: the hieararchy separator character
* @short_names: %TRUE if the (short) name of a folder is the part after
* the last @separator in the full name. %FALSE if it is the full name.
*
* This takes an array of folders and attaches them together according
* to the hierarchy described by their full_names and @separator. If
* @namespace_ is non-%NULL, then it will be ignored as a full_name
* prefix, for purposes of comparison. If necessary,
* camel_folder_info_build() will create additional #CamelFolderInfo with
* %NULL urls to fill in gaps in the tree. The value of @short_names
* is used in constructing the names of these intermediate folders.
*
* NOTE: This is deprected, do not use this.
* FIXME: remove this/move it to imap, which is the only user of it now.
*
* Returns: the top level of the tree of linked folder info.
**/
CamelFolderInfo *
camel_folder_info_build (GPtrArray *folders,
const gchar *namespace_,
gchar separator,
gboolean short_names)
{
CamelFolderInfo *fi, *pfi, *top = NULL, *tail = NULL;
GHashTable *hash;
gchar *p, *pname;
gint i, nlen;
if (namespace_ == NULL)
namespace_ = "";
nlen = strlen (namespace_);
qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), folder_info_cmp);
/* Hash the folders. */
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
for (i = 0; i < folders->len; i++) {
fi = folders->pdata[i];
g_hash_table_insert (hash, g_strdup (fi->full_name), fi);
}
/* Now find parents. */
for (i = 0; i < folders->len; i++) {
fi = folders->pdata[i];
if (!strncmp (namespace_, fi->full_name, nlen)
&& (p = strrchr (fi->full_name + nlen, separator))) {
pname = g_strndup (fi->full_name, p - fi->full_name);
pfi = g_hash_table_lookup (hash, pname);
if (pfi) {
g_free (pname);
} else {
/* we are missing a folder in the heirarchy so
* create a fake folder node */
pfi = camel_folder_info_new ();
if (short_names) {
pfi->display_name = strrchr (pname, separator);
if (pfi->display_name != NULL)
pfi->display_name = g_strdup (pfi->display_name + 1);
else
pfi->display_name = g_strdup (pname);
} else
pfi->display_name = g_strdup (pname);
pfi->full_name = g_strdup (pname);
/* Since this is a "fake" folder
* node, it is not selectable. */
pfi->flags |= CAMEL_FOLDER_NOSELECT;
g_hash_table_insert (hash, pname, pfi);
g_ptr_array_add (folders, pfi);
}
tail = (CamelFolderInfo *) &pfi->child;
while (tail->next)
tail = tail->next;
tail->next = fi;
fi->parent = pfi;
} else if (!top || !g_ascii_strcasecmp (fi->full_name, "Inbox"))
top = fi;
}
g_hash_table_destroy (hash);
/* Link together the top-level folders */
tail = top;
for (i = 0; i < folders->len; i++) {
fi = folders->pdata[i];
if (fi->child)
fi->flags &= ~CAMEL_FOLDER_NOCHILDREN;
if (fi->parent || fi == top)
continue;
if (tail == NULL) {
tail = fi;
top = fi;
} else {
tail->next = fi;
tail = fi;
}
}
return top;
}
static CamelFolderInfo *
folder_info_clone_rec (CamelFolderInfo *fi,
CamelFolderInfo *parent)
{
CamelFolderInfo *info;
info = camel_folder_info_new ();
info->parent = parent;
info->full_name = g_strdup (fi->full_name);
info->display_name = g_strdup (fi->display_name);
info->unread = fi->unread;
info->flags = fi->flags;
if (fi->next)
info->next = folder_info_clone_rec (fi->next, parent);
else
info->next = NULL;
if (fi->child)
info->child = folder_info_clone_rec (fi->child, info);
else
info->child = NULL;
return info;
}
/**
* camel_folder_info_clone:
* @fi: a #CamelFolderInfo
*
* Clones @fi recursively.
*
* Returns: the cloned #CamelFolderInfo tree.
**/
CamelFolderInfo *
camel_folder_info_clone (CamelFolderInfo *fi)
{
if (fi == NULL)
return NULL;
return folder_info_clone_rec (fi, NULL);
}
/**
* camel_store_can_refresh_folder
* @store: a #CamelStore
* @info: a #CamelFolderInfo
* @error: return location for a #GError, or %NULL
*
* Returns if this folder (param info) should be checked for new mail or not.
* It should not look into sub infos (info->child) or next infos, it should
* return value only for the actual folder info.
* Default behavior is that all Inbox folders are intended to be refreshed.
*
* Returns: whether folder should be checked for new mails
*
* Since: 2.22
**/
gboolean
camel_store_can_refresh_folder (CamelStore *store,
CamelFolderInfo *info,
GError **error)
{
CamelStoreClass *class;
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
g_return_val_if_fail (info != NULL, FALSE);
class = CAMEL_STORE_GET_CLASS (store);
g_return_val_if_fail (class->can_refresh_folder != NULL, FALSE);
return class->can_refresh_folder (store, info, error);
}
/**
* camel_store_get_folder_sync:
* @store: a #CamelStore
* @folder_name: name of the folder to get
* @flags: folder flags (create, save body index, etc)
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Gets a specific folder object from @store by name.
*
* Returns: the requested #CamelFolder object, or %NULL on error
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_folder_sync (CamelStore *store,
const gchar *folder_name,
CamelStoreGetFolderFlags flags,
GCancellable *cancellable,
GError **error)
{
CamelStoreClass *class;
CamelFolder *folder = NULL;
CamelVeeFolder *vjunk = NULL;
CamelVeeFolder *vtrash = NULL;
gboolean create_folder = FALSE;
gboolean folder_name_is_vjunk;
gboolean folder_name_is_vtrash;
gboolean store_uses_vjunk;
gboolean store_uses_vtrash;
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (folder_name != NULL, NULL);
class = CAMEL_STORE_GET_CLASS (store);
try_again:
/* Try cache first. */
folder = camel_object_bag_reserve (store->folders, folder_name);
if (folder != NULL)
return folder;
store_uses_vjunk =
((store->flags & CAMEL_STORE_VJUNK) != 0);
store_uses_vtrash =
((store->flags & CAMEL_STORE_VTRASH) != 0);
folder_name_is_vjunk =
store_uses_vjunk &&
(strcmp (folder_name, CAMEL_VJUNK_NAME) == 0);
folder_name_is_vtrash =
store_uses_vtrash &&
(strcmp (folder_name, CAMEL_VTRASH_NAME) == 0);
if (flags & CAMEL_STORE_IS_MIGRATING) {
if (folder_name_is_vtrash) {
if (store->folders != NULL)
camel_object_bag_abort (
store->folders, folder_name);
return NULL;
}
if (folder_name_is_vjunk) {
if (store->folders != NULL)
camel_object_bag_abort (
store->folders, folder_name);
return NULL;
}
}
camel_operation_push_message (
cancellable, _("Opening folder '%s'"), folder_name);
if (folder_name_is_vtrash) {
folder = class->get_trash_folder_sync (
store, cancellable, error);
CAMEL_CHECK_GERROR (
store, get_trash_folder_sync,
folder != NULL, error);
} else if (folder_name_is_vjunk) {
folder = class->get_junk_folder_sync (
store, cancellable, error);
CAMEL_CHECK_GERROR (
store, get_junk_folder_sync,
folder != NULL, error);
} else {
GError *local_error = NULL;
/* If CAMEL_STORE_FOLDER_CREATE flag is set, note it and
* strip it so subclasses never receive it. We'll handle
* it ourselves below. */
create_folder = ((flags & CAMEL_STORE_FOLDER_CREATE) != 0);
flags &= ~CAMEL_STORE_FOLDER_CREATE;
folder = class->get_folder_sync (
store, folder_name, flags,
cancellable, &local_error);
CAMEL_CHECK_LOCAL_GERROR (
store, get_folder_sync,
folder != NULL, local_error);
/* XXX This depends on subclasses setting this error code
* consistently. Do they? I guess we'll find out... */
create_folder &= g_error_matches (
local_error,
CAMEL_STORE_ERROR,
CAMEL_STORE_ERROR_NO_FOLDER);
if (create_folder)
g_clear_error (&local_error);
if (local_error != NULL)
g_propagate_error (error, local_error);
if (folder != NULL && store_uses_vjunk)
vjunk = camel_object_bag_get (
store->folders, CAMEL_VJUNK_NAME);
if (folder != NULL && store_uses_vtrash)
vtrash = camel_object_bag_get (
store->folders, CAMEL_VTRASH_NAME);
}
/* Release the folder name reservation before adding the
* folder to the virtual Junk and Trash folders, just to
* reduce the chance of deadlock. */
if (folder != NULL)
camel_object_bag_add (
store->folders, folder_name, folder);
else
camel_object_bag_abort (
store->folders, folder_name);
/* If this is a normal folder and the store uses a
* virtual Junk folder, let the virtual Junk folder
* track this folder. */
if (vjunk != NULL) {
camel_vee_folder_add_folder (vjunk, folder, NULL);
g_object_unref (vjunk);
}
/* If this is a normal folder and the store uses a
* virtual Trash folder, let the virtual Trash folder
* track this folder. */
if (vtrash != NULL) {
camel_vee_folder_add_folder (vtrash, folder, NULL);
g_object_unref (vtrash);
}
camel_operation_pop_message (cancellable);
if (folder != NULL)
camel_store_folder_opened (store, folder);
/* Handle CAMEL_STORE_FOLDER_CREATE flag. */
if (create_folder) {
CamelFolderInfo *folder_info;
gchar *reversed_name;
gchar **child_and_parent;
g_warn_if_fail (folder == NULL);
/* XXX GLib lacks a rightmost string splitting function,
* so we'll reverse the string and use g_strsplit(). */
reversed_name = g_strreverse (g_strdup (folder_name));
child_and_parent = g_strsplit (reversed_name, "/", 2);
g_return_val_if_fail (child_and_parent[0] != NULL, NULL);
/* Element 0 is the new folder name.
* Element 1 is the parent path, or NULL. */
/* XXX Reverse the child and parent names back. */
g_strreverse (child_and_parent[0]);
if (child_and_parent[1] != NULL)
g_strreverse (child_and_parent[1]);
/* Call the method directly to avoid the queuing
* behavior of camel_store_create_folder_sync(). */
folder_info = class->create_folder_sync (
store,
child_and_parent[1],
child_and_parent[0],
cancellable, error);
CAMEL_CHECK_GERROR (
store, create_folder_sync,
folder_info != NULL, error);
g_strfreev (child_and_parent);
g_free (reversed_name);
/* If we successfully created the folder, retry the
* method without the CAMEL_STORE_FOLDER_CREATE flag. */
if (folder_info != NULL) {
camel_folder_info_free (folder_info);
goto try_again;
}
}
return folder;
}
/* Helper for camel_store_get_folder() */
static void
store_get_folder_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelFolder *folder;
AsyncContext *async_context;
GError *local_error = NULL;
async_context = (AsyncContext *) task_data;
folder = camel_store_get_folder_sync (
CAMEL_STORE (source_object),
async_context->folder_name_1,
async_context->flags,
cancellable, &local_error);
if (local_error != NULL) {
g_warn_if_fail (folder == NULL);
g_task_return_error (task, local_error);
} else {
g_task_return_pointer (
task, folder,
(GDestroyNotify) g_object_unref);
}
}
/**
* camel_store_get_folder:
* @store: a #CamelStore
* @folder_name: name of the folder to get
* @flags: folder flags (create, save body index, etc)
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously gets a specific folder object from @store by name.
*
* When the operation is finished, @callback will be called. You can then
* call camel_store_get_folder_finish() to get the result of the operation.
*
* Since: 3.0
**/
void
camel_store_get_folder (CamelStore *store,
const gchar *folder_name,
CamelStoreGetFolderFlags flags,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (folder_name != NULL);
async_context = g_slice_new0 (AsyncContext);
async_context->folder_name_1 = g_strdup (folder_name);
async_context->flags = flags;
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_get_folder);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
g_task_run_in_thread (task, store_get_folder_thread);
g_object_unref (task);
}
/**
* camel_store_get_folder_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_get_folder().
*
* Returns: the requested #CamelFolder object, or %NULL on error
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_folder_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (g_task_is_valid (result, store), NULL);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_get_folder), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* camel_store_get_folder_info_sync:
* @store: a #CamelStore
* @top: the name of the folder to start from
* @flags: various CAMEL_STORE_FOLDER_INFO_* flags to control behavior
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* This fetches information about the folder structure of @store,
* starting with @top, and returns a tree of #CamelFolderInfo
* structures. If @flags includes %CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
* only subscribed folders will be listed. If the store doesn't support
* subscriptions, then it will list all folders. If @flags includes
* %CAMEL_STORE_FOLDER_INFO_RECURSIVE, the returned tree will include
* all levels of hierarchy below @top. If not, it will only include
* the immediate subfolders of @top. If @flags includes
* %CAMEL_STORE_FOLDER_INFO_FAST, the unread_message_count fields of
* some or all of the structures may be set to %-1, if the store cannot
* determine that information quickly. If @flags includes
* %CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL, don't include special virtual
* folders (such as vTrash or vJunk).
*
* The returned #CamelFolderInfo tree should be freed with
* camel_folder_info_free().
*
* The CAMEL_STORE_FOLDER_INFO_FAST flag should be considered
* deprecated; most backends will behave the same whether it is
* supplied or not. The only guaranteed way to get updated folder
* counts is to both open the folder and invoke refresh_info() it.
*
* Returns: a #CamelFolderInfo tree, or %NULL on error
*
* Since: 3.0
**/
CamelFolderInfo *
camel_store_get_folder_info_sync (CamelStore *store,
const gchar *top,
CamelStoreGetFolderInfoFlags flags,
GCancellable *cancellable,
GError **error)
{
CamelStoreClass *class;
CamelFolderInfo *info;
gboolean allow_virtual;
gboolean start_at_root;
gboolean store_has_vtrash;
gboolean store_has_vjunk;
gchar *name;
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
class = CAMEL_STORE_GET_CLASS (store);
g_return_val_if_fail (class->get_folder_info_sync != NULL, NULL);
name = camel_service_get_name (CAMEL_SERVICE (store), TRUE);
camel_operation_push_message (
cancellable, _("Scanning folders in '%s'"), name);
g_free (name);
/* Recover from a dropped connection, unless we're offline. */
if (!store_maybe_connect_sync (store, cancellable, error)) {
camel_operation_pop_message (cancellable);
return NULL;
}
info = class->get_folder_info_sync (
store, top, flags, cancellable, error);
if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) == 0)
CAMEL_CHECK_GERROR (
store, get_folder_info_sync, info != NULL, error);
/* For readability. */
allow_virtual = ((flags & CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL) == 0);
start_at_root = (top == NULL || *top == '\0');
store_has_vtrash = ((store->flags & CAMEL_STORE_VTRASH) != 0);
store_has_vjunk = ((store->flags & CAMEL_STORE_VJUNK) != 0);
if (info != NULL && start_at_root && allow_virtual) {
if (store_has_vtrash) {
/* Add the virtual Trash folder. */
add_special_info (
store,
info,
CAMEL_VTRASH_NAME,
_("Trash"),
FALSE,
CAMEL_FOLDER_VIRTUAL |
CAMEL_FOLDER_SYSTEM |
CAMEL_FOLDER_VTRASH |
CAMEL_FOLDER_TYPE_TRASH);
}
if (store_has_vjunk) {
/* Add the virtual Junk folder. */
add_special_info (
store,
info,
CAMEL_VJUNK_NAME,
_("Junk"),
TRUE,
CAMEL_FOLDER_VIRTUAL |
CAMEL_FOLDER_SYSTEM |
CAMEL_FOLDER_VTRASH |
CAMEL_FOLDER_TYPE_JUNK);
}
} else if (info == NULL && !start_at_root && allow_virtual) {
CamelFolderInfo *root_info = NULL;
gboolean start_at_vtrash;
gboolean start_at_vjunk;
start_at_vtrash =
store_has_vtrash &&
g_str_equal (top, CAMEL_VTRASH_NAME);
start_at_vjunk =
store_has_vjunk &&
g_str_equal (top, CAMEL_VJUNK_NAME);
if (start_at_vtrash) {
root_info = class->get_folder_info_sync (
store, NULL,
flags & (~CAMEL_STORE_FOLDER_INFO_RECURSIVE),
cancellable, error);
if (root_info != NULL)
add_special_info (
store,
root_info,
CAMEL_VTRASH_NAME,
_("Trash"),
FALSE,
CAMEL_FOLDER_VIRTUAL |
CAMEL_FOLDER_SYSTEM |
CAMEL_FOLDER_VTRASH |
CAMEL_FOLDER_TYPE_TRASH);
} else if (start_at_vjunk) {
root_info = class->get_folder_info_sync (
store, NULL,
flags & (~CAMEL_STORE_FOLDER_INFO_RECURSIVE),
cancellable, error);
if (root_info != NULL)
add_special_info (
store,
root_info,
CAMEL_VJUNK_NAME,
_("Junk"),
TRUE,
CAMEL_FOLDER_VIRTUAL |
CAMEL_FOLDER_SYSTEM |
CAMEL_FOLDER_VTRASH |
CAMEL_FOLDER_TYPE_JUNK);
}
if (root_info != NULL) {
info = root_info->next;
root_info->next = NULL;
info->next = NULL;
info->parent = NULL;
camel_folder_info_free (root_info);
}
}
camel_operation_pop_message (cancellable);
if (camel_debug_start ("store:folder_info")) {
const gchar *uid;
uid = camel_service_get_uid (CAMEL_SERVICE (store));
printf (
"Get folder info(%p:%s, '%s') =\n",
(gpointer) store, uid, top ? top : "");
dump_fi (info, 2);
camel_debug_end ();
}
return info;
}
/* Helper for camel_store_get_folder_info() */
static void
store_get_folder_info_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelFolderInfo *folder_info;
AsyncContext *async_context;
GError *local_error = NULL;
async_context = (AsyncContext *) task_data;
folder_info = camel_store_get_folder_info_sync (
CAMEL_STORE (source_object),
async_context->folder_name_1,
async_context->flags,
cancellable, &local_error);
if (local_error != NULL) {
g_warn_if_fail (folder_info == NULL);
g_task_return_error (task, local_error);
} else {
g_task_return_pointer (
task, folder_info,
(GDestroyNotify) camel_folder_info_free);
}
}
/**
* camel_store_get_folder_info:
* @store: a #CamelStore
* @top: the name of the folder to start from
* @flags: various CAMEL_STORE_FOLDER_INFO_* flags to control behavior
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously fetches information about the folder structure of @store,
* starting with @top. For details of the behavior, see
* camel_store_get_folder_info_sync().
*
* When the operation is finished, @callback will be called. You can
* then call camel_store_get_folder_info_finish() to get the result of
* the operation.
*
* Since: 3.0
**/
void
camel_store_get_folder_info (CamelStore *store,
const gchar *top,
CamelStoreGetFolderInfoFlags flags,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_STORE (store));
async_context = g_slice_new0 (AsyncContext);
async_context->folder_name_1 = g_strdup (top);
async_context->flags = flags;
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_get_folder_info);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
g_task_run_in_thread (task, store_get_folder_info_thread);
g_object_unref (task);
}
/**
* camel_store_get_folder_info_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_get_folder_info().
* The returned #CamelFolderInfo tree should be freed with
* camel_folder_info_free().
*
* Returns: a #CamelFolderInfo tree, or %NULL on error
*
* Since: 3.0
**/
CamelFolderInfo *
camel_store_get_folder_info_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (g_task_is_valid (result, store), NULL);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_get_folder_info), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* camel_store_get_inbox_folder_sync:
* @store: a #CamelStore
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Gets the folder in @store into which new mail is delivered.
*
* Returns: the inbox folder for @store, or %NULL on error or if no such
* folder exists
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_inbox_folder_sync (CamelStore *store,
GCancellable *cancellable,
GError **error)
{
CamelStoreClass *class;
CamelFolder *folder;
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
class = CAMEL_STORE_GET_CLASS (store);
g_return_val_if_fail (class->get_inbox_folder_sync != NULL, NULL);
folder = class->get_inbox_folder_sync (store, cancellable, error);
CAMEL_CHECK_GERROR (
store, get_inbox_folder_sync, folder != NULL, error);
return folder;
}
/* Helper for camel_store_get_inbox_folder() */
static void
store_get_inbox_folder_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelFolder *folder;
GError *local_error = NULL;
folder = camel_store_get_inbox_folder_sync (
CAMEL_STORE (source_object),
cancellable, &local_error);
if (local_error != NULL) {
g_warn_if_fail (folder == NULL);
g_task_return_error (task, local_error);
} else {
g_task_return_pointer (
task, folder,
(GDestroyNotify) g_object_unref);
}
}
/**
* camel_store_get_inbox_folder:
* @store: a #CamelStore
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously gets the folder in @store into which new mail is delivered.
*
* When the operation is finished, @callback will be called. You can
* then call camel_store_get_inbox_folder_finish() to get the result of
* the operation.
*
* Since: 3.0
**/
void
camel_store_get_inbox_folder (CamelStore *store,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_return_if_fail (CAMEL_IS_STORE (store));
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_get_inbox_folder);
g_task_set_priority (task, io_priority);
g_task_run_in_thread (task, store_get_inbox_folder_thread);
g_object_unref (task);
}
/**
* camel_store_get_inbox_folder_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_get_inbox_folder().
*
* Returns: the inbox folder for @store, or %NULL on error or if no such
* folder exists
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_inbox_folder_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (g_task_is_valid (result, store), NULL);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_get_inbox_folder), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* camel_store_get_junk_folder_sync:
* @store: a #CamelStore
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Gets the folder in @store into which junk is delivered.
*
* Returns: the junk folder for @store, or %NULL on error or if no such
* folder exists
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_junk_folder_sync (CamelStore *store,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
if ((store->flags & CAMEL_STORE_VJUNK) == 0) {
CamelStoreClass *class;
CamelFolder *folder;
class = CAMEL_STORE_GET_CLASS (store);
g_return_val_if_fail (class->get_junk_folder_sync != NULL, NULL);
folder = class->get_junk_folder_sync (store, cancellable, error);
CAMEL_CHECK_GERROR (
store, get_junk_folder_sync, folder != NULL, error);
return folder;
}
return camel_store_get_folder_sync (
store, CAMEL_VJUNK_NAME, 0, cancellable, error);
}
/* Helper for camel_store_get_junk_folder() */
static void
store_get_junk_folder_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelFolder *folder;
GError *local_error = NULL;
folder = camel_store_get_junk_folder_sync (
CAMEL_STORE (source_object),
cancellable, &local_error);
if (local_error != NULL) {
g_warn_if_fail (folder == NULL);
g_task_return_error (task, local_error);
} else {
g_task_return_pointer (
task, folder,
(GDestroyNotify) g_object_unref);
}
}
/**
* camel_store_get_junk_folder:
* @store: a #CamelStore
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously gets the folder in @store into which junk is delivered.
*
* When the operation is finished, @callback will be called. You can
* then call camel_store_get_junk_folder_finish() to get the result of
* the operation.
*
* Since: 3.0
**/
void
camel_store_get_junk_folder (CamelStore *store,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_return_if_fail (CAMEL_IS_STORE (store));
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_get_junk_folder);
g_task_set_priority (task, io_priority);
g_task_run_in_thread (task, store_get_junk_folder_thread);
g_object_unref (task);
}
/**
* camel_store_get_junk_folder_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_get_junk_folder().
*
* Returns: the junk folder for @store, or %NULL on error or if no such
* folder exists
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_junk_folder_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (g_task_is_valid (result, store), NULL);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_get_junk_folder), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* camel_store_get_trash_folder_sync:
* @store: a #CamelStore
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Gets the folder in @store into which trash is delivered.
*
* Returns: the trash folder for @store, or %NULL on error or if no such
* folder exists
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_trash_folder_sync (CamelStore *store,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
if ((store->flags & CAMEL_STORE_VTRASH) == 0) {
CamelStoreClass *class;
CamelFolder *folder;
class = CAMEL_STORE_GET_CLASS (store);
g_return_val_if_fail (class->get_trash_folder_sync != NULL, NULL);
folder = class->get_trash_folder_sync (
store, cancellable, error);
CAMEL_CHECK_GERROR (
store, get_trash_folder_sync, folder != NULL, error);
return folder;
}
return camel_store_get_folder_sync (
store, CAMEL_VTRASH_NAME, 0, cancellable, error);
}
/* Helper for camel_store_get_trash_folder() */
static void
store_get_trash_folder_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelFolder *folder;
GError *local_error = NULL;
folder = camel_store_get_trash_folder_sync (
CAMEL_STORE (source_object),
cancellable, &local_error);
if (local_error != NULL) {
g_warn_if_fail (folder == NULL);
g_task_return_error (task, local_error);
} else {
g_task_return_pointer (
task, folder,
(GDestroyNotify) g_object_unref);
}
}
/**
* camel_store_get_trash_folder:
* @store: a #CamelStore
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously gets the folder in @store into which trash is delivered.
*
* When the operation is finished, @callback will be called. You can
* then call camel_store_get_trash_folder_finish() to get the result of
* the operation.
*
* Since: 3.0
**/
void
camel_store_get_trash_folder (CamelStore *store,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_return_if_fail (CAMEL_IS_STORE (store));
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_get_trash_folder);
g_task_set_priority (task, io_priority);
g_task_run_in_thread (task, store_get_trash_folder_thread);
g_object_unref (task);
}
/**
* camel_store_get_trash_folder_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_get_trash_folder().
*
* Returns: the trash folder for @store, or %NULL on error or if no such
* folder exists
*
* Since: 3.0
**/
CamelFolder *
camel_store_get_trash_folder_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (g_task_is_valid (result, store), NULL);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_get_trash_folder), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* camel_store_create_folder_sync:
* @store: a #CamelStore
* @parent_name: name of the new folder's parent, or %NULL
* @folder_name: name of the folder to create
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Creates a new folder as a child of an existing folder.
* @parent_name can be %NULL to create a new top-level folder.
* The returned #CamelFolderInfo struct should be freed with
* camel_folder_info_free().
*
* Returns: info about the created folder, or %NULL on error
*
* Since: 3.0
**/
CamelFolderInfo *
camel_store_create_folder_sync (CamelStore *store,
const gchar *parent_name,
const gchar *folder_name,
GCancellable *cancellable,
GError **error)
{
CamelAsyncClosure *closure;
GAsyncResult *result;
CamelFolderInfo *folder_info;
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (folder_name != NULL, NULL);
closure = camel_async_closure_new ();
camel_store_create_folder (
store, parent_name, folder_name,
G_PRIORITY_DEFAULT, cancellable,
camel_async_closure_callback, closure);
result = camel_async_closure_wait (closure);
folder_info = camel_store_create_folder_finish (store, result, error);
camel_async_closure_free (closure);
return folder_info;
}
/* Helper for camel_store_create_folder() */
static void
store_create_folder_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelStore *store;
CamelStoreClass *class;
AsyncContext *async_context;
CamelFolderInfo *folder_info;
const gchar *parent_name;
const gchar *folder_name;
GError *local_error = NULL;
store = CAMEL_STORE (source_object);
async_context = (AsyncContext *) task_data;
parent_name = async_context->folder_name_1;
folder_name = async_context->folder_name_2;
class = CAMEL_STORE_GET_CLASS (store);
g_return_if_fail (class->create_folder_sync != NULL);
if (parent_name == NULL || *parent_name == '\0') {
gboolean reserved_vfolder_name;
reserved_vfolder_name =
((store->flags & CAMEL_STORE_VJUNK) &&
g_str_equal (folder_name, CAMEL_VJUNK_NAME)) ||
((store->flags & CAMEL_STORE_VTRASH) &&
g_str_equal (folder_name, CAMEL_VTRASH_NAME));
if (reserved_vfolder_name) {
g_task_return_new_error (
task, CAMEL_STORE_ERROR,
CAMEL_STORE_ERROR_INVALID,
_("Cannot create folder: %s: folder exists"),
folder_name);
return;
}
}
camel_operation_push_message (
cancellable, _("Creating folder '%s'"), folder_name);
folder_info = class->create_folder_sync (
store, parent_name, folder_name, cancellable, &local_error);
CAMEL_CHECK_LOCAL_GERROR (
store, create_folder_sync, folder_info != NULL, local_error);
camel_operation_pop_message (cancellable);
if (local_error != NULL) {
g_warn_if_fail (folder_info == NULL);
g_task_return_error (task, local_error);
} else {
g_task_return_pointer (
task, folder_info,
(GDestroyNotify) camel_folder_info_free);
}
}
/**
* camel_store_create_folder:
* @store: a #CamelStore
* @parent_name: name of the new folder's parent, or %NULL
* @folder_name: name of the folder to create
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously creates a new folder as a child of an existing folder.
* @parent_name can be %NULL to create a new top-level folder.
*
* When the operation is finished, @callback will be called. You can then
* call camel_store_create_folder_finish() to get the result of the operation.
*
* Since: 3.0
**/
void
camel_store_create_folder (CamelStore *store,
const gchar *parent_name,
const gchar *folder_name,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
CamelService *service;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (folder_name != NULL);
service = CAMEL_SERVICE (store);
async_context = g_slice_new0 (AsyncContext);
async_context->folder_name_1 = g_strdup (parent_name);
async_context->folder_name_2 = g_strdup (folder_name);
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_create_folder);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
camel_service_queue_task (
service, task, store_create_folder_thread);
g_object_unref (task);
}
/**
* camel_store_create_folder_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_create_folder().
* The returned #CamelFolderInfo struct should be freed with
* camel_folder_info_free().
*
* Returns: info about the created folder, or %NULL on error
*
* Since: 3.0
**/
CamelFolderInfo *
camel_store_create_folder_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (g_task_is_valid (result, store), NULL);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_create_folder), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
/**
* camel_store_delete_folder_sync:
* @store: a #CamelStore
* @folder_name: name of the folder to delete
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Deletes the folder described by @folder_name. The folder must be empty.
*
* Returns: %TRUE on success, %FALSE on failure
*
* Since: 3.0
**/
gboolean
camel_store_delete_folder_sync (CamelStore *store,
const gchar *folder_name,
GCancellable *cancellable,
GError **error)
{
CamelAsyncClosure *closure;
GAsyncResult *result;
gboolean success;
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
g_return_val_if_fail (folder_name != NULL, FALSE);
closure = camel_async_closure_new ();
camel_store_delete_folder (
store, folder_name,
G_PRIORITY_DEFAULT, cancellable,
camel_async_closure_callback, closure);
result = camel_async_closure_wait (closure);
success = camel_store_delete_folder_finish (store, result, error);
camel_async_closure_free (closure);
return success;
}
/* Helper for camel_store_delete_folder() */
static void
store_delete_folder_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelStore *store;
CamelStoreClass *class;
AsyncContext *async_context;
const gchar *folder_name;
gboolean reserved_vfolder_name;
gboolean success;
GError *local_error = NULL;
store = CAMEL_STORE (source_object);
async_context = (AsyncContext *) task_data;
folder_name = async_context->folder_name_1;
class = CAMEL_STORE_GET_CLASS (store);
g_return_if_fail (class->delete_folder_sync != NULL);
reserved_vfolder_name =
((store->flags & CAMEL_STORE_VJUNK) &&
g_str_equal (folder_name, CAMEL_VJUNK_NAME)) ||
((store->flags & CAMEL_STORE_VTRASH) &&
g_str_equal (folder_name, CAMEL_VTRASH_NAME));
if (reserved_vfolder_name) {
g_task_return_new_error (
task, CAMEL_STORE_ERROR,
CAMEL_STORE_ERROR_NO_FOLDER,
_("Cannot delete folder: %s: Invalid operation"),
folder_name);
return;
}
success = class->delete_folder_sync (
store, folder_name, cancellable, &local_error);
CAMEL_CHECK_LOCAL_GERROR (
store, delete_folder_sync, success, local_error);
/* ignore 'no such table' errors */
if (local_error != NULL &&
g_ascii_strncasecmp (local_error->message, "no such table", 13) == 0)
g_clear_error (&local_error);
if (local_error != NULL) {
g_task_return_error (task, local_error);
} else {
cs_delete_cached_folder (store, folder_name);
g_task_return_boolean (task, success);
}
}
/**
* camel_store_delete_folder:
* @store: a #CamelStore
* @folder_name: name of the folder to delete
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously deletes the folder described by @folder_name. The
* folder must be empty.
*
* When the operation is finished, @callback will be called. You can then
* call camel_store_delete_folder_finish() to get the result of the operation.
*
* Since: 3.0
**/
void
camel_store_delete_folder (CamelStore *store,
const gchar *folder_name,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
CamelService *service;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (folder_name != NULL);
service = CAMEL_SERVICE (store);
async_context = g_slice_new0 (AsyncContext);
async_context->folder_name_1 = g_strdup (folder_name);
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_delete_folder);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
camel_service_queue_task (
service, task, store_delete_folder_thread);
g_object_unref (task);
}
/**
* camel_store_delete_folder_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_delete_folder().
*
* Returns: %TRUE on success, %FALSE on error
*
* Since: 3.0
**/
gboolean
camel_store_delete_folder_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_delete_folder), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/**
* camel_store_rename_folder_sync:
* @store: a #CamelStore
* @old_name: the current name of the folder
* @new_name: the new name of the folder
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Renames the folder described by @old_name to @new_name.
*
* Returns: %TRUE on success, %FALSE on error
*
* Since: 3.0
**/
gboolean
camel_store_rename_folder_sync (CamelStore *store,
const gchar *old_name,
const gchar *new_name,
GCancellable *cancellable,
GError **error)
{
CamelAsyncClosure *closure;
GAsyncResult *result;
gboolean success;
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
g_return_val_if_fail (old_name != NULL, FALSE);
g_return_val_if_fail (new_name != NULL, FALSE);
closure = camel_async_closure_new ();
camel_store_rename_folder (
store, old_name, new_name,
G_PRIORITY_DEFAULT, cancellable,
camel_async_closure_callback, closure);
result = camel_async_closure_wait (closure);
success = camel_store_rename_folder_finish (store, result, error);
camel_async_closure_free (closure);
return success;
}
/* Helper for camel_store_rename_folder() */
static void
store_rename_folder_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
CamelStore *store;
CamelStoreClass *class;
CamelFolder *folder;
GPtrArray *folders;
const gchar *old_name;
const gchar *new_name;
gboolean reserved_vfolder_name;
gboolean success;
gsize old_name_len;
guint ii;
AsyncContext *async_context;
GError *local_error = NULL;
store = CAMEL_STORE (source_object);
async_context = (AsyncContext *) task_data;
old_name = async_context->folder_name_1;
new_name = async_context->folder_name_2;
class = CAMEL_STORE_GET_CLASS (store);
g_return_if_fail (class->rename_folder_sync != NULL);
if (g_str_equal (old_name, new_name)) {
g_task_return_boolean (task, TRUE);
return;
}
reserved_vfolder_name =
((store->flags & CAMEL_STORE_VJUNK) &&
g_str_equal (old_name, CAMEL_VJUNK_NAME)) ||
((store->flags & CAMEL_STORE_VTRASH) &&
g_str_equal (old_name, CAMEL_VTRASH_NAME));
if (reserved_vfolder_name) {
g_task_return_new_error (
task, CAMEL_STORE_ERROR,
CAMEL_STORE_ERROR_NO_FOLDER,
_("Cannot rename folder: %s: Invalid operation"),
old_name);
return;
}
old_name_len = strlen (old_name);
/* If the folder is open (or any subfolders of the open folder)
* We need to rename them atomically with renaming the actual
* folder path. */
folders = camel_object_bag_list (store->folders);
for (ii = 0; ii < folders->len; ii++) {
const gchar *full_name;
gsize full_name_len;
folder = folders->pdata[ii];
full_name = camel_folder_get_full_name (folder);
full_name_len = strlen (full_name);
if ((full_name_len == old_name_len &&
strcmp (full_name, old_name) == 0)
|| ((full_name_len > old_name_len)
&& strncmp (full_name, old_name, old_name_len) == 0
&& full_name[old_name_len] == '/')) {
camel_folder_lock (folder);
} else {
g_ptr_array_remove_index_fast (folders, ii);
ii--;
g_object_unref (folder);
}
}
/* Now try the real rename (will emit renamed signal) */
success = class->rename_folder_sync (
store, old_name, new_name, cancellable, &local_error);
CAMEL_CHECK_LOCAL_GERROR (
store, rename_folder_sync, success, local_error);
/* If it worked, update all open folders/unlock them */
if (success) {
CamelStoreGetFolderInfoFlags flags;
CamelFolderInfo *folder_info;
flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE;
for (ii = 0; ii < folders->len; ii++) {
const gchar *full_name;
gchar *new;
folder = folders->pdata[ii];
full_name = camel_folder_get_full_name (folder);
new = g_strdup_printf ("%s%s", new_name, full_name + strlen (old_name));
camel_object_bag_rekey (store->folders, folder, new);
camel_folder_rename (folder, new);
g_free (new);
camel_folder_unlock (folder);
g_object_unref (folder);
}
/* Emit renamed signal */
if (CAMEL_IS_SUBSCRIBABLE (store))
flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
folder_info = class->get_folder_info_sync (
store, new_name, flags, cancellable, &local_error);
CAMEL_CHECK_LOCAL_GERROR (
store, get_folder_info,
folder_info != NULL, local_error);
if (folder_info != NULL) {
camel_store_folder_renamed (store, old_name, folder_info);
camel_folder_info_free (folder_info);
}
} else {
/* Failed, just unlock our folders for re-use */
for (ii = 0; ii < folders->len; ii++) {
folder = folders->pdata[ii];
camel_folder_unlock (folder);
g_object_unref (folder);
}
}
g_ptr_array_free (folders, TRUE);
if (local_error != NULL) {
g_task_return_error (task, local_error);
} else {
g_task_return_boolean (task, success);
}
}
/**
* camel_store_rename_folder:
* @store: a #CamelStore
* @old_name: the current name of the folder
* @new_name: the new name of the folder
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Asynchronously renames the folder described by @old_name to @new_name.
*
* When the operation is finished, @callback will be called. You can then
* call camel_store_rename_folder_finish() to get the result of the operation.
*
* Since: 3.0
**/
void
camel_store_rename_folder (CamelStore *store,
const gchar *old_name,
const gchar *new_name,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
CamelService *service;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (old_name != NULL);
g_return_if_fail (new_name != NULL);
service = CAMEL_SERVICE (store);
async_context = g_slice_new0 (AsyncContext);
async_context->folder_name_1 = g_strdup (old_name);
async_context->folder_name_2 = g_strdup (new_name);
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_rename_folder);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
camel_service_queue_task (
service, task, store_rename_folder_thread);
g_object_unref (task);
}
/**
* camel_store_rename_folder_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_rename_folder().
*
* Returns: %TRUE on success, %FALSE on error
*
* Since: 3.0
**/
gboolean
camel_store_rename_folder_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_rename_folder), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/**
* camel_store_synchronize_sync:
* @store: a #CamelStore
* @expunge: whether to expunge after synchronizing
* @cancellable: optional #GCancellable object, or %NULL
* @error: return location for a #GError, or %NULL
*
* Synchronizes any changes that have been made to @store and its folders
* with the real store.
*
* Returns: %TRUE on success, %FALSE on error
*
* Since: 3.0
**/
gboolean
camel_store_synchronize_sync (CamelStore *store,
gboolean expunge,
GCancellable *cancellable,
GError **error)
{
CamelStoreClass *class;
gboolean success;
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
class = CAMEL_STORE_GET_CLASS (store);
g_return_val_if_fail (class->synchronize_sync != NULL, FALSE);
success = class->synchronize_sync (store, expunge, cancellable, error);
CAMEL_CHECK_GERROR (store, synchronize_sync, success, error);
return success;
}
/* Helper for camel_store_synchronize() */
static void
store_synchronize_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
gboolean success;
AsyncContext *async_context;
GError *local_error = NULL;
async_context = (AsyncContext *) task_data;
success = camel_store_synchronize_sync (
CAMEL_STORE (source_object),
async_context->expunge,
cancellable, &local_error);
if (local_error != NULL) {
g_task_return_error (task, local_error);
} else {
g_task_return_boolean (task, success);
}
}
/**
* camel_store_synchronize:
* @store: a #CamelStore
* @expunge: whether to expunge after synchronizing
* @io_priority: the I/O priority of the request
* @cancellable: optional #GCancellable object, or %NULL
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to the callback function
*
* Synchronizes any changes that have been made to @store and its folders
* with the real store asynchronously.
*
* When the operation is finished, @callback will be called. You can then
* call camel_store_synchronize_finish() to get the result of the operation.
*
* Since: 3.0
**/
void
camel_store_synchronize (CamelStore *store,
gboolean expunge,
gint io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
AsyncContext *async_context;
g_return_if_fail (CAMEL_IS_STORE (store));
async_context = g_slice_new0 (AsyncContext);
async_context->expunge = expunge;
task = g_task_new (store, cancellable, callback, user_data);
g_task_set_source_tag (task, camel_store_synchronize);
g_task_set_priority (task, io_priority);
g_task_set_task_data (
task, async_context,
(GDestroyNotify) async_context_free);
g_task_run_in_thread (task, store_synchronize_thread);
g_object_unref (task);
}
/**
* camel_store_synchronize_finish:
* @store: a #CamelStore
* @result: a #GAsyncResult
* @error: return location for a #GError, or %NULL
*
* Finishes the operation started with camel_store_synchronize().
*
* Returns: %TRUE on success, %FALSE on error
*
* Since: 3.0
**/
gboolean
camel_store_synchronize_finish (CamelStore *store,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (
result, camel_store_synchronize), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/**
* camel_store_maybe_run_db_maintenance:
* @store: a #CamelStore instance
* @error: (allow-none): return location for a #GError, or %NULL
*
* Checks the state of the current CamelDB used for the @store and eventually
* runs maintenance routines on it.
*
* Returns: Whether succeeded.
*
* Since: 3.16
**/
gboolean
camel_store_maybe_run_db_maintenance (CamelStore *store,
GError **error)
{
g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
if (g_atomic_int_get (&store->priv->maintenance_lock) > 0)
return TRUE;
if (!store->cdb_w)
return TRUE;
return camel_db_maybe_run_maintenance (store->cdb_w, error);
}