/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/***************************************************************************
 *            camel-imapx-extd-server.c
 *
 *  2011-11-28, 20:16:38
 *  Copyright 2011, Christian Hilberg
 *  <hilberg@unix-ag.org>
 ****************************************************************************/

/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This program 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 main.c; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/*----------------------------------------------------------------------------*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

// fixme, use own type funcs
#include <ctype.h>

#include <time.h>
#include <errno.h>
#include <string.h>
#include <glib/gstdio.h>
#include <glib/gi18n-lib.h>

#include "camel-imapx-command.h"
#include "camel-imapx-job.h"
#include "camel-imapx-settings.h"
#include "camel-imapx-summary.h"
#include "camel-imapx-utils.h"

#include "camel-imapx-extd-folder.h"
#include "camel-imapx-extd-store.h"

#include "camel-imapx-extd-server.h"
#include "camel-imapx-server-defs.h"
#include "camel-imapx-server-friend.h"

/*----------------------------------------------------------------------------*/

typedef struct _CamelIMAPXExtdServerPrivate CamelIMAPXExtdServerPrivate;
struct _CamelIMAPXExtdServerPrivate {
	CamelImapxMetadata *md; /* raw annotation data (different from CamelKolabImapxMetadata) */
};

#define CAMEL_IMAPX_EXTD_SERVER_PRIVATE(obj)  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CAMEL_TYPE_IMAPX_EXTD_SERVER, CamelIMAPXExtdServerPrivate))

G_DEFINE_TYPE (CamelIMAPXExtdServer, camel_imapx_extd_server, CAMEL_TYPE_IMAPX_SERVER)

/*----------------------------------------------------------------------------*/
/* externs / module statics */

extern gint camel_application_is_exiting;
static guint signals[LAST_SIGNAL];

/*----------------------------------------------------------------------------*/
/* object init */

static void
camel_imapx_extd_server_init (CamelIMAPXExtdServer *self)
{
	CamelIMAPXExtdServerPrivate *priv = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	priv = CAMEL_IMAPX_EXTD_SERVER_PRIVATE (self);

	priv->md = camel_imapx_metadata_new (CAMEL_IMAPX_METADATA_PROTO_INVAL,
	                                     FALSE);
}

static void
camel_imapx_extd_server_constructed (GObject *object)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (object));

	G_OBJECT_CLASS (camel_imapx_extd_server_parent_class)->constructed (object);
}

static void
camel_imapx_extd_server_dispose (GObject *object)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (object));

	G_OBJECT_CLASS (camel_imapx_extd_server_parent_class)->dispose (object);
}

static void
camel_imapx_extd_server_finalize (GObject *object)
{
	CamelIMAPXExtdServer *self = NULL;
	CamelIMAPXExtdServerPrivate *priv = NULL;

	self = CAMEL_IMAPX_EXTD_SERVER (object);
	priv = CAMEL_IMAPX_EXTD_SERVER_PRIVATE (self);

	if (priv->md != NULL)
		camel_imapx_metadata_free (priv->md);

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

/*----------------------------------------------------------------------------*/
/* internal statics */

static gboolean
extd_server_untagged_annotation (CamelIMAPXServer *is,
                                 gboolean *lsub,
                                 guint id,
                                 GCancellable *cancellable,
                                 GError **err)
{
	CamelIMAPXExtdServer *self = NULL;
	CamelIMAPXExtdServerPrivate *priv = NULL;
	guint32 capa = 0;
	GError *tmp_err = NULL;
	gboolean parse_and_add_ok = FALSE;

	(void)lsub;
	(void)id;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (is));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	self = CAMEL_IMAPX_EXTD_SERVER (is);
	priv = CAMEL_IMAPX_EXTD_SERVER_PRIVATE (self);

	/* capability check */
	capa = is->cinfo->capa & IMAPX_ANNOTATEMORE;
	if (!capa) {
		g_set_error (err,
		             CAMEL_IMAPX_ERROR,
		             1, /* FIXME define and add a sensible code here */
		             "Got ANNOTATION response but server did not advertise ANNOTATEMORE capability");
		return FALSE;
	}

	parse_and_add_ok = camel_imapx_metadata_add_from_server_response (priv->md,
	                                                                  is->stream,
	                                                                  cancellable,
	                                                                  &tmp_err);
	if (!parse_and_add_ok) {
		g_propagate_error (err, tmp_err);
		return FALSE;
	}

	return TRUE;
}

static gboolean
extd_server_untagged (CamelIMAPXExtdServer *self,
                      GCancellable *cancellable,
                      GError **error)
{
	/* modified dupe of imapx_untagged */

	CamelIMAPXServer *is = NULL;
	CamelService *service = NULL;
	CamelSettings *settings = NULL;
	CamelSortType fetch_order;
	guint id, len;
	guchar *token = NULL, *p = NULL, c;
	gint tok;
	gboolean lsub = FALSE;
	struct _status_info *sinfo = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	is = CAMEL_IMAPX_SERVER (self);
	service = CAMEL_SERVICE (is->store);
	settings = camel_service_get_settings (service);

	fetch_order = camel_imapx_settings_get_fetch_order (CAMEL_IMAPX_SETTINGS (settings));

	e(is->tagprefix, "got untagged response\n");
	id = 0;
	tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
	if (tok < 0)
		return FALSE;

	if (tok == IMAPX_TOK_INT) {
		id = strtoul ((gchar *) token, NULL, 10);
		tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
		if (tok < 0)
			return FALSE;
	}

	if (tok == '\n') {
		g_set_error (error, CAMEL_IMAPX_ERROR, 1,
		             "truncated server response");
		return FALSE;
	}

	e(is->tagprefix, "Have token '%s' id %d\n", token, id);
	p = token;
	while ((c = *p))
		*p++ = toupper((gchar) c);

	switch (imapx_tokenise ((const gchar *) token, len)) {
	case IMAPX_CAPABILITY:
		if (is->cinfo)
			imapx_free_capability (is->cinfo);
		is->cinfo = imapx_parse_capability (is->stream, cancellable, error);
		if (is->cinfo == NULL)
			return FALSE;
		c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
		return TRUE;
	case IMAPX_EXPUNGE: {
		guint32 expunge = id;
		CamelIMAPXJob *job = camel_imapx_server_match_active_job (is, IMAPX_JOB_EXPUNGE, NULL);

		/* If there is a job running, let it handle the deletion */
		if (job)
			break;

		c(is->tagprefix, "expunged: %d\n", id);
		if (is->select_folder) {
			gchar *uid = NULL;

			uid = camel_imapx_server_get_uid_from_index (is, is->select_folder->summary, expunge - 1);
			if (!uid)
				break;

			camel_imapx_server_expunge_uid_from_summary (is, uid, TRUE);
		}

		break;
	}
	case IMAPX_VANISHED: {
		GPtrArray *uids;
		GList *uid_list = NULL;
		gboolean unsolicited = TRUE;
		gint i;
		guint len;
		guchar *token;
		gint tok;

		tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
		if (tok < 0)
			return FALSE;
		if (tok == '(') {
			unsolicited = FALSE;
			while (tok != ')') {
				/* We expect this to be 'EARLIER' */
				tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
				if (tok < 0)
					return FALSE;
			}
		} else
			camel_imapx_stream_ungettoken (is->stream, tok, token, len);

		uids = imapx_parse_uids (is->stream, cancellable, error);
		if (uids == NULL)
			return FALSE;

		if (unsolicited) {
			CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *)is->select_folder;

			if (ifolder->exists_on_server < uids->len) {
				c(is->tagprefix, "Error: exists_on_folder %d is fewer than vanished %d\n",
				  ifolder->exists_on_server, uids->len);
				ifolder->exists_on_server = 0;
			} else
				ifolder->exists_on_server -= uids->len;
		}
		if (is->changes == NULL)
			is->changes = camel_folder_change_info_new ();

		for (i = 0; i < uids->len; i++) {
			gchar *uid = g_strdup_printf("%u", GPOINTER_TO_UINT(g_ptr_array_index (uids, i)));

			c(is->tagprefix, "vanished: %s\n", uid);

			uid_list = g_list_append(uid_list, uid);
			camel_folder_change_info_remove_uid (is->changes, uid);
		}
		camel_folder_summary_remove_uids(is->select_folder->summary, uid_list);
		is->expunged = g_list_concat(is->expunged, uid_list);
		g_ptr_array_free (uids, FALSE);
		break;
	}
	case IMAPX_NAMESPACE: {
		CamelIMAPXNamespaceList *nsl = NULL;

		nsl = imapx_parse_namespace_list (is->stream, cancellable, error);
		if (nsl != NULL) {
			CamelIMAPXStore *imapx_store = (CamelIMAPXStore *) is->store;
			CamelIMAPXStoreNamespace *ns;

			if (imapx_store->summary->namespaces)
				camel_imapx_namespace_list_clear (imapx_store->summary->namespaces);
			imapx_store->summary->namespaces = nsl;
			camel_store_summary_touch ((CamelStoreSummary *) imapx_store->summary);

			/* TODO Need to remove imapx_store->dir_sep to support multiple namespaces */
			ns = nsl->personal;
			if (ns)
				imapx_store->dir_sep = ns->sep;
		}

		return TRUE;
	}
	case IMAPX_EXISTS:
		c(is->tagprefix, "exists: %d\n", id);
		is->exists = id;

		if (is->select_folder)
			((CamelIMAPXFolder *) is->select_folder)->exists_on_server = id;

		if (camel_imapx_server_idle_supported (is) && camel_imapx_server_in_idle (is)) {
			if (camel_folder_summary_count (is->select_folder->summary) < id)
				camel_imapx_server_stop_idle (is, error);
		}

		break;
	case IMAPX_FLAGS: {
		guint32 flags;

		imapx_parse_flags (is->stream, &flags, NULL, cancellable, error);

		c(is->tagprefix, "flags: %08x\n", flags);
		break;
	}
	case IMAPX_FETCH: {
		struct _fetch_info *finfo;

		finfo = imapx_parse_fetch (is->stream, cancellable, error);
		if (finfo == NULL) {
			imapx_free_fetch (finfo);
			return FALSE;
		}

		if ((finfo->got & (FETCH_BODY | FETCH_UID)) == (FETCH_BODY | FETCH_UID)) {
			CamelIMAPXJob *job = camel_imapx_server_match_active_job (is, IMAPX_JOB_GET_MESSAGE, finfo->uid);
			GetMessageData *data;

			data = camel_imapx_job_get_data (job);
			g_return_val_if_fail (data != NULL, FALSE);

			/* This must've been a get-message request, fill out the body stream,
			 * in the right spot */

			if (job && job->error == NULL) {
				if (data->use_multi_fetch) {
					data->body_offset = finfo->offset;
					g_seekable_seek (G_SEEKABLE (data->stream), finfo->offset, G_SEEK_SET, NULL, NULL);
				}

				data->body_len = camel_stream_write_to_stream (finfo->body, data->stream, job->cancellable, &job->error);
				if (data->body_len == -1)
					g_prefix_error (
					                &job->error,
					                _("Error writing to cache stream: "));
			}
		}

		if ((finfo->got & FETCH_FLAGS) && !(finfo->got & FETCH_HEADER)) {
			CamelIMAPXJob *job = camel_imapx_server_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO, NULL);
			RefreshInfoData *data = NULL;

			if (job) {
				data = camel_imapx_job_get_data (job);
				g_return_val_if_fail (data != NULL, FALSE);
			}

			/* This is either a refresh_info job, check to see if it is and update
			 * if so, otherwise it must've been an unsolicited response, so update
			 * the summary to match */
			if (data && (finfo->got & FETCH_UID) && data->scan_changes) {
				struct _refresh_info r;

				r.uid = finfo->uid;
				finfo->uid = NULL;
				r.server_flags = finfo->flags;
				r.server_user_flags = finfo->user_flags;
				finfo->user_flags = NULL;
				r.exists = FALSE;
				g_array_append_val (data->infos, r);
			} else if (is->select_folder) {
				CamelFolder *folder;
				CamelMessageInfo *mi = NULL;
				gboolean changed = FALSE;
				gchar *uid = NULL;

				g_object_ref (is->select_folder);
				folder = is->select_folder;

				c(is->tagprefix, "flag changed: %d\n", id);

				if (finfo->got & FETCH_UID) {
					uid = finfo->uid;
					finfo->uid = NULL;
				} else {
					uid = camel_imapx_server_get_uid_from_index (is, folder->summary, id - 1);
				}

				if (uid) {
					mi = camel_folder_summary_get (folder->summary, uid);
					if (mi) {
						/* It's unsolicited _unless_ is->select_pending (i.e. during
						 * a QRESYNC SELECT */
						changed = imapx_update_message_info_flags (mi, finfo->flags, finfo->user_flags, is->permanentflags, folder, !is->select_pending);
					} else {
						/* This (UID + FLAGS for previously unknown message) might
						 * happen during a SELECT (QRESYNC). We should use it. */
						c(is->tagprefix, "flags changed for unknown uid %s\n.", uid);
					}
					finfo->user_flags = NULL;
				}

				if (changed) {
					if (is->changes == NULL)
						is->changes = camel_folder_change_info_new ();

					camel_folder_change_info_change_uid (is->changes, uid);
					g_free (uid);
				}

				if (camel_imapx_server_idle_supported (is) && changed && camel_imapx_server_in_idle (is)) {
					camel_folder_summary_save_to_db (is->select_folder->summary, NULL);
					imapx_update_store_summary (is->select_folder);
					camel_folder_changed (is->select_folder, is->changes);
					camel_folder_change_info_clear (is->changes);
				}

				if (mi)
					camel_message_info_free (mi);
				g_object_unref (folder);
			}
		}

		if ((finfo->got & (FETCH_HEADER | FETCH_UID)) == (FETCH_HEADER | FETCH_UID)) {
			CamelIMAPXJob *job = camel_imapx_server_match_active_job (is, IMAPX_JOB_FETCH_NEW_MESSAGES | IMAPX_JOB_REFRESH_INFO, NULL);

			/* This must be a refresh info job as well, but it has asked for
			 * new messages to be added to the index */

			if (job) {
				CamelMimeParser *mp;
				CamelMessageInfo *mi;

				/* Do we want to save these headers for later too?  Do we care? */

				mp = camel_mime_parser_new ();
				camel_mime_parser_init_with_stream (mp, finfo->header, NULL);
				mi = camel_folder_summary_info_new_from_parser (job->folder->summary, mp);
				g_object_unref (mp);

				if (mi) {
					guint32 server_flags;
					CamelFlag *server_user_flags;
					CamelMessageInfoBase *binfo;
					gboolean free_user_flags = FALSE;

					mi->uid = camel_pstring_strdup (finfo->uid);

					if (!(finfo->got & FETCH_FLAGS)) {
						RefreshInfoData *data;
						struct _refresh_info *r = NULL;
						gint min, max, mid;
						gboolean found = FALSE;

						data = camel_imapx_job_get_data (job);
						g_return_val_if_fail (data != NULL, FALSE);

						min = data->last_index;
						max = data->index - 1;

						/* array is sorted, so use a binary search */
						do {
							gint cmp = 0;

							mid = (min + max) / 2;
							r = &g_array_index (data->infos, struct _refresh_info, mid);
							cmp = camel_imapx_server_refresh_info_uid_cmp (is, finfo->uid, r->uid, fetch_order == CAMEL_SORT_ASCENDING);

							if (cmp > 0)
								min = mid + 1;
							else if (cmp < 0)
								max = mid - 1;
							else
								found = TRUE;

						} while (!found && min <= max);

						if (!found)
							g_assert_not_reached ();

						server_flags = r->server_flags;
						server_user_flags = r->server_user_flags;
					} else {
						server_flags = finfo->flags;
						server_user_flags = finfo->user_flags;
						/* free user_flags ? */
						finfo->user_flags = NULL;
						free_user_flags = TRUE;
					}

					/* If the message is a really new one -- equal or higher than what
					 * we know as UIDNEXT for the folder, then it came in since we last
					 * fetched UIDNEXT and UNREAD count. We'll update UIDNEXT in the
					 * command completion, but update UNREAD count now according to the
					 * message SEEN flag */
					if (!(server_flags & CAMEL_MESSAGE_SEEN)) {
						CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
						guint64 uidl = strtoull (mi->uid, NULL, 10);

						if (uidl >= ifolder->uidnext_on_server) {
							c(is->tagprefix, "Updating unread count for new message %s\n", mi->uid);
							((CamelIMAPXFolder *) job->folder)->unread_on_server++;
						} else {
							c(is->tagprefix, "Not updating unread count for new message %s\n", mi->uid);
						}
					}

					binfo = (CamelMessageInfoBase *) mi;
					binfo->size = finfo->size;

					if (!camel_folder_summary_check_uid (job->folder->summary, mi->uid)) {
						RefreshInfoData *data;
						CamelIMAPXFolder *ifolder = (CamelIMAPXFolder *) job->folder;
						gint cnt;

						data = camel_imapx_job_get_data (job);
						g_return_val_if_fail (data != NULL, FALSE);

						imapx_set_message_info_flags_for_new_message (mi, server_flags, server_user_flags, job->folder);
						camel_folder_summary_add (job->folder->summary, mi);
						camel_folder_change_info_add_uid (data->changes, mi->uid);

						if (!g_hash_table_lookup (ifolder->ignore_recent, mi->uid)) {
							camel_folder_change_info_recent_uid (data->changes, mi->uid);
							g_hash_table_remove (ifolder->ignore_recent, mi->uid);
						}

						cnt = (camel_folder_summary_count (job->folder->summary) * 100 ) / ifolder->exists_on_server;
						camel_operation_progress (job->cancellable, cnt ? cnt : 1);
					} else {
						camel_message_info_free (mi);
					}

					if (free_user_flags && server_user_flags)
						camel_flag_list_free (&server_user_flags);

				}
			}
		}

		imapx_free_fetch (finfo);
		break;
	}
	case IMAPX_LSUB:
		lsub = TRUE;
	case IMAPX_LIST: {
		struct _list_info *linfo = imapx_parse_list (is->stream, cancellable, error);
		CamelIMAPXJob *job;
		ListData *data;

		if (!linfo)
			break;

		job = camel_imapx_server_match_active_job (is, IMAPX_JOB_LIST, linfo->name);

		data = camel_imapx_job_get_data (job);
		g_return_val_if_fail (data != NULL, FALSE);

		// TODO: we want to make sure the names match?

		if (data->flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
			c(is->tagprefix, "lsub: '%s' (%c)\n", linfo->name, linfo->separator);

		} else {
			c(is->tagprefix, "list: '%s' (%c)\n", linfo->name, linfo->separator);
		}

		if (job && g_hash_table_lookup (data->folders, linfo->name) == NULL) {
			if (lsub)
				linfo->flags |= CAMEL_FOLDER_SUBSCRIBED;
			g_hash_table_insert (data->folders, linfo->name, linfo);
		} else {
			g_warning("got list response but no current listing job happening?\n");
			imapx_free_list (linfo);
		}
		break;
	}
	case IMAPX_RECENT:
		c(is->tagprefix, "recent: %d\n", id);
		is->recent = id;
		break;
	case IMAPX_STATUS: {
		struct _state_info *sinfo = imapx_parse_status_info (is->stream, cancellable, error);
		if (sinfo) {
			CamelIMAPXStoreSummary *s = ((CamelIMAPXStore *) is->store)->summary;
			CamelIMAPXStoreNamespace *ns;
			CamelIMAPXFolder *ifolder = NULL;;

			ns = camel_imapx_store_summary_namespace_find_full (s, sinfo->name);
			if (ns) {
				gchar *path_name;

				path_name = camel_imapx_store_summary_full_to_path (s, sinfo->name, ns->sep);
				c(is->tagprefix, "Got folder path '%s' for full '%s'\n", path_name, sinfo->name);
				if (path_name) {
					ifolder = (gpointer) camel_store_get_folder_sync (is->store, path_name, 0, cancellable, error);
					g_free (path_name);
				}
			}
			if (ifolder) {
				CamelFolder *cfolder = CAMEL_FOLDER (ifolder);

				ifolder->unread_on_server = sinfo->unseen;
				ifolder->exists_on_server = sinfo->messages;
				ifolder->modseq_on_server = sinfo->highestmodseq;
				ifolder->uidnext_on_server = sinfo->uidnext;
				ifolder->uidvalidity_on_server = sinfo->uidvalidity;
				if (sinfo->uidvalidity && sinfo->uidvalidity != ((CamelIMAPXSummary *) cfolder->summary)->validity)
					camel_imapx_server_invalidate_local_cache (is, ifolder, sinfo->uidvalidity);
			} else {
				c(is->tagprefix, "Received STATUS for unknown folder '%s'\n", sinfo->name);
			}

			g_free (sinfo->name);
			g_free (sinfo);
		}
		break;
	}
	case IMAPX_ANNOTATION:
		(void)extd_server_untagged_annotation (is,
		                                       &lsub,
		                                       id,
		                                       cancellable,
		                                       error);
		break;
	case IMAPX_BYE: {
		guchar *token;

		if (camel_imapx_stream_text (is->stream, &token, cancellable, NULL)) {
			c(is->tagprefix, "BYE: %s\n", token);
			g_set_error (
			             error, CAMEL_IMAPX_ERROR, 1,
			             "IMAP server said BYE: %s", token);
		}
		is->state = IMAPX_SHUTDOWN;
		return FALSE;
	}
	case IMAPX_PREAUTH:
		c(is->tagprefix, "preauthenticated\n");
		if (is->state < IMAPX_AUTHENTICATED)
			is->state = IMAPX_AUTHENTICATED;
		/* fall through... */
	case IMAPX_OK: case IMAPX_NO: case IMAPX_BAD:
		/* TODO: validate which ones of these can happen as unsolicited responses */
		/* TODO: handle bye/preauth differently */
		camel_imapx_stream_ungettoken (is->stream, tok, token, len);
		sinfo = imapx_parse_status (is->stream, cancellable, error);
		if (sinfo == NULL)
			return FALSE;
		switch (sinfo->condition) {
		case IMAPX_CLOSED:
			c(is->tagprefix, "previously selected folder is now closed\n");
			if (is->select_pending && !is->select_folder) {
				is->select_folder = is->select_pending;
			}
			break;
		case IMAPX_READ_WRITE:
			is->mode = IMAPX_MODE_READ | IMAPX_MODE_WRITE;
			c(is->tagprefix, "folder is read-write\n");
			break;
		case IMAPX_READ_ONLY:
			is->mode = IMAPX_MODE_READ;
			c(is->tagprefix, "folder is read-only\n");
			break;
		case IMAPX_UIDVALIDITY:
			is->uidvalidity = sinfo->u.uidvalidity;
			break;
		case IMAPX_UNSEEN:
			is->unseen = sinfo->u.unseen;
			break;
		case IMAPX_HIGHESTMODSEQ:
			is->highestmodseq = sinfo->u.highestmodseq;
			break;
		case IMAPX_PERMANENTFLAGS:
			is->permanentflags = sinfo->u.permanentflags;
			break;
		case IMAPX_UIDNEXT:
			is->uidnext = sinfo->u.uidnext;
			break;
		case IMAPX_ALERT:
			c(is->tagprefix, "ALERT!: %s\n", sinfo->text);
			break;
		case IMAPX_PARSE:
			c(is->tagprefix, "PARSE: %s\n", sinfo->text);
			break;
		case IMAPX_CAPABILITY:
			if (sinfo->u.cinfo) {
				struct _capability_info *cinfo = is->cinfo;
				is->cinfo = sinfo->u.cinfo;
				sinfo->u.cinfo = NULL;
				if (cinfo)
					imapx_free_capability (cinfo);
				c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
			}
			break;
		default:
			break;
		}
		imapx_free_status (sinfo);
		return TRUE;
	default:
		/* unknown response, just ignore it */
		c(is->tagprefix, "unknown token: %s\n", token);
	}

	return (camel_imapx_stream_skip (is->stream, cancellable, error) == 0);
}

static gboolean
extd_server_step (CamelIMAPXExtdServer *self,
                  GCancellable *cancellable,
                  GError **error)
{
	/* modified dupe of imapx_step() */

	CamelIMAPXServer *is = NULL;
	guint len = 0;
	guchar *token = NULL;
	gint tok = -1;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	is = CAMEL_IMAPX_SERVER (self);

	// poll ?  wait for other stuff? loop?
	tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
	if (tok < 0)
		return FALSE;

	if (tok == '*')
		return extd_server_untagged (self, cancellable, error);
	else if (tok == IMAPX_TOK_TOKEN)
		return camel_imapx_server_completion (is, token, len, cancellable, error);
	else if (tok == '+')
		return camel_imapx_server_continuation (is, FALSE, cancellable, error);

	g_set_error (
	             error, CAMEL_IMAPX_ERROR, 1,
	             "unexpected server response:");

	return FALSE;
}

/* Used to run 1 command synchronously,
 * use for capa, login, and namespaces only. */
static gboolean
extd_server_command_run (CamelIMAPXExtdServer *self,
                         CamelIMAPXCommand *ic,
                         GCancellable *cancellable,
                         GError **error)
{
	/* modified dupe of imapx_command_run() */

	CamelIMAPXServer *is = NULL;
	gboolean success = TRUE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (ic != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	is = CAMEL_IMAPX_SERVER (self);

	camel_imapx_command_close (ic);

	QUEUE_LOCK (is);
	camel_imapx_server_command_start (is, ic, cancellable, error);
	QUEUE_UNLOCK (is);

	while (success && ic->status == NULL)
		success = extd_server_step (self, cancellable, error);

	if (is->literal == ic)
		is->literal = NULL;

	QUEUE_LOCK (is);
	camel_imapx_command_queue_remove (is->active, ic);
	QUEUE_UNLOCK (is);

	return success;
}

static gboolean
extd_server_connect_to_server (CamelIMAPXExtdServer *self,
                               GCancellable *cancellable,
                               GError **error)
{
	/* modified dupe of imapx_connect_to_server() */

	CamelIMAPXServer *is = NULL;
	CamelNetworkSettings *network_settings = NULL;
	CamelNetworkSecurityMethod method;
	CamelStream * tcp_stream = NULL;
	CamelSockOptData sockopt;
	CamelSettings *settings = NULL;
	CamelService *service = NULL;
	guint len;
	guchar *token = NULL;
	gint tok;
	CamelIMAPXCommand *ic = NULL;
	gboolean success = TRUE;
	gchar *host = NULL;
	GError *local_error = NULL;

#ifndef G_OS_WIN32
	gboolean use_shell_command;
	gchar *shell_command = NULL;
#endif
	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	is = CAMEL_IMAPX_SERVER (self);
	service = CAMEL_SERVICE (is->store);
	settings = camel_service_get_settings (service);

	network_settings = CAMEL_NETWORK_SETTINGS (settings);
	host = camel_network_settings_dup_host (network_settings);
	method = camel_network_settings_get_security_method (network_settings);

#ifndef G_OS_WIN32
	use_shell_command = camel_imapx_settings_get_use_shell_command (
	                                                                CAMEL_IMAPX_SETTINGS (settings));

	if (use_shell_command)
		shell_command = camel_imapx_settings_dup_shell_command (
		                                                        CAMEL_IMAPX_SETTINGS (settings));

	if (shell_command != NULL) {
		gboolean success;

		success = camel_imapx_server_connect_to_server_process (
		                                                        is, shell_command, &local_error);

		g_free (shell_command);

		if (success)
			goto connected;
		else
			goto exit;
	}
#endif

	tcp_stream = camel_network_service_connect_sync (
	                                                 CAMEL_NETWORK_SERVICE (is->store), cancellable, error);

	if (tcp_stream == NULL) {
		success = FALSE;
		goto exit;
	}

	is->stream = (CamelIMAPXStream *) camel_imapx_stream_new (tcp_stream);
	g_object_unref (tcp_stream);

	/* Disable Nagle - we send a lot of small requests which nagle slows down */
	sockopt.option = CAMEL_SOCKOPT_NODELAY;
	sockopt.value.no_delay = TRUE;
	camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);

	/* Set keepalive - needed for some hosts/router configurations, we're idle a lot */
	sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
	sockopt.value.keep_alive = TRUE;
	camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);

 connected:
	is->stream->tagprefix = is->tagprefix;
	while (1) {
		// poll ?  wait for other stuff? loop?
		if (camel_application_is_exiting || is->parser_quit) {
			g_set_error (
			             error, G_IO_ERROR,
			             G_IO_ERROR_CANCELLED,
			             "Connection to server cancelled\n");
			success = FALSE;
			goto exit;
		}

		tok = camel_imapx_stream_token (is->stream, &token, &len, cancellable, error);
		if (tok < 0) {
			success = FALSE;
			goto exit;
		}

		if (tok == '*') {
			extd_server_untagged (self, cancellable, error);
			break;
		}
		camel_imapx_stream_ungettoken (is->stream, tok, token, len);
		if (camel_imapx_stream_text (is->stream, &token, cancellable, error)) {
			success = FALSE;
			goto exit;
		}
		e(is->tagprefix, "Got unexpected line before greeting:  '%s'\n", token);
		g_free (token);
	}

	if (!is->cinfo) {
		ic = camel_imapx_command_new (is, "CAPABILITY", NULL, "CAPABILITY");
		if (!extd_server_command_run (self, ic, cancellable, error)) {
			camel_imapx_command_unref (ic);
			success = FALSE;
			goto exit;
		}

		/* Server reported error. */
		if (ic->status->result != IMAPX_OK) {
			g_set_error (
			             error, CAMEL_ERROR,
			             CAMEL_ERROR_GENERIC,
			             "%s", ic->status->text);
			camel_imapx_command_unref (ic);
			success = FALSE;
			goto exit;
		}

		camel_imapx_command_unref (ic);
	}

	if (method == CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT) {

		if (!(is->cinfo->capa & IMAPX_CAPABILITY_STARTTLS)) {
			g_set_error (
			             &local_error, CAMEL_ERROR,
			             CAMEL_ERROR_GENERIC,
			             _("Failed to connect to IMAP server %s in secure mode: %s"),
			             host, _("STARTTLS not supported"));
			goto exit;
		}

		ic = camel_imapx_command_new (
		                              is, "STARTTLS", NULL, "STARTTLS");
		if (!extd_server_command_run (self, ic, cancellable, error)) {
			camel_imapx_command_unref (ic);
			success = FALSE;
			goto exit;
		}

		/* Server reported error. */
		if (ic->status->result != IMAPX_OK) {
			g_set_error (
			             error, CAMEL_ERROR,
			             CAMEL_ERROR_GENERIC,
			             "%s", ic->status->text);
			camel_imapx_command_unref (ic);
			success = FALSE;
			goto exit;
		}

		/* See if we got new capabilities in the STARTTLS response */
		imapx_free_capability (is->cinfo);
		is->cinfo = NULL;
		if (ic->status->condition == IMAPX_CAPABILITY) {
			is->cinfo = ic->status->u.cinfo;
			ic->status->u.cinfo = NULL;
			c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
		}

		camel_imapx_command_unref (ic);

		if (camel_tcp_stream_ssl_enable_ssl (
		                                     CAMEL_TCP_STREAM_SSL (tcp_stream),
		                                     cancellable, &local_error) == -1) {
			g_prefix_error (
			                &local_error,
			                _("Failed to connect to IMAP server %s in secure mode: "),
			                host);
			goto exit;
		}
		/* Get new capabilities if they weren't already given */
		if (!is->cinfo) {
			ic = camel_imapx_command_new (
			                              is, "CAPABILITY", NULL, "CAPABILITY");
			if (!extd_server_command_run (self, ic, cancellable, error)) {
				camel_imapx_command_unref (ic);
				success = FALSE;
				goto exit;
			}

			camel_imapx_command_unref (ic);
		}
	}

 exit:
	if (!success) {
		if (is->stream != NULL) {
			g_object_unref (is->stream);
			is->stream = NULL;
		}

		if (is->cinfo != NULL) {
			imapx_free_capability (is->cinfo);
			is->cinfo = NULL;
		}
	}

	g_free (host);

	return success;
}

static gboolean
extd_server_reconnect (CamelIMAPXExtdServer *self,
                       GCancellable *cancellable,
                       GError **error)
{
	/* modified dupe of imapx_reconnect() */

	CamelIMAPXExtdServerPrivate *priv = NULL;
	CamelIMAPXServer *is = NULL;
	CamelIMAPXCommand *ic = NULL;
	CamelService *service = NULL;
	CamelSession *session = NULL;
	CamelSettings *settings = NULL;
	gchar *mechanism = NULL;
	gboolean use_idle = FALSE;
	gboolean use_qresync = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	priv = CAMEL_IMAPX_EXTD_SERVER_PRIVATE (self);
	is = CAMEL_IMAPX_SERVER (self);
	service = CAMEL_SERVICE (is->store);
	session = camel_service_get_session (service);
	settings = camel_service_get_settings (service);

	mechanism = camel_network_settings_dup_auth_mechanism (
	                                                       CAMEL_NETWORK_SETTINGS (settings));

	use_idle = camel_imapx_settings_get_use_idle (
	                                              CAMEL_IMAPX_SETTINGS (settings));

	use_qresync = camel_imapx_settings_get_use_qresync (
	                                                    CAMEL_IMAPX_SETTINGS (settings));

	if (!extd_server_connect_to_server (self, cancellable, error))
		goto exception;

	if (is->state == IMAPX_AUTHENTICATED)
		goto preauthed;

	if (!camel_session_authenticate_sync (
	                                      session, service, mechanism, cancellable, error))
		goto exception;

	/* After login we re-capa unless the server already told us */
	if (!is->cinfo) {
		ic = camel_imapx_command_new (
		                              is, "CAPABILITY", NULL, "CAPABILITY");
		if (!extd_server_command_run (self, ic, cancellable, error)) {
			camel_imapx_command_unref (ic);
			goto exception;
		}

		camel_imapx_command_unref (ic);
	}

	is->state = IMAPX_AUTHENTICATED;

 preauthed:
	is->use_idle = use_idle;

	if (camel_imapx_server_idle_supported (is))
		camel_imapx_server_init_idle (is);

	/* Fetch namespaces */
	if (is->cinfo->capa & IMAPX_CAPABILITY_NAMESPACE) {
		ic = camel_imapx_command_new (
		                              is, "NAMESPACE", NULL, "NAMESPACE");
		if (!extd_server_command_run (self, ic, cancellable, error)) {
			camel_imapx_command_unref (ic);
			goto exception;
		}

		camel_imapx_command_unref (ic);
	}

	if (use_qresync && is->cinfo->capa & IMAPX_CAPABILITY_QRESYNC) {
		ic = camel_imapx_command_new (
		                              is, "ENABLE", NULL, "ENABLE CONDSTORE QRESYNC");
		if (!extd_server_command_run (self, ic, cancellable, error)) {
			camel_imapx_command_unref (ic);
			goto exception;
		}

		camel_imapx_command_unref (ic);

		is->use_qresync = TRUE;
	} else
		is->use_qresync = FALSE;

	if (((CamelIMAPXStore *) is->store)->summary->namespaces == NULL) {
		CamelIMAPXNamespaceList *nsl = NULL;
		CamelIMAPXStoreNamespace *ns = NULL;
		CamelIMAPXStore *imapx_store = (CamelIMAPXStore *) is->store;

		/* set a default namespace */
		nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
		ns = g_new0 (CamelIMAPXStoreNamespace, 1);
		ns->next = NULL;
		ns->path = g_strdup ("");
		ns->full_name = g_strdup ("");
		ns->sep = '/';
		nsl->personal = ns;
		imapx_store->summary->namespaces = nsl;
		/* FIXME needs to be identified from list response */
		imapx_store->dir_sep = ns->sep;
	}

	/* set annotation protocol, depending on server capability */

	if (is->cinfo->capa & IMAPX_ANNOTATEMORE) {
		/* IMAP ANNOTATEMORE (draft 05) */
		g_mutex_lock (priv->md->md_lock);
		priv->md->proto = CAMEL_IMAPX_METADATA_PROTO_ANNOTATEMORE;
		g_mutex_unlock (priv->md->md_lock);
	}
#if 0
	if (is->cinfo->capa & IMAPX_METADATA) {
		/* IMAP METADATA not yet supported */
		g_mutex_lock (priv->md->md_lock);
		priv->md->proto = CAMEL_IMAPX_METADATA_PROTO_METADATA;
		g_mutex_unlock (priv->md->md_lock);
	}
#endif
	is->state = IMAPX_INITIALISED;

	g_free (mechanism);

	return TRUE;

 exception:

	camel_imapx_server_disconnect (is);

	if (is->cinfo) {
		imapx_free_capability (is->cinfo);
		is->cinfo = NULL;
	}

	g_free (mechanism);

	return FALSE;
}

static void
extd_server_parse_contents (CamelIMAPXExtdServer *self,
                            GCancellable *cancellable,
                            GError **error)
{
	/* modified dupe of parse_contents() */

	CamelIMAPXServer *is = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	/* cancellable may be NULL */
	g_return_if_fail (error == NULL || *error == NULL);

	is = CAMEL_IMAPX_SERVER (self);

	while (extd_server_step (self, cancellable, error))
		if (camel_imapx_stream_buffered (is->stream) == 0)
			break;
}

/*
 * The main processing (reading) loop.
 *
 * Main area of locking required is command_queue
 * and command_start_next, the 'literal' command,
 * the jobs queue, the active queue, the queue
 * queue. */
static gpointer
extd_server_parser_thread (gpointer d)
{
	/* modified dupe of imapx_parser_thread() */

	CamelIMAPXExtdServer *self = CAMEL_IMAPX_EXTD_SERVER (d);
	CamelIMAPXServer *is = CAMEL_IMAPX_SERVER (self);
	GCancellable *cancellable;
	GError *local_error = NULL;

	QUEUE_LOCK (is);
	cancellable = camel_operation_new ();
	is->cancellable = g_object_ref (cancellable);
	QUEUE_UNLOCK (is);

	while (local_error == NULL && is->stream) {
		g_cancellable_reset (cancellable);

#ifndef G_OS_WIN32
		if (is->is_process_stream)	{
			GPollFD fds[2] = { {0, 0, 0}, {0, 0, 0} };
			gint res;

			fds[0].fd = ((CamelStreamProcess *) is->stream->source)->sockfd;
			fds[0].events = G_IO_IN;
			fds[1].fd = g_cancellable_get_fd (cancellable);
			fds[1].events = G_IO_IN;
			res = g_poll (fds, 2, -1);
			if (res == -1)
				g_usleep (1) /* ?? */ ;
			else if (res == 0)
				/* timed out */;
			else if (fds[0].revents & G_IO_IN)
				extd_server_parse_contents (self, cancellable, &local_error);
			g_cancellable_release_fd (cancellable);
		} else
#endif
			{
				extd_server_parse_contents (self, cancellable, &local_error);
			}

		if (is->parser_quit)
			g_cancellable_cancel (cancellable);
		else if (g_cancellable_is_cancelled (cancellable)) {
			gint is_empty;

			QUEUE_LOCK (is);
			is_empty = camel_imapx_command_queue_is_empty (is->active);
			QUEUE_UNLOCK (is);

			if (is_empty || (camel_imapx_server_idle_supported (is) && camel_imapx_server_in_idle (is))) {
				g_cancellable_reset (cancellable);
				g_clear_error (&local_error);
			} else {
				/* Cancelled error should be set. */
				g_warn_if_fail (local_error != NULL);
			}
		}

		/* Jump out of the loop if an error occurred. */
		if (local_error != NULL)
			break;
	}

	QUEUE_LOCK (is);
	is->state = IMAPX_SHUTDOWN;
	QUEUE_UNLOCK (is);

	camel_imapx_server_cancel_all_jobs (is, local_error);

	g_clear_error (&local_error);

	QUEUE_LOCK (is);
	if (is->cancellable != NULL) {
		g_object_unref (is->cancellable);
		is->cancellable = NULL;
	}
	g_object_unref (cancellable);
	QUEUE_UNLOCK (is);

	is->parser_quit = FALSE;
	g_signal_emit (is, signals[SHUTDOWN], 0);

	return NULL;
}

/*----------------------------------------------------------------------------*/
/* class functions */

static gboolean
imapx_extd_server_connect (CamelIMAPXServer *self,
                           GCancellable *cancellable,
                           GError **err)
{
	/* modified dupe of camel_imapx_server_connect() */

	gboolean success;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	if (self->state == IMAPX_SHUTDOWN) {
		g_set_error (err, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE, _("Shutting down"));
		return FALSE;
	}

	if (self->state >= IMAPX_INITIALISED)
		return TRUE;

	g_static_rec_mutex_lock (&self->ostream_lock);
	success = extd_server_reconnect (CAMEL_IMAPX_EXTD_SERVER (self), cancellable, err);
	g_static_rec_mutex_unlock (&self->ostream_lock);

	if (!success)
		return FALSE;

	self->parser_thread = g_thread_create ((GThreadFunc) extd_server_parser_thread, self, TRUE, NULL);
	return TRUE;
}

static gboolean
imapx_extd_server_connect_to_server (CamelIMAPXServer *self,
                                     GCancellable *cancellable,
                                     GError **err)
{
	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	return extd_server_connect_to_server (CAMEL_IMAPX_EXTD_SERVER (self),
	                                      cancellable,
	                                      err);
}

static CamelAuthenticationResult
imapx_extd_server_authenticate (CamelIMAPXServer *self,
                                const gchar *mechanism,
                                GCancellable *cancellable,
                                GError **error)
{
	/* modified dupe of camel_imapx_server_authenticate() */

	CamelIMAPXServer *is = NULL;
	CamelNetworkSettings *network_settings = NULL;
	CamelSettings *settings = NULL;
	CamelAuthenticationResult result = CAMEL_AUTHENTICATION_REJECTED;
	CamelIMAPXCommand *ic = NULL;
	CamelService *service = NULL;
	CamelSasl *sasl = NULL;
	gchar *host = NULL;
	gchar *user = NULL;

	g_return_val_if_fail (
	                      CAMEL_IS_IMAPX_EXTD_SERVER (self),
	                      CAMEL_AUTHENTICATION_REJECTED);
	/* mechanism may be NULL */
	/* cancellable may be NULL */
	g_return_val_if_fail (error == NULL || *error == NULL, CAMEL_AUTHENTICATION_REJECTED);

	is = self;
	service = CAMEL_SERVICE (is->store);
	settings = camel_service_get_settings (service);

	network_settings = CAMEL_NETWORK_SETTINGS (settings);
	host = camel_network_settings_dup_host (network_settings);
	user = camel_network_settings_dup_user (network_settings);

	if (mechanism != NULL) {
		if (!g_hash_table_lookup (is->cinfo->auth_types, mechanism)) {
			g_set_error (
			             error, CAMEL_SERVICE_ERROR,
			             CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			             _("IMAP server %s does not support %s "
			               "authentication"), host, mechanism);
			result = CAMEL_AUTHENTICATION_ERROR;
			goto exit;
		}

		sasl = camel_sasl_new ("imap", mechanism, service);
		if (sasl == NULL) {
			g_set_error (
			             error, CAMEL_SERVICE_ERROR,
			             CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			             _("No support for %s authentication"),
			             mechanism);
			result = CAMEL_AUTHENTICATION_ERROR;
			goto exit;
		}
	}

	if (sasl != NULL) {
		ic = camel_imapx_command_new (
		                              is, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl);
	} else {
		const gchar *password;

		password = camel_service_get_password (service);

		if (user == NULL) {
			g_set_error_literal (
			                     error, CAMEL_SERVICE_ERROR,
			                     CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			                     _("Cannot authenticate without a username"));
			result = CAMEL_AUTHENTICATION_ERROR;
			goto exit;
		}

		if (password == NULL) {
			g_set_error_literal (
			                     error, CAMEL_SERVICE_ERROR,
			                     CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
			                     _("Authentication password not available"));
			result = CAMEL_AUTHENTICATION_ERROR;
			goto exit;
		}

		ic = camel_imapx_command_new (
		                              is, "LOGIN", NULL, "LOGIN %s %s", user, password);
	}

	if (!extd_server_command_run (CAMEL_IMAPX_EXTD_SERVER (self), ic, cancellable, error))
		result = CAMEL_AUTHENTICATION_ERROR;
	else if (ic->status->result == IMAPX_OK)
		result = CAMEL_AUTHENTICATION_ACCEPTED;
	else
		result = CAMEL_AUTHENTICATION_REJECTED;

	/* Forget old capabilities after login. */
	if (result == CAMEL_AUTHENTICATION_ACCEPTED) {
		if (is->cinfo) {
			imapx_free_capability (is->cinfo);
			is->cinfo = NULL;
		}

		if (ic->status->condition == IMAPX_CAPABILITY) {
			is->cinfo = ic->status->u.cinfo;
			ic->status->u.cinfo = NULL;
			c(is->tagprefix, "got capability flags %08x\n", is->cinfo->capa);
		}
	}

	camel_imapx_command_unref (ic);

	if (sasl != NULL)
		g_object_unref (sasl);

 exit:
	g_free (host);
	g_free (user);

	return result;
}

static GPtrArray*
imapx_extd_server_list (CamelIMAPXServer *self,
                        const gchar *top,
                        guint32 flags,
                        const gchar *ext,
                        GCancellable *cancellable,
                        GError **err)
{
	GPtrArray *list = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (top != NULL); /* FIXME correct? */
	g_assert (ext != NULL); /* FIXME correct? */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	list = camel_imapx_server_list (self,
	                                top,
	                                flags,
	                                ext,
	                                cancellable,
	                                err);
	return list;
}

static gboolean
imapx_extd_server_refresh_info (CamelIMAPXServer *self,
                                CamelFolder *folder,
                                GCancellable *cancellable,
                                GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_FOLDER (folder));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_refresh_info (self,
	                                      folder,
	                                      cancellable,
	                                      err);
	return ok;
}

static gboolean
imapx_extd_server_sync_changes (CamelIMAPXServer *self,
                                CamelFolder *folder,
                                GCancellable *cancellable,
                                GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_IMAPX_EXTD_FOLDER (folder));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_sync_changes (self,
	                                      folder,
	                                      cancellable,
	                                      err);
	return ok;
}

static gboolean
imapx_extd_server_expunge (CamelIMAPXServer *self,
                           CamelFolder *folder,
                           GCancellable *cancellable,
                           GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_IMAPX_EXTD_FOLDER (folder));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_expunge (self,
	                                 folder,
	                                 cancellable,
	                                 err);
	return ok;
}

static gboolean
imapx_extd_server_noop (CamelIMAPXServer *self,
                        CamelFolder *folder,
                        GCancellable *cancellable,
                        GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_IMAPX_EXTD_FOLDER (folder));
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_noop (self,
	                              folder,
	                              cancellable,
	                              err);
	return ok;
}

static CamelStream*
imapx_extd_server_get_message (CamelIMAPXServer *self,
                               CamelFolder *folder,
                               const gchar *uid,
                               GCancellable *cancellable,
                               GError **err)
{
	CamelStream *stream = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_IMAPX_EXTD_FOLDER (folder));
	g_assert (uid != NULL); /* FIXME correct? */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	stream = camel_imapx_server_get_message (self,
	                                         folder,
	                                         uid,
	                                         cancellable,
	                                         err);
	return stream;

}

static gboolean
imapx_extd_server_copy_message (CamelIMAPXServer *self,
                                CamelFolder *source,
                                CamelFolder *dest,
                                GPtrArray *uids,
                                gboolean delete_originals,
                                GCancellable *cancellable,
                                GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_FOLDER (source));
	g_assert (CAMEL_IS_FOLDER (dest));
	g_assert (uids != NULL); /* FIXME correct? */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_copy_message (self,
	                                      source,
	                                      dest,
	                                      uids,
	                                      delete_originals,
	                                      cancellable,
	                                      err);
	return ok;
}

static gboolean
imapx_extd_server_append_message (CamelIMAPXServer *self,
                                  CamelFolder *folder,
                                  CamelMimeMessage *message,
                                  const CamelMessageInfo *mi,
                                  GCancellable *cancellable,
                                  GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_IMAPX_EXTD_FOLDER (folder));
	g_assert (CAMEL_IS_MIME_MESSAGE (message));
	g_assert (mi != NULL); /* FIXME correct? */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_append_message (self,
	                                        folder,
	                                        message,
	                                        mi,
	                                        cancellable,
	                                        err);
	return ok;
}

static gboolean
imapx_extd_server_sync_message (CamelIMAPXServer *self,
                                CamelFolder *folder,
                                const gchar *uid,
                                GCancellable *cancellable,
                                GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (CAMEL_IS_IMAPX_EXTD_FOLDER (folder));
	g_assert (uid != NULL); /* FIXME correct? */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_sync_message (self,
	                                      folder,
	                                      uid,
	                                      cancellable,
	                                      err);
	return ok;
}

static gboolean
imapx_extd_server_manage_subscription (CamelIMAPXServer *self,
                                       const gchar *foldername,
                                       gboolean subscribe,
                                       GCancellable *cancellable,
                                       GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (foldername != NULL); /* FIXME correct? */
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_manage_subscription (self,
	                                             foldername,
	                                             subscribe,
	                                             cancellable,
	                                             err);
	return ok;
}

static gboolean
imapx_extd_server_create_folder (CamelIMAPXServer *self,
                                 const gchar *foldername,
                                 GCancellable *cancellable,
                                 GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_create_folder (self,
	                                       foldername,
	                                       cancellable,
	                                       err);
	return ok;
}

static gboolean
imapx_extd_server_delete_folder (CamelIMAPXServer *self,
                                 const gchar *foldername,
                                 GCancellable *cancellable,
                                 GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (foldername != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_delete_folder (self,
	                                       foldername,
	                                       cancellable,
	                                       err);
	return ok;
}

static gboolean
imapx_extd_server_rename_folder (CamelIMAPXServer *self,
                                 const gchar *oldname,
                                 const gchar *newname,
                                 GCancellable *cancellable,
                                 GError **err)
{
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (oldname != NULL);
	g_assert (newname != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	ok = camel_imapx_server_rename_folder (self,
	                                       oldname,
	                                       newname,
	                                       cancellable,
	                                       err);
	return ok;
}

static struct _IMAPXJobQueueInfo*
imapx_extd_server_get_job_queue_info (CamelIMAPXServer *self)
{
	struct _IMAPXJobQueueInfo *qi = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));

	qi = camel_imapx_server_get_job_queue_info (self);

	return qi;
}

static camel_imapx_metadata_proto_t
imapx_extd_server_metadata_get_proto (CamelIMAPXExtdServer *self)
{
	CamelIMAPXExtdServerPrivate *priv = NULL;
	camel_imapx_metadata_proto_t proto = CAMEL_IMAPX_METADATA_PROTO_INVAL;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));

	priv = CAMEL_IMAPX_EXTD_SERVER_PRIVATE (self);

	if (priv->md == NULL) {
		/* TODO implement online folder annotation query
		 *      in case there is no metadata as yet
		 *      (decide the protocol here, depending on
		 *      the IMAP server's untagged response)
		 */
		g_warning ("%s: FIXME implement online annotation protocol query",
		           __func__);
		return CAMEL_IMAPX_METADATA_PROTO_INVAL; /* FIXME */
	}

	proto = camel_imapx_metadata_get_proto (priv->md);

	return proto;
}

static CamelImapxMetadata*
imapx_extd_server_get_metadata (CamelIMAPXExtdServer *self,
                                CamelImapxMetadataSpec *spec,
                                gboolean do_resect,
                                GCancellable *cancellable,
                                GError **err)
{
	CamelIMAPXExtdServerPrivate *priv = NULL;
	CamelIMAPXServer *is = NULL;
	CamelIMAPXCommand *ic = NULL;
	CamelIMAPXJob *job = NULL;
	CamelImapxMetadata *md = NULL;
	gchar *cmd = NULL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (spec != NULL);
	/* cancellable may be NULL */
	g_return_val_if_fail (err == NULL || *err == NULL, NULL);

	priv = CAMEL_IMAPX_EXTD_SERVER_PRIVATE (self);
	is = CAMEL_IMAPX_SERVER (self);

	/* TODO move GETANNOTATION string to -metadata.[hc]
	 *      (add a function to return proper string depending
	 *      on protocol type)
	 */
	cmd = g_strdup_printf ("GETANNOTATION \"%s\" \"%s\" \"%s\"",
	                       spec->mailbox_name,
	                       spec->entry_name,
	                       spec->attrib_name);
	ic = camel_imapx_command_new (is,
	                              "GETANNOTATION",
	                              NULL,
	                              cmd);
	job = camel_imapx_job_new (cancellable);
	camel_imapx_command_set_job (ic, job);
	/* TODO set more job details like priority? */

	/* run GETANNOTATION command synchronously
	 * (TODO can we change this to async operation?)
	 */
	ok = camel_imapx_server_command_run_sync (is, ic, cancellable, &tmp_err);

	g_free (cmd);
	camel_imapx_command_done (ic);
	camel_imapx_command_unref (ic);

	if (! ok) {
		g_propagate_error (err, tmp_err);
		return NULL;
	}

	/* only if do_resect==TRUE we do return the metadata,
	 * otherwise we only get it from the server and leave
	 * the result inside this object for later resection
	 */
	if (do_resect)
		md = camel_imapx_metadata_resect (&(priv->md));

	return md;
}

static gboolean
imapx_extd_server_set_metadata (CamelIMAPXExtdServer *self,
                                CamelImapxMetadata *md,
                                GCancellable *cancellable,
                                GError **err)
{
	CamelIMAPXServer *is = NULL;
	CamelIMAPXCommand *ic = NULL;
	CamelIMAPXJob *job = NULL;
	GSList *commands = NULL;
	GSList *cur_cmd = NULL;
	GError *tmp_err = NULL;
	gboolean ok = FALSE;

	g_assert (CAMEL_IS_IMAPX_EXTD_SERVER (self));
	g_assert (md != NULL);
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);

	is = CAMEL_IMAPX_SERVER (self);

	commands = camel_imapx_metadata_new_commandlist (md);

	if (commands == NULL)
		return TRUE;

	cur_cmd = commands;
	while (cur_cmd != NULL) {
		/* TODO move SETANNOTATION string to -metadata.[hc]
		 *      (add a function to return proper string depending
		 *      on protocol type)
		 */
		ic = camel_imapx_command_new (is,
		                              "SETANNOTATION",
		                              NULL,
		                              (gchar *) cur_cmd->data);
		job = camel_imapx_job_new (cancellable);
		camel_imapx_command_set_job (ic, job);
		/* TODO set more job details like priority? */

		/* run SETANNOTATION command synchronously
		 * (TODO can we change this to async operation?)
		 */
		ok = camel_imapx_server_command_run_sync (is, ic, cancellable, &tmp_err);

		camel_imapx_command_done (ic);
		camel_imapx_command_unref (ic);

		if (! ok) {
			g_slist_free (commands);
			g_propagate_error (err, tmp_err);
			return FALSE;
		}

		cur_cmd = g_slist_next (cur_cmd);
	}

	g_slist_free (commands);

	return TRUE;
}

/*----------------------------------------------------------------------------*/
/* class init */

static void
camel_imapx_extd_server_class_init (CamelIMAPXExtdServerClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (klass, sizeof (CamelIMAPXExtdServerPrivate));

	object_class->constructed = camel_imapx_extd_server_constructed;
	object_class->dispose = camel_imapx_extd_server_dispose;
	object_class->finalize = camel_imapx_extd_server_finalize;

	/* parent class functions (not yet virtualized in parent) */
	klass->connect = imapx_extd_server_connect;
	klass->connect_to_server = imapx_extd_server_connect_to_server;
	klass->authenticate = imapx_extd_server_authenticate;
	klass->list = imapx_extd_server_list;
	klass->refresh_info = imapx_extd_server_refresh_info;
	klass->sync_changes = imapx_extd_server_sync_changes;
	klass->expunge = imapx_extd_server_expunge;
	klass->noop = imapx_extd_server_noop;
	klass->get_message = imapx_extd_server_get_message;
	klass->copy_message = imapx_extd_server_copy_message;
	klass->append_message = imapx_extd_server_append_message;
	klass->sync_message = imapx_extd_server_sync_message;
	klass->manage_subscription = imapx_extd_server_manage_subscription;
	klass->create_folder = imapx_extd_server_create_folder;
	klass->delete_folder = imapx_extd_server_delete_folder;
	klass->rename_folder = imapx_extd_server_rename_folder;
	klass->get_job_queue_info = imapx_extd_server_get_job_queue_info;

	/* true CamelIMAPXExtdServer class functions */
	klass->metadata_get_proto = imapx_extd_server_metadata_get_proto;
	klass->get_metadata = imapx_extd_server_get_metadata;
	klass->set_metadata = imapx_extd_server_set_metadata;
}

/*----------------------------------------------------------------------------*/
/* API functions */

CamelIMAPXExtdServer*
camel_imapx_extd_server_new (CamelIMAPXExtdStore *store)
{
	CamelIMAPXExtdServer *self = NULL;
	CamelIMAPXServer *is = NULL;
	CamelService *service = NULL;
	CamelSession *session = NULL;

	g_assert (CAMEL_IS_IMAPX_EXTD_STORE (store));

	service = CAMEL_SERVICE (store);
	session = camel_service_get_session (service);

	self = g_object_new (CAMEL_TYPE_IMAPX_EXTD_SERVER, NULL);
	is = CAMEL_IMAPX_SERVER (self);

	is->session = g_object_ref (session);
	is->store = CAMEL_STORE (store);

	return self;
}

gboolean
camel_imapx_extd_server_connect (CamelIMAPXServer *self,
                                 GCancellable *cancellable,
                                 GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->connect (self,
	                     cancellable,
	                     err);
	return ok;
}

gboolean
camel_imapx_extd_server_connect_to_server (CamelIMAPXServer *self,
                                           GCancellable *cancellable,
                                           GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->connect_to_server (self,
	                               cancellable,
	                               err);
	return ok;
}

CamelAuthenticationResult
camel_imapx_extd_server_authenticate (CamelIMAPXServer *self,
                                      const gchar *mechanism,
                                      GCancellable *cancellable,
                                      GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	CamelAuthenticationResult result = CAMEL_AUTHENTICATION_ERROR;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), CAMEL_AUTHENTICATION_REJECTED);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	result = klass->authenticate (self,
	                              mechanism,
	                              cancellable,
	                              err);
	return result;
}

GPtrArray*
camel_imapx_extd_server_list (CamelIMAPXServer *self,
                              const gchar *top,
                              guint32 flags,
                              const gchar *ext,
                              GCancellable *cancellable,
                              GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	GPtrArray *list = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), NULL);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	list = klass->list (self,
	                    top,
	                    flags,
	                    ext,
	                    cancellable,
	                    err);
	return list;
}

gboolean
camel_imapx_extd_server_refresh_info (CamelIMAPXServer *self,
                                      CamelFolder *folder,
                                      GCancellable *cancellable,
                                      GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->refresh_info (self,
	                          folder,
	                          cancellable,
	                          err);
	return ok;
}

gboolean
camel_imapx_extd_server_sync_changes (CamelIMAPXServer *self,
                                      CamelFolder *folder,
                                      GCancellable *cancellable,
                                      GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->sync_changes (self,
	                          folder,
	                          cancellable,
	                          err);
	return ok;
}

gboolean
camel_imapx_extd_server_expunge (CamelIMAPXServer *self,
                                 CamelFolder *folder,
                                 GCancellable *cancellable,
                                 GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->expunge (self,
	                     folder,
	                     cancellable,
	                     err);
	return ok;
}

gboolean
camel_imapx_extd_server_noop (CamelIMAPXServer *self,
                              CamelFolder *folder,
                              GCancellable *cancellable,
                              GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->noop (self,
	                  folder,
	                  cancellable,
	                  err);
	return ok;
}

CamelStream*
camel_imapx_extd_server_get_message (CamelIMAPXServer *self,
                                     CamelFolder *folder,
                                     const gchar *uid,
                                     GCancellable *cancellable,
                                     GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	CamelStream *stream = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), NULL);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	stream = klass->get_message (self,
	                             folder,
	                             uid,
	                             cancellable,
	                             err);
	return stream;
}

gboolean
camel_imapx_extd_server_copy_message (CamelIMAPXServer *self,
                                      CamelFolder *source,
                                      CamelFolder *dest,
                                      GPtrArray *uids,
                                      gboolean delete_originals,
                                      GCancellable *cancellable,
                                      GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->copy_message (self,
	                          source,
	                          dest,
	                          uids,
	                          delete_originals,
	                          cancellable,
	                          err);
	return ok;
}

gboolean
camel_imapx_extd_server_append_message (CamelIMAPXServer *self,
                                        CamelFolder *folder,
                                        CamelMimeMessage *message,
                                        const CamelMessageInfo *mi,
                                        GCancellable *cancellable,
                                        GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->append_message (self,
	                            folder,
	                            message,
	                            mi,
	                            cancellable,
	                            err);
	return ok;
}


gboolean
camel_imapx_extd_server_sync_message (CamelIMAPXServer *self,
                                      CamelFolder *folder,
                                      const gchar *uid,
                                      GCancellable *cancellable,
                                      GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->sync_message (self,
	                          folder,
	                          uid,
	                          cancellable,
	                          err);
	return ok;
}

gboolean
camel_imapx_extd_server_manage_subscription (CamelIMAPXServer *self,
                                             const gchar *foldername,
                                             gboolean subscribe,
                                             GCancellable *cancellable,
                                             GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->manage_subscription (self,
	                                 foldername,
	                                 subscribe,
	                                 cancellable,
	                                 err);
	return ok;
}

gboolean
camel_imapx_extd_server_create_folder (CamelIMAPXServer *self,
                                       const gchar *foldername,
                                       GCancellable *cancellable,
                                       GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->create_folder (self,
	                           foldername,
	                           cancellable,
	                           err);
	return ok;
}

gboolean
camel_imapx_extd_server_delete_folder (CamelIMAPXServer *self,
                                       const gchar *foldername,
                                       GCancellable *cancellable,
                                       GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->delete_folder (self,
	                           foldername,
	                           cancellable,
	                           err);
	return ok;
}

gboolean
camel_imapx_extd_server_rename_folder (CamelIMAPXServer *self,
                                       const gchar *oldname,
                                       const gchar *newname,
                                       GCancellable *cancellable,
                                       GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->rename_folder (self,
	                           oldname,
	                           newname,
	                           cancellable,
	                           err);
	return ok;
}

struct _IMAPXJobQueueInfo*
camel_imapx_extd_server_get_job_queue_info (CamelIMAPXServer *self)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	struct _IMAPXJobQueueInfo *qi = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), NULL);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	qi = klass->get_job_queue_info (self);

	return qi;
}

camel_imapx_metadata_proto_t
camel_imapx_extd_server_metadata_get_proto (CamelIMAPXExtdServer *self)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	camel_imapx_metadata_proto_t proto = CAMEL_IMAPX_METADATA_PROTO_INVAL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), CAMEL_IMAPX_METADATA_PROTO_INVAL);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	proto = klass->metadata_get_proto (self);

	return proto;
}

CamelImapxMetadata*
camel_imapx_extd_server_get_metadata (CamelIMAPXExtdServer *self,
                                      CamelImapxMetadataSpec *spec,
                                      gboolean do_resect,
                                      GCancellable *cancellable,
                                      GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	CamelImapxMetadata *md = NULL;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), NULL);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	md = klass->get_metadata (self,
	                          spec,
	                          do_resect,
	                          cancellable,
	                          err);
	return md;
}

gboolean
camel_imapx_extd_server_set_metadata (CamelIMAPXExtdServer *self,
                                      CamelImapxMetadata *md,
                                      GCancellable *cancellable,
                                      GError **err)
{
	CamelIMAPXExtdServerClass *klass = NULL;
	gboolean ok = FALSE;

	g_return_val_if_fail (CAMEL_IS_IMAPX_EXTD_SERVER (self), FALSE);

	klass = CAMEL_IMAPX_EXTD_SERVER_GET_CLASS (self);
	ok = klass->set_metadata (self, md, cancellable, err);

	return ok;
}

/*----------------------------------------------------------------------------*/
