/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* mail-importer.c
*
* Authors: Iain Holmes <iain@ximian.com>
* Michael Zucchi <notzed@ximian.com>
*
* Copyright (C) 2001-2003 Ximian, Inc.
* Copyright (C) 2004 Novell Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gmodule.h>
#include <libgnome/gnome-util.h>
#include <glib/gi18n.h>
#include <camel/camel-folder.h>
#include <camel/camel-store.h>
#include <camel/camel-mime-message.h>
#include <camel/camel-mime-parser.h>
#include <camel/camel-exception.h>
#include <camel/camel-stream-mem.h>
#include "e-util/e-util-private.h"
#include "mail/mail-mt.h"
#include "mail/mail-component.h"
#include "mail/mail-tools.h"
#include "mail-importer.h"
/**
* mail_importer_make_local_folder:
* @folderpath:
*
* Check a local folder exists at path @folderpath, and if not, create it.
*
* Return value: The physical uri of the folder, or NULL if the folder did
* not exist and could not be created.
**/
char *
mail_importer_make_local_folder(const char *folderpath)
{
return g_strdup_printf("mbox:/home/notzed/.evolution/mail/local/%s", folderpath);
}
/**
* mail_importer_add_line:
* importer: A MailImporter structure.
* str: Next line of the mbox.
* finished: TRUE if @str is the last line of the message.
*
* Adds lines to the message until it is finished, and then adds
* the complete message to the folder.
*/
void
mail_importer_add_line (MailImporter *importer,
const char *str,
gboolean finished)
{
CamelMimeMessage *msg;
CamelMessageInfo *info;
CamelException *ex;
if (importer->mstream == NULL)
importer->mstream = CAMEL_STREAM_MEM (camel_stream_mem_new ());
camel_stream_write (CAMEL_STREAM (importer->mstream), str, strlen (str));
if (finished == FALSE)
return;
camel_stream_reset (CAMEL_STREAM (importer->mstream));
info = camel_message_info_new(NULL);
camel_message_info_set_flags(info, CAMEL_MESSAGE_SEEN, ~0);
msg = camel_mime_message_new ();
camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg),
CAMEL_STREAM (importer->mstream));
camel_object_unref (importer->mstream);
importer->mstream = NULL;
ex = camel_exception_new ();
camel_folder_append_message (importer->folder, msg, info, NULL, ex);
camel_object_unref (msg);
camel_exception_free (ex);
camel_message_info_free(info);
}
struct _BonoboObject *mail_importer_factory_cb(struct _BonoboGenericFactory *factory, const char *iid, void *data)
{
#if 0
if (strcmp(iid, ELM_INTELLIGENT_IMPORTER_IID) == 0)
return elm_intelligent_importer_new();
else if (strcmp(iid, PINE_INTELLIGENT_IMPORTER_IID) == 0)
return pine_intelligent_importer_new();
else if (strcmp(iid, NETSCAPE_INTELLIGENT_IMPORTER_IID) == 0)
return netscape_intelligent_importer_new();
else if (strcmp(iid, MBOX_IMPORTER_IID) == 0)
return mbox_importer_new();
else if (strcmp(iid, OUTLOOK_IMPORTER_IID) == 0)
return outlook_importer_new();
#endif
return NULL;
}
struct _import_mbox_msg {
struct _mail_msg msg;
char *path;
char *uri;
CamelOperation *cancel;
void (*done)(void *data, CamelException *ex);
void *done_data;
};
static char *
import_mbox_describe(struct _mail_msg *mm, int complete)
{
return g_strdup (_("Importing mailbox"));
}
static struct {
char tag;
guint32 mozflag;
guint32 flag;
} status_flags[] = {
{ 'F', MSG_FLAG_MARKED, CAMEL_MESSAGE_FLAGGED },
{ 'A', MSG_FLAG_REPLIED, CAMEL_MESSAGE_ANSWERED },
{ 'D', MSG_FLAG_EXPUNGED, CAMEL_MESSAGE_DELETED },
{ 'R', MSG_FLAG_READ, CAMEL_MESSAGE_SEEN },
};
static guint32
decode_status(const char *status)
{
const char *p;
guint32 flags = 0;
int i;
p = status;
while ((*p++)) {
for (i=0;i<sizeof(status_flags)/sizeof(status_flags[0]);i++)
if (status_flags[i].tag == *p)
flags |= status_flags[i].flag;
}
return flags;
}
static guint32
decode_mozilla_status(const char *tmp)
{
unsigned long status = strtoul(tmp, NULL, 16);
guint32 flags = 0;
int i;
for (i=0;i<sizeof(status_flags)/sizeof(status_flags[0]);i++)
if (status_flags[i].mozflag & status)
flags |= status_flags[i].flag;
return flags;
}
static void
import_mbox_import(struct _mail_msg *mm)
{
struct _import_mbox_msg *m = (struct _import_mbox_msg *) mm;
CamelFolder *folder;
CamelMimeParser *mp = NULL;
struct stat st;
int fd;
CamelMessageInfo *info;
if (g_stat(m->path, &st) == -1) {
g_warning("cannot find source file to import '%s': %s", m->path, g_strerror(errno));
return;
}
if (m->uri == NULL || m->uri[0] == 0)
folder = mail_component_get_folder(NULL, MAIL_COMPONENT_FOLDER_INBOX);
else
folder = mail_tool_uri_to_folder(m->uri, CAMEL_STORE_FOLDER_CREATE, &mm->ex);
if (folder == NULL)
return;
if (S_ISREG(st.st_mode)) {
CamelOperation *oldcancel = NULL;
fd = g_open(m->path, O_RDONLY|O_BINARY, 0);
if (fd == -1) {
g_warning("cannot find source file to import '%s': %s", m->path, g_strerror(errno));
goto fail1;
}
mp = camel_mime_parser_new();
camel_mime_parser_scan_from(mp, TRUE);
if (camel_mime_parser_init_with_fd(mp, fd) == -1) { /* will never happen - 0 is unconditionally returned */
goto fail2;
}
if (m->cancel)
oldcancel = camel_operation_register(m->cancel);
camel_operation_start(NULL, _("Importing `%s'"), folder->full_name);
camel_folder_freeze(folder);
while (camel_mime_parser_step(mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) {
CamelMimeMessage *msg;
const char *tmp;
int pc = 0;
guint32 flags = 0;
if (st.st_size > 0)
pc = (int)(100.0 * ((double)camel_mime_parser_tell(mp) / (double)st.st_size));
camel_operation_progress(NULL, pc);
msg = camel_mime_message_new();
if (camel_mime_part_construct_from_parser((CamelMimePart *)msg, mp) == -1) {
/* set exception? */
camel_object_unref(msg);
break;
}
info = camel_message_info_new(NULL);
tmp = camel_medium_get_header((CamelMedium *)msg, "X-Mozilla-Status");
if (tmp)
flags |= decode_mozilla_status(tmp);
tmp = camel_medium_get_header((CamelMedium *)msg, "Status");
if (tmp)
flags |= decode_status(tmp);
tmp = camel_medium_get_header((CamelMedium *)msg, "X-Status");
if (tmp)
flags |= decode_status(tmp);
camel_message_info_set_flags(info, flags, ~0);
camel_folder_append_message(folder, msg, info, NULL, &mm->ex);
camel_message_info_free(info);
camel_object_unref(msg);
if (camel_exception_is_set(&mm->ex))
break;
camel_mime_parser_step(mp, 0, 0);
}
camel_folder_sync(folder, FALSE, NULL);
camel_folder_thaw(folder);
camel_operation_end(NULL);
/* TODO: these api's are a bit weird, registering the old is the same as deregistering */
if (m->cancel)
camel_operation_register(oldcancel);
fail2:
camel_object_unref(mp);
}
fail1:
camel_folder_sync(folder, FALSE, NULL);
camel_object_unref(folder);
}
static void
import_mbox_done(struct _mail_msg *mm)
{
struct _import_mbox_msg *m = (struct _import_mbox_msg *)mm;
if (m->done)
m->done(m->done_data, &mm->ex);
}
static void
import_mbox_free (struct _mail_msg *mm)
{
struct _import_mbox_msg *m = (struct _import_mbox_msg *)mm;
if (m->cancel)
camel_operation_unref(m->cancel);
g_free(m->uri);
g_free(m->path);
}
static struct _mail_msg_op import_mbox_op = {
import_mbox_describe,
import_mbox_import,
import_mbox_done,
import_mbox_free,
};
int
mail_importer_import_mbox(const char *path, const char *folderuri, CamelOperation *cancel, void (*done)(void *data, CamelException *), void *data)
{
struct _import_mbox_msg *m;
int id;
m = mail_msg_new(&import_mbox_op, NULL, sizeof (*m));
m->path = g_strdup(path);
m->uri = g_strdup(folderuri);
m->done = done;
m->done_data = data;
if (cancel) {
m->cancel = cancel;
camel_operation_ref(cancel);
}
id = m->msg.seq;
e_thread_put(mail_thread_queued, (EMsg *)m);
return id;
}
void
mail_importer_import_mbox_sync(const char *path, const char *folderuri, CamelOperation *cancel)
{
struct _import_mbox_msg *m;
m = mail_msg_new(&import_mbox_op, NULL, sizeof (*m));
m->path = g_strdup(path);
m->uri = g_strdup(folderuri);
if (cancel) {
m->cancel = cancel;
camel_operation_ref(cancel);
}
import_mbox_import(&m->msg);
import_mbox_done(&m->msg);
mail_msg_free(&m->msg);
}
struct _import_folders_data {
MailImporterSpecial *special_folders;
CamelOperation *cancel;
guint elmfmt:1;
};
static void
import_folders_rec(struct _import_folders_data *m, const char *filepath, const char *folderparent)
{
GDir *dir;
const char *d;
struct stat st;
char *filefull, *foldersub, *uri, *utf8_filename;
const char *folder;
dir = g_dir_open(filepath, 0, NULL);
if (dir == NULL)
return;
utf8_filename = g_filename_to_utf8 (filepath, -1, NULL, NULL, NULL);
camel_operation_start(NULL, _("Scanning %s"), utf8_filename);
g_free (utf8_filename);
while ( (d=g_dir_read_name(dir))) {
if (d[0] == '.')
continue;
filefull = g_build_filename(filepath, d, NULL);
/* skip non files and directories, and skip directories in mozilla mode */
if (g_stat(filefull, &st) == -1
|| !(S_ISREG(st.st_mode)
|| (m->elmfmt && S_ISDIR(st.st_mode)))) {
g_free(filefull);
continue;
}
folder = d;
if (folderparent == NULL) {
int i;
for (i=0;m->special_folders[i].orig;i++)
if (strcmp(m->special_folders[i].orig, folder) == 0) {
folder = m->special_folders[i].new;
break;
}
/* FIXME: need a better way to get default store location */
uri = g_strdup_printf("mbox:%s/mail/local#%s", mail_component_peek_base_directory(NULL), folder);
} else {
uri = g_strdup_printf("mbox:%s/mail/local#%s/%s", mail_component_peek_base_directory(NULL), folderparent, folder);
}
printf("importing to uri %s\n", uri);
mail_importer_import_mbox_sync(filefull, uri, m->cancel);
g_free(uri);
/* This little gem re-uses the stat buffer and filefull to automagically scan mozilla-format folders */
if (!m->elmfmt) {
char *tmp = g_strdup_printf("%s.sbd", filefull);
g_free(filefull);
filefull = tmp;
if (g_stat(filefull, &st) == -1) {
g_free(filefull);
continue;
}
}
if (S_ISDIR(st.st_mode)) {
foldersub = folderparent?g_strdup_printf("%s/%s", folderparent, folder):g_strdup(folder);
import_folders_rec(m, filefull, foldersub);
g_free(foldersub);
}
g_free(filefull);
}
g_dir_close(dir);
camel_operation_end(NULL);
}
/**
* mail_importer_import_folders_sync:
* @filepath:
* @:
* @flags:
* @cancel:
*
* import from a base path @filepath into the root local folder tree,
* scanning all sub-folders.
*
* if @flags is MAIL_IMPORTER_MOZFMT, then subfolders are assumed to
* be in mozilla/evolutoin 1.5 format, appending '.sbd' to the
* directory name. Otherwise they are in elm/mutt/pine format, using
* standard unix directories.
**/
void
mail_importer_import_folders_sync(const char *filepath, MailImporterSpecial special_folders[], int flags, CamelOperation *cancel)
{
struct _import_folders_data m;
CamelOperation *oldcancel = NULL;
m.special_folders = special_folders;
m.elmfmt = (flags & MAIL_IMPORTER_MOZFMT) == 0;
m.cancel = cancel;
if (cancel)
oldcancel = camel_operation_register(cancel);
import_folders_rec(&m, filepath, NULL);
if (cancel)
camel_operation_register(oldcancel);
}