* refactoring: split src/ into mu/ and lib/
This commit is contained in:
95
lib/Makefile.am
Normal file
95
lib/Makefile.am
Normal file
@ -0,0 +1,95 @@
|
||||
## Copyright (C) 2010-2012 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
include $(top_srcdir)/gtest.mk
|
||||
|
||||
# enforce compiling guile (optionally) first,then this dir first
|
||||
# before decending into tests/
|
||||
SUBDIRS= .
|
||||
|
||||
if BUILD_TESTS
|
||||
SUBDIRS += tests
|
||||
endif
|
||||
|
||||
INCLUDES=$(XAPIAN_CXXFLAGS) $(GMIME_CFLAGS) $(GLIB_CFLAGS)
|
||||
|
||||
# don't use -Werror, as it might break on other compilers
|
||||
# use -Wno-unused-parameters, because some callbacks may not
|
||||
# really need all the params they get
|
||||
AM_CFLAGS=-Wall -Wextra -Wno-unused-parameter -Wdeclaration-after-statement -pedantic -Wno-variadic-macros
|
||||
AM_CXXFLAGS=-Wall -Wextra -Wno-unused-parameter
|
||||
|
||||
noinst_LTLIBRARIES= \
|
||||
libmu.la
|
||||
|
||||
libmu_la_SOURCES= \
|
||||
mu-bookmarks.c \
|
||||
mu-bookmarks.h \
|
||||
mu-contacts.c \
|
||||
mu-contacts.h \
|
||||
mu-container.c \
|
||||
mu-container.h \
|
||||
mu-date.c \
|
||||
mu-date.h \
|
||||
mu-flags.h \
|
||||
mu-flags.c \
|
||||
mu-index.c \
|
||||
mu-index.h \
|
||||
mu-log.c \
|
||||
mu-log.h \
|
||||
mu-maildir.c \
|
||||
mu-maildir.h \
|
||||
mu-msg-cache.c \
|
||||
mu-msg-cache.h \
|
||||
mu-msg-doc.cc \
|
||||
mu-msg-doc.h \
|
||||
mu-msg-fields.c \
|
||||
mu-msg-fields.h \
|
||||
mu-msg-file.c \
|
||||
mu-msg-file.h \
|
||||
mu-msg-iter.cc \
|
||||
mu-msg-iter.h \
|
||||
mu-msg-part.c \
|
||||
mu-msg-part.h \
|
||||
mu-msg-prio.c \
|
||||
mu-msg-prio.h \
|
||||
mu-msg-priv.h \
|
||||
mu-msg-sexp.c \
|
||||
mu-msg.c \
|
||||
mu-msg.h \
|
||||
mu-msg.h \
|
||||
mu-query.cc \
|
||||
mu-query.h \
|
||||
mu-runtime.c \
|
||||
mu-runtime.h \
|
||||
mu-store.cc \
|
||||
mu-store.h \
|
||||
mu-store-read.cc \
|
||||
mu-store-write.cc \
|
||||
mu-store-priv.hh \
|
||||
mu-str-normalize.c \
|
||||
mu-str.c \
|
||||
mu-str.h \
|
||||
mu-threader.c \
|
||||
mu-threader.h \
|
||||
mu-util.c \
|
||||
mu-util.h
|
||||
|
||||
|
||||
libmu_la_LIBADD= \
|
||||
$(XAPIAN_LIBS) \
|
||||
$(GMIME_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
147
lib/mu-bookmarks.c
Normal file
147
lib/mu-bookmarks.c
Normal file
@ -0,0 +1,147 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
/*
|
||||
** Copyright (C) 2010-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include "mu-bookmarks.h"
|
||||
|
||||
#define MU_BOOKMARK_GROUP "mu"
|
||||
|
||||
struct _MuBookmarks {
|
||||
char *_bmpath;
|
||||
GHashTable *_hash;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
fill_hash (GHashTable *hash, GKeyFile *kfile)
|
||||
{
|
||||
gchar **keys, **cur;
|
||||
|
||||
keys = g_key_file_get_keys (kfile, MU_BOOKMARK_GROUP, NULL, NULL);
|
||||
if (!keys)
|
||||
return;
|
||||
|
||||
for (cur = keys; *cur; ++cur) {
|
||||
gchar *val;
|
||||
val = g_key_file_get_string (kfile, MU_BOOKMARK_GROUP,
|
||||
*cur, NULL);
|
||||
if (val)
|
||||
g_hash_table_insert (hash, *cur, val);
|
||||
}
|
||||
|
||||
/* don't use g_strfreev, because we put them in the hash table;
|
||||
* only free the gchar** itself */
|
||||
g_free (keys);
|
||||
}
|
||||
|
||||
static GHashTable*
|
||||
create_hash_from_key_file (const gchar *bmpath)
|
||||
{
|
||||
GKeyFile *kfile;
|
||||
GHashTable *hash;
|
||||
|
||||
kfile = g_key_file_new ();
|
||||
|
||||
if (!g_key_file_load_from_file (kfile, bmpath, G_KEY_FILE_NONE, NULL)) {
|
||||
g_key_file_free (kfile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
fill_hash (hash, kfile);
|
||||
|
||||
g_key_file_free (kfile);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
|
||||
MuBookmarks*
|
||||
mu_bookmarks_new (const gchar *bmpath)
|
||||
{
|
||||
MuBookmarks *bookmarks;
|
||||
GHashTable *hash;
|
||||
|
||||
g_return_val_if_fail (bmpath, NULL);
|
||||
|
||||
hash = create_hash_from_key_file (bmpath);
|
||||
if (!hash)
|
||||
return NULL;
|
||||
|
||||
bookmarks = g_new (MuBookmarks, 1);
|
||||
|
||||
bookmarks->_bmpath = g_strdup (bmpath);
|
||||
bookmarks->_hash = hash;
|
||||
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
mu_bookmarks_destroy (MuBookmarks *bm)
|
||||
{
|
||||
if (!bm)
|
||||
return;
|
||||
|
||||
g_free (bm->_bmpath);
|
||||
g_hash_table_destroy (bm->_hash);
|
||||
g_free (bm);
|
||||
}
|
||||
|
||||
const gchar*
|
||||
mu_bookmarks_lookup (MuBookmarks *bm, const gchar *name)
|
||||
{
|
||||
g_return_val_if_fail (bm, NULL);
|
||||
g_return_val_if_fail (name, NULL);
|
||||
|
||||
return g_hash_table_lookup (bm->_hash, name);
|
||||
}
|
||||
|
||||
struct _BMData {
|
||||
MuBookmarksForeachFunc _func;
|
||||
gpointer _user_data;
|
||||
};
|
||||
typedef struct _BMData BMData;
|
||||
|
||||
|
||||
static void
|
||||
each_bookmark (const gchar* key, const gchar *val, BMData *bmdata)
|
||||
{
|
||||
bmdata->_func (key, val, bmdata->_user_data);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_bookmarks_foreach (MuBookmarks *bm, MuBookmarksForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
BMData bmdata;
|
||||
|
||||
g_return_if_fail (bm);
|
||||
g_return_if_fail (func);
|
||||
|
||||
bmdata._func = func;
|
||||
bmdata._user_data = user_data;
|
||||
|
||||
g_hash_table_foreach (bm->_hash, (GHFunc)each_bookmark, &bmdata);
|
||||
}
|
||||
|
||||
75
lib/mu-bookmarks.h
Normal file
75
lib/mu-bookmarks.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_BOOKMARKS_H__
|
||||
#define __MU_BOOKMARKS_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _MuBookmarks MuBookmarks;
|
||||
|
||||
|
||||
/**
|
||||
* create a new bookmarks object. when it's no longer needed, use
|
||||
* mu_bookmarks_destroy
|
||||
*
|
||||
* @param bmpath path to the bookmarks file
|
||||
*
|
||||
* @return a new BookMarks object, or NULL in case of error
|
||||
*/
|
||||
MuBookmarks *mu_bookmarks_new (const gchar *bmpath)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* destroy a bookmarks object
|
||||
*
|
||||
* @param bm a bookmarks object, or NULL
|
||||
*/
|
||||
void mu_bookmarks_destroy (MuBookmarks *bm);
|
||||
|
||||
|
||||
/**
|
||||
* get the value for some bookmark
|
||||
*
|
||||
* @param bm a valid bookmarks object
|
||||
* @param name name of the bookmark to retrieve
|
||||
*
|
||||
* @return the value of the bookmark or NULL in case in error, e.g. if
|
||||
* the bookmark was not found
|
||||
*/
|
||||
const gchar* mu_bookmarks_lookup (MuBookmarks *bm, const gchar *name);
|
||||
|
||||
typedef void (*MuBookmarksForeachFunc) (const gchar *key, const gchar *val,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* call a function for each bookmark
|
||||
*
|
||||
* @param bm a valid bookmarks object
|
||||
* @param func a callback function to be called for each bookmarks
|
||||
* @param user_data a user pointer passed to the callback
|
||||
*/
|
||||
void mu_bookmarks_foreach (MuBookmarks *bm, MuBookmarksForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_BOOKMARKS_H__*/
|
||||
447
lib/mu-contacts.c
Normal file
447
lib/mu-contacts.c
Normal file
@ -0,0 +1,447 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mu-contacts.h"
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
|
||||
#define EMAIL_KEY "email"
|
||||
#define NAME_KEY "name"
|
||||
#define TSTAMP_KEY "tstamp"
|
||||
|
||||
struct _ContactInfo {
|
||||
gchar *_name, *_email;
|
||||
time_t _tstamp;
|
||||
};
|
||||
typedef struct _ContactInfo ContactInfo;
|
||||
|
||||
static void contact_info_destroy (ContactInfo *cinfo);
|
||||
static ContactInfo *contact_info_new (char *email, char *name,
|
||||
time_t tstamp);
|
||||
|
||||
struct _MuContacts {
|
||||
GKeyFile *_ccache;
|
||||
gchar *_path;
|
||||
|
||||
GHashTable *_hash;
|
||||
gboolean _dirty;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* we use the e-mail address to create a key in the GKeyFile, but we
|
||||
* have to mutilate a bit so that it's (a) *cough* practically-unique
|
||||
* and (b) valid as a GKeyFile group name (ie., valid utf8, no control
|
||||
* chars, no '[' or ']')
|
||||
*/
|
||||
static const char*
|
||||
encode_email_address (const char *addr)
|
||||
{
|
||||
static char enc[254 + 1]; /* max size for an e-mail addr */
|
||||
char *cur;
|
||||
|
||||
if (!addr)
|
||||
return FALSE;
|
||||
|
||||
/* make sure chars are with {' ' .. '~'}, and not '[' ']' */
|
||||
for (cur = strncpy(enc, addr, sizeof(enc)); *cur != '\0'; ++cur)
|
||||
if (!isalnum(*cur))
|
||||
*cur = 'A' + (*cur % ('Z' - 'A'));
|
||||
|
||||
return enc;
|
||||
}
|
||||
|
||||
static GKeyFile*
|
||||
load_key_file (const char *path)
|
||||
{
|
||||
GError *err;
|
||||
GKeyFile *keyfile;
|
||||
gboolean file_exists;
|
||||
|
||||
err = NULL;
|
||||
|
||||
/* of course this is racy, but it's only for giving more
|
||||
* meaningful errors to users */
|
||||
file_exists = TRUE;
|
||||
if (access(path, F_OK) != 0) {
|
||||
if (errno != ENOENT) {
|
||||
g_warning ("cannot open %s: %s", path, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
file_exists = FALSE;
|
||||
}
|
||||
|
||||
err = NULL;
|
||||
keyfile = g_key_file_new ();
|
||||
|
||||
if (file_exists && !g_key_file_load_from_file
|
||||
(keyfile, path, G_KEY_FILE_KEEP_COMMENTS, &err)) {
|
||||
g_warning ("could not load keyfile %s: %s", path, err->message);
|
||||
g_error_free (err);
|
||||
g_key_file_free (keyfile);
|
||||
return NULL;
|
||||
}
|
||||
return keyfile;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
get_values (GKeyFile *kfile, const gchar *group,
|
||||
gchar **email, gchar **name, size_t *tstamp)
|
||||
{
|
||||
GError *err;
|
||||
|
||||
err = NULL;
|
||||
*email = g_key_file_get_value (kfile, group, EMAIL_KEY, &err);
|
||||
if (!*email) {
|
||||
g_warning ("cannot get e-mail for %s: %s",
|
||||
group, err->message ? err->message: "error");
|
||||
if (err)
|
||||
g_error_free (err);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*tstamp = (time_t)g_key_file_get_integer (kfile, group, TSTAMP_KEY, &err);
|
||||
if (err) {
|
||||
g_warning ("cannot get timestamp for %s: %s",
|
||||
group, err->message ? err->message: "error");
|
||||
if (err)
|
||||
g_error_free (err);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* name is not required */
|
||||
*name = g_key_file_get_value (kfile, group, NAME_KEY, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
deserialize_cache (MuContacts *self)
|
||||
{
|
||||
gchar **groups;
|
||||
gsize i, len;
|
||||
|
||||
groups = g_key_file_get_groups (self->_ccache, &len);
|
||||
for (i = 0; i != len; ++i) {
|
||||
ContactInfo *cinfo;
|
||||
char *name, *email;
|
||||
size_t tstamp;
|
||||
if (!get_values (self->_ccache, groups[i],
|
||||
&email, &name, &tstamp))
|
||||
continue; /* ignore this one... */
|
||||
|
||||
cinfo = contact_info_new (email, name, tstamp);
|
||||
|
||||
/* note, we're using the groups[i], so don't free with g_strfreev */
|
||||
g_hash_table_insert (self->_hash, groups[i],
|
||||
cinfo);
|
||||
}
|
||||
|
||||
g_free (groups);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
set_comment (GKeyFile *kfile)
|
||||
{
|
||||
GError *err;
|
||||
const char *comment =
|
||||
" automatically generated -- do not edit";
|
||||
|
||||
err = NULL;
|
||||
if (!g_key_file_set_comment (kfile, NULL, NULL, comment, &err)) {
|
||||
g_warning ("could not write comment to keyfile: %s",
|
||||
err->message);
|
||||
g_error_free (err);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
MuContacts*
|
||||
mu_contacts_new (const gchar *path)
|
||||
{
|
||||
MuContacts *self;
|
||||
|
||||
g_return_val_if_fail (path, NULL);
|
||||
self = g_new0 (MuContacts, 1);
|
||||
|
||||
self->_path = g_strdup (path);
|
||||
self->_hash = g_hash_table_new_full
|
||||
(g_str_hash, g_str_equal, g_free,
|
||||
(GDestroyNotify)contact_info_destroy);
|
||||
|
||||
self->_ccache = load_key_file (path);
|
||||
if (!self->_ccache || !set_comment (self->_ccache)) {
|
||||
mu_contacts_destroy (self);
|
||||
return NULL;
|
||||
}
|
||||
deserialize_cache (self);
|
||||
MU_WRITE_LOG("deserialized contacts from cache %s",
|
||||
path);
|
||||
|
||||
self->_dirty = FALSE;
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_contacts_clear (MuContacts *self)
|
||||
{
|
||||
g_return_if_fail (self);
|
||||
|
||||
if (self->_ccache)
|
||||
g_key_file_free (self->_ccache);
|
||||
|
||||
g_hash_table_remove_all (self->_hash);
|
||||
|
||||
self->_ccache = g_key_file_new ();
|
||||
self->_dirty = FALSE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
gboolean
|
||||
mu_contacts_add (MuContacts *self, const char *email, const char* name,
|
||||
time_t tstamp)
|
||||
{
|
||||
ContactInfo *cinfo;
|
||||
const char* group;
|
||||
|
||||
g_return_val_if_fail (self, FALSE);
|
||||
g_return_val_if_fail (email, FALSE);
|
||||
|
||||
/* add the info, if either there is no info for this email
|
||||
* yet, *OR* the new one is more recent and does not have an
|
||||
* empty name */
|
||||
group = encode_email_address (email);
|
||||
|
||||
cinfo = (ContactInfo*) g_hash_table_lookup (self->_hash, group);
|
||||
if (!cinfo || (cinfo->_tstamp < tstamp && !mu_str_is_empty(name))) {
|
||||
ContactInfo *ci;
|
||||
ci = contact_info_new (g_strdup(email),
|
||||
name ? g_strdup(name) : NULL,
|
||||
tstamp);
|
||||
|
||||
g_hash_table_insert (self->_hash, g_strdup(group), ci);
|
||||
|
||||
return self->_dirty = TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
struct _EachContactData {
|
||||
MuContactsForeachFunc _func;
|
||||
gpointer _user_data;
|
||||
GRegex *_rx;
|
||||
size_t _num;
|
||||
};
|
||||
typedef struct _EachContactData EachContactData;
|
||||
|
||||
static void /* email will never be NULL, but ci->_name may be */
|
||||
each_contact (const char *group, ContactInfo *ci, EachContactData *ecdata)
|
||||
{
|
||||
if (!ci->_email)
|
||||
g_warning ("missing email: %u", (unsigned)ci->_tstamp);
|
||||
|
||||
/* ignore this contact if we have a regexp, and it matches
|
||||
* neither email nor name (if we have a name) */
|
||||
while (ecdata->_rx) { /* note, only once */
|
||||
if (g_regex_match (ecdata->_rx, ci->_email, 0, NULL))
|
||||
break; /* email matches? continue! */
|
||||
if (!ci->_name)
|
||||
return; /* email did not match, no name? ignore this one */
|
||||
if (g_regex_match (ecdata->_rx, ci->_name, 0, NULL))
|
||||
break; /* name matches? continue! */
|
||||
return; /* nothing matched, ignore this one */
|
||||
}
|
||||
|
||||
ecdata->_func (ci->_email, ci->_name,
|
||||
ci->_tstamp, ecdata->_user_data);
|
||||
++ecdata->_num;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_contacts_foreach (MuContacts *self, MuContactsForeachFunc func,
|
||||
gpointer user_data, const char *pattern, size_t *num)
|
||||
{
|
||||
EachContactData ecdata;
|
||||
|
||||
g_return_val_if_fail (self, FALSE);
|
||||
g_return_val_if_fail (func, FALSE);
|
||||
|
||||
if (pattern) {
|
||||
GError *err;
|
||||
err = NULL;
|
||||
ecdata._rx = g_regex_new
|
||||
(pattern, G_REGEX_CASELESS|G_REGEX_OPTIMIZE,
|
||||
0, &err);
|
||||
if (!ecdata._rx) {
|
||||
g_warning ("error in regexp '%s': %s",
|
||||
pattern, err->message);
|
||||
g_error_free (err);
|
||||
return FALSE;
|
||||
}
|
||||
} else
|
||||
ecdata._rx = NULL;
|
||||
|
||||
ecdata._func = func;
|
||||
ecdata._user_data = user_data;
|
||||
ecdata._num = 0;
|
||||
|
||||
g_hash_table_foreach (self->_hash,
|
||||
(GHFunc)each_contact,
|
||||
&ecdata);
|
||||
|
||||
if (ecdata._rx)
|
||||
g_regex_unref (ecdata._rx);
|
||||
|
||||
if (num)
|
||||
*num = ecdata._num;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
each_keyval (const char *group, ContactInfo *cinfo, MuContacts *self)
|
||||
{
|
||||
/* use set value so the string do not necessarily have to be
|
||||
* valid utf-8 */
|
||||
if (cinfo->_name)
|
||||
g_key_file_set_value (self->_ccache, group, NAME_KEY,
|
||||
cinfo->_name);
|
||||
|
||||
g_key_file_set_value (self->_ccache, group, EMAIL_KEY,
|
||||
cinfo->_email);
|
||||
g_key_file_set_integer (self->_ccache, group, TSTAMP_KEY,
|
||||
(int)cinfo->_tstamp);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
serialize_cache (MuContacts *self)
|
||||
{
|
||||
gchar *data;
|
||||
gsize len;
|
||||
gboolean rv;
|
||||
|
||||
g_hash_table_foreach (self->_hash, (GHFunc) each_keyval, self);
|
||||
|
||||
/* Note: err arg is unused */
|
||||
data = g_key_file_to_data (self->_ccache, &len, NULL);
|
||||
if (len) {
|
||||
GError *err;
|
||||
err = NULL;
|
||||
rv = g_file_set_contents (self->_path, data, len, &err);
|
||||
if (!rv) {
|
||||
g_warning ("failed to serialize cache to %s: %s",
|
||||
self->_path, err->message);
|
||||
g_error_free (err);
|
||||
}
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
mu_contacts_destroy (MuContacts *self)
|
||||
{
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
if (self->_ccache && self->_dirty) {
|
||||
serialize_cache (self);
|
||||
MU_WRITE_LOG("serialized contacts cache %s",
|
||||
self->_path);
|
||||
}
|
||||
|
||||
if (self->_ccache)
|
||||
g_key_file_free (self->_ccache);
|
||||
|
||||
g_free (self->_path);
|
||||
|
||||
if (self->_hash)
|
||||
g_hash_table_destroy (self->_hash);
|
||||
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
clear_str (char* str)
|
||||
{
|
||||
/* replace ctrl chars with '_' */
|
||||
while (str && *str) {
|
||||
if (iscntrl (*str))
|
||||
*str = '_';
|
||||
++str;
|
||||
}
|
||||
|
||||
if (str)
|
||||
g_strstrip (str);
|
||||
}
|
||||
|
||||
/* note, we will *own* the name, email we get, and we'll free them in
|
||||
* the end... */
|
||||
static ContactInfo *
|
||||
contact_info_new (char *email, char *name, time_t tstamp)
|
||||
{
|
||||
ContactInfo *cinfo;
|
||||
|
||||
/* email should not be NULL, name can */
|
||||
g_return_val_if_fail (email, NULL);
|
||||
|
||||
cinfo = g_slice_new (ContactInfo);
|
||||
|
||||
/* we need to clear the strings from control chars because
|
||||
* they could screw up the keyfile */
|
||||
clear_str (email);
|
||||
clear_str (name);
|
||||
|
||||
cinfo->_email = email;
|
||||
cinfo->_name = name;
|
||||
cinfo->_tstamp = tstamp;
|
||||
|
||||
return cinfo;
|
||||
}
|
||||
|
||||
static void
|
||||
contact_info_destroy (ContactInfo *cinfo)
|
||||
{
|
||||
if (!cinfo)
|
||||
return;
|
||||
|
||||
g_free (cinfo->_email);
|
||||
g_free (cinfo->_name);
|
||||
|
||||
g_slice_free (ContactInfo, cinfo);
|
||||
}
|
||||
|
||||
114
lib/mu-contacts.h
Normal file
114
lib/mu-contacts.h
Normal file
@ -0,0 +1,114 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_CONTACTS_H__
|
||||
#define __MU_CONTACTS_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <time.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuContacts;
|
||||
typedef struct _MuContacts MuContacts;
|
||||
|
||||
/**
|
||||
* create a new MuContacts object; use mu_contacts_destroy when you no longer need it
|
||||
*
|
||||
* @param ccachefile full path to the file with cached list of contacts
|
||||
*
|
||||
* @return a new MuContacts* if succeeded, NULL otherwise
|
||||
*/
|
||||
MuContacts* mu_contacts_new (const gchar *ccachefile)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* add a contacts; if there's a contact with this e-mail address
|
||||
* already, it will not updated unless the timestamp of this one is
|
||||
* higher and has a non-empty name
|
||||
*
|
||||
* @param contacts a contacts object
|
||||
* @param email e-mail address of the contact (not NULL)
|
||||
* @param name name of the contact (or NULL)
|
||||
* @param tstamp timestamp for this address
|
||||
*
|
||||
* @return TRUE if succeeded, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_contacts_add (MuContacts *self, const char *email,
|
||||
const char* name, time_t tstamp);
|
||||
|
||||
/**
|
||||
* destroy the Contacts object
|
||||
*
|
||||
* @param contacts a contacts object
|
||||
*/
|
||||
void mu_contacts_destroy (MuContacts *self);
|
||||
|
||||
|
||||
/**
|
||||
* clear all contacts from the cache
|
||||
*
|
||||
* @param self a MuContacts instance
|
||||
*/
|
||||
void mu_contacts_clear (MuContacts *self);
|
||||
|
||||
|
||||
/**
|
||||
* get the path for the contacts cache file
|
||||
*
|
||||
* @param contacts a contacts object
|
||||
*
|
||||
* @return the path as a constant string (don't free), or NULL in case
|
||||
* of error
|
||||
*/
|
||||
const gchar* mu_contacts_get_path (MuContacts *self);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* call called for mu_contacts_foreach; returns the e-mail address,
|
||||
* name (which may be NULL) and the timestamp for the address
|
||||
*
|
||||
*/
|
||||
typedef void (*MuContactsForeachFunc) (const char *email, const char *name,
|
||||
time_t tstamp, gpointer user_data);
|
||||
|
||||
/**
|
||||
* call a function for either each contact, or each contact satisfying
|
||||
* a regular expression,
|
||||
*
|
||||
* @param contacts contacts object
|
||||
* @param func callback function to be called for each
|
||||
* @param user_data user data to pass to the callback
|
||||
* @param pattern a regular expression which matches either the e-mail
|
||||
* or name, to filter out contacts, or NULL to not do any filtering.
|
||||
* @param num receives the number of contacts found, or NULL
|
||||
*
|
||||
* @return TRUE if the function succeeded, or FALSE if the provide
|
||||
* regular expression was invalid (and not NULL)
|
||||
*/
|
||||
gboolean mu_contacts_foreach (MuContacts *self, MuContactsForeachFunc func,
|
||||
gpointer user_data, const char* pattern, size_t *num);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_CONTACTS_H__*/
|
||||
615
lib/mu-container.c
Normal file
615
lib/mu-container.c
Normal file
@ -0,0 +1,615 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h> /* for memset */
|
||||
#include <math.h> /* for log, ceil */
|
||||
|
||||
#include "mu-container.h"
|
||||
#include "mu-msg.h"
|
||||
#include "mu-msg-iter.h"
|
||||
|
||||
|
||||
/*
|
||||
* path data structure, to determine the thread paths mentioned above;
|
||||
* the path is filled as we're traversing the tree of MuContainers
|
||||
* (messages)
|
||||
*/
|
||||
struct _Path {
|
||||
int *_data;
|
||||
guint _len;
|
||||
};
|
||||
typedef struct _Path Path;
|
||||
|
||||
static Path* path_new (guint initial);
|
||||
static void path_destroy (Path *p);
|
||||
static void path_inc (Path *p, guint index);
|
||||
static gchar* path_to_string (Path *p, const char* frmt);
|
||||
|
||||
MuContainer*
|
||||
mu_container_new (MuMsg *msg, guint docid, const char *msgid)
|
||||
{
|
||||
MuContainer *c;
|
||||
|
||||
g_return_val_if_fail (!msg || docid != 0, NULL);
|
||||
|
||||
c = g_slice_new0 (MuContainer);
|
||||
if (msg)
|
||||
c->msg = mu_msg_ref (msg);
|
||||
|
||||
c->docid = docid;
|
||||
c->msgid = msgid;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
mu_container_destroy (MuContainer *c)
|
||||
{
|
||||
if (!c)
|
||||
return;
|
||||
|
||||
if (c->msg)
|
||||
mu_msg_unref (c->msg);
|
||||
|
||||
g_slice_free (MuContainer, c);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_parent (MuContainer *c, MuContainer *parent)
|
||||
{
|
||||
while (c) {
|
||||
c->parent = parent;
|
||||
c = c->next;
|
||||
}
|
||||
}
|
||||
|
||||
static MuContainer*
|
||||
find_last (MuContainer *c)
|
||||
{
|
||||
while (c && c->next)
|
||||
c = c->next;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
G_GNUC_UNUSED static gboolean
|
||||
check_dup (MuContainer *c, GHashTable *hash)
|
||||
{
|
||||
if (g_hash_table_lookup (hash, c)) {
|
||||
g_warning ("ALREADY!!");
|
||||
mu_container_dump (c, TRUE);
|
||||
g_assert (0);
|
||||
} else
|
||||
g_hash_table_insert (hash, c, GUINT_TO_POINTER(TRUE));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
G_GNUC_UNUSED static void
|
||||
assert_no_duplicates (MuContainer *c)
|
||||
{
|
||||
GHashTable *hash;
|
||||
|
||||
hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
|
||||
mu_container_foreach (c,
|
||||
(MuContainerForeachFunc)check_dup,
|
||||
hash);
|
||||
|
||||
g_hash_table_destroy (hash);
|
||||
}
|
||||
|
||||
|
||||
MuContainer*
|
||||
mu_container_append_siblings (MuContainer *c, MuContainer *sibling)
|
||||
{
|
||||
g_assert (c);
|
||||
|
||||
g_return_val_if_fail (c, NULL);
|
||||
g_return_val_if_fail (sibling, NULL);
|
||||
g_return_val_if_fail (c != sibling, NULL);
|
||||
|
||||
/* assert_no_duplicates (c); */
|
||||
|
||||
set_parent (sibling, c->parent);
|
||||
(find_last(c))->next = sibling;
|
||||
|
||||
/* assert_no_duplicates (c); */
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
MuContainer*
|
||||
mu_container_remove_sibling (MuContainer *c, MuContainer *sibling)
|
||||
{
|
||||
MuContainer *cur, *prev;
|
||||
|
||||
g_return_val_if_fail (c, NULL);
|
||||
g_return_val_if_fail (sibling, NULL);
|
||||
|
||||
for (prev = NULL, cur = c; cur; cur = cur->next) {
|
||||
|
||||
if (cur == sibling) {
|
||||
if (!prev)
|
||||
c = cur->next;
|
||||
else
|
||||
prev->next = cur->next;
|
||||
break;
|
||||
}
|
||||
prev = cur;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
MuContainer*
|
||||
mu_container_append_children (MuContainer *c, MuContainer *child)
|
||||
{
|
||||
g_return_val_if_fail (c, NULL);
|
||||
g_return_val_if_fail (child, NULL);
|
||||
g_return_val_if_fail (c != child, NULL);
|
||||
|
||||
/* assert_no_duplicates (c); */
|
||||
|
||||
set_parent (child, c);
|
||||
if (!c->child)
|
||||
c->child = child;
|
||||
else
|
||||
c->child = mu_container_append_siblings (c->child, child);
|
||||
|
||||
/* assert_no_duplicates (c->child); */
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
MuContainer*
|
||||
mu_container_remove_child (MuContainer *c, MuContainer *child)
|
||||
{
|
||||
g_return_val_if_fail (c, NULL);
|
||||
g_return_val_if_fail (child, NULL);
|
||||
|
||||
g_assert (!child->child);
|
||||
g_return_val_if_fail (!child->child, NULL);
|
||||
g_return_val_if_fail (c != child, NULL);
|
||||
|
||||
c->child = mu_container_remove_sibling (c->child, child);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
typedef void (*MuContainerPathForeachFunc) (MuContainer*, gpointer, Path*);
|
||||
|
||||
static void
|
||||
mu_container_path_foreach_real (MuContainer *c, guint level, Path *path,
|
||||
MuContainerPathForeachFunc func, gpointer user_data)
|
||||
{
|
||||
if (!c)
|
||||
return;
|
||||
|
||||
path_inc (path, level);
|
||||
func (c, user_data, path);
|
||||
|
||||
/* children */
|
||||
mu_container_path_foreach_real (c->child, level + 1, path, func, user_data);
|
||||
|
||||
/* siblings */
|
||||
mu_container_path_foreach_real (c->next, level, path, func, user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
mu_container_path_foreach (MuContainer *c, MuContainerPathForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
Path *path;
|
||||
|
||||
path = path_new (100);
|
||||
|
||||
mu_container_path_foreach_real (c, 0, path, func, user_data);
|
||||
|
||||
path_destroy (path);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_container_foreach (MuContainer *c, MuContainerForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_val_if_fail (func, FALSE);
|
||||
|
||||
if (!c)
|
||||
return TRUE;
|
||||
|
||||
if (!mu_container_foreach (c->child, func, user_data))
|
||||
return FALSE; /* recurse into children */
|
||||
|
||||
/* recurse into siblings */
|
||||
if (!mu_container_foreach (c->next, func, user_data))
|
||||
return FALSE;
|
||||
|
||||
return func (c, user_data);
|
||||
}
|
||||
|
||||
|
||||
MuContainer*
|
||||
mu_container_splice_children (MuContainer *parent, MuContainer *child)
|
||||
{
|
||||
MuContainer *newchild;
|
||||
|
||||
g_return_val_if_fail (parent, NULL);
|
||||
g_return_val_if_fail (child, NULL);
|
||||
g_return_val_if_fail (parent != child, NULL);
|
||||
|
||||
newchild = child->child;
|
||||
child->child=NULL;
|
||||
|
||||
mu_container_remove_child (parent, child);
|
||||
|
||||
return mu_container_append_children (parent, newchild);
|
||||
}
|
||||
|
||||
|
||||
static GSList*
|
||||
mu_container_to_list (MuContainer *c)
|
||||
{
|
||||
GSList *lst;
|
||||
|
||||
for (lst = NULL; c; c = c->next)
|
||||
lst = g_slist_prepend (lst, c);
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
|
||||
static MuContainer*
|
||||
mu_container_from_list (GSList *lst)
|
||||
{
|
||||
MuContainer *c, *cur;
|
||||
|
||||
if (!lst)
|
||||
return NULL;
|
||||
|
||||
for (c = cur = (MuContainer*)lst->data; cur; lst = g_slist_next(lst)) {
|
||||
cur->next = lst ? (MuContainer*)lst->data : NULL;
|
||||
cur=cur->next;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
struct _SortFuncData {
|
||||
MuMsgFieldId mfid;
|
||||
gboolean revert;
|
||||
gpointer user_data;
|
||||
};
|
||||
typedef struct _SortFuncData SortFuncData;
|
||||
|
||||
|
||||
static int
|
||||
sort_func_wrapper (MuContainer *a, MuContainer *b, SortFuncData *data)
|
||||
{
|
||||
MuContainer *a1, *b1;
|
||||
|
||||
/* use the first non-empty 'left child' message if this one
|
||||
* is */
|
||||
for (a1 = a; a1->msg == NULL && a1->child != NULL; a1 = a1->child);
|
||||
for (b1 = b; b1->msg == NULL && b1->child != NULL; b1 = b1->child);
|
||||
|
||||
if (a1 == b1)
|
||||
return 0;
|
||||
else if (!a1->msg)
|
||||
return 1;
|
||||
else if (!b1->msg)
|
||||
return -1;
|
||||
|
||||
if (data->revert)
|
||||
return mu_msg_cmp (b1->msg, a1->msg, data->mfid);
|
||||
else
|
||||
return mu_msg_cmp (a1->msg, b1->msg, data->mfid);
|
||||
}
|
||||
|
||||
static MuContainer*
|
||||
mu_container_sort_real (MuContainer *c, SortFuncData *sfdata)
|
||||
{
|
||||
GSList *lst;
|
||||
MuContainer *cur;
|
||||
|
||||
if (!c)
|
||||
return NULL;
|
||||
|
||||
for (cur = c; cur; cur = cur->next)
|
||||
if (cur->child)
|
||||
cur->child = mu_container_sort_real (cur->child, sfdata);
|
||||
|
||||
/* sort siblings */
|
||||
lst = mu_container_to_list (c);
|
||||
lst = g_slist_sort_with_data(lst,
|
||||
(GCompareDataFunc)sort_func_wrapper,
|
||||
sfdata);
|
||||
c = mu_container_from_list (lst);
|
||||
g_slist_free (lst);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
MuContainer*
|
||||
mu_container_sort (MuContainer *c, MuMsgFieldId mfid, gboolean revert,
|
||||
gpointer user_data)
|
||||
{
|
||||
SortFuncData sfdata;
|
||||
|
||||
sfdata.mfid = mfid;
|
||||
sfdata.revert = revert;
|
||||
sfdata.user_data = user_data;
|
||||
|
||||
g_return_val_if_fail (c, NULL);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), NULL);
|
||||
|
||||
return mu_container_sort_real (c, &sfdata);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
unequal (MuContainer *a, MuContainer *b)
|
||||
{
|
||||
return a == b ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_container_reachable (MuContainer *haystack, MuContainer *needle)
|
||||
{
|
||||
g_return_val_if_fail (haystack, FALSE);
|
||||
g_return_val_if_fail (needle, FALSE);
|
||||
|
||||
if (!mu_container_foreach
|
||||
(haystack, (MuContainerForeachFunc)unequal, needle))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
dump_container (MuContainer *c)
|
||||
{
|
||||
const gchar* subject;
|
||||
|
||||
if (!c) {
|
||||
g_print ("<empty>\n");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
subject = (c->msg) ? mu_msg_get_subject (c->msg) : "<none>";
|
||||
|
||||
g_print ("[%s][%s m:%p p:%p docid:%u %s]\n",c->msgid, subject, (void*)c,
|
||||
(void*)c->parent, c->docid,
|
||||
c->msg ? mu_msg_get_path (c->msg) : "");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_container_dump (MuContainer *c, gboolean recursive)
|
||||
{
|
||||
g_return_if_fail (c);
|
||||
|
||||
if (!recursive)
|
||||
dump_container (c);
|
||||
else
|
||||
mu_container_foreach (c, (MuContainerForeachFunc)dump_container,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static Path*
|
||||
path_new (guint initial)
|
||||
{
|
||||
Path *p;
|
||||
|
||||
p = g_slice_new0 (Path);
|
||||
|
||||
p->_data = g_new0 (int, initial);
|
||||
p->_len = initial;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static void
|
||||
path_destroy (Path *p)
|
||||
{
|
||||
if (!p)
|
||||
return;
|
||||
|
||||
g_free (p->_data);
|
||||
g_slice_free (Path, p);
|
||||
}
|
||||
|
||||
static void
|
||||
path_inc (Path *p, guint index)
|
||||
{
|
||||
if (index + 1 >= p->_len) {
|
||||
p->_data = g_renew (int, p->_data, 2 * p->_len);
|
||||
memset (&p->_data[p->_len], 0, p->_len);
|
||||
p->_len *= 2;
|
||||
}
|
||||
|
||||
++p->_data[index];
|
||||
p->_data[index + 1] = 0;
|
||||
}
|
||||
|
||||
|
||||
static gchar*
|
||||
path_to_string (Path *p, const char* frmt)
|
||||
{
|
||||
char *str;
|
||||
guint u;
|
||||
|
||||
if (!p->_data)
|
||||
return NULL;
|
||||
|
||||
for (u = 0, str = NULL; p->_data[u] != 0; ++u) {
|
||||
|
||||
char segm[16];
|
||||
snprintf (segm, sizeof(segm), frmt, p->_data[u] - 1);
|
||||
|
||||
if (!str)
|
||||
str = g_strdup (segm);
|
||||
else {
|
||||
gchar *tmp;
|
||||
tmp = g_strdup_printf ("%s:%s", str, segm);
|
||||
g_free (str);
|
||||
str = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
count_colons (const char *str)
|
||||
{
|
||||
unsigned num;
|
||||
|
||||
num = 0;
|
||||
while (str++ && *str)
|
||||
if (*str == ':')
|
||||
++num;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static MuMsgIterThreadInfo*
|
||||
thread_info_new (gchar *threadpath, gboolean root, gboolean child,
|
||||
gboolean empty_parent, gboolean has_child, gboolean is_dup)
|
||||
{
|
||||
MuMsgIterThreadInfo *ti;
|
||||
|
||||
ti = g_slice_new (MuMsgIterThreadInfo);
|
||||
ti->threadpath = threadpath;
|
||||
ti->level = count_colons (threadpath); /* hacky... */
|
||||
|
||||
ti->prop = 0;
|
||||
ti->prop |= root ? MU_MSG_ITER_THREAD_PROP_ROOT : 0;
|
||||
ti->prop |= child ? MU_MSG_ITER_THREAD_PROP_FIRST_CHILD : 0;
|
||||
ti->prop |= empty_parent ? MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT : 0;
|
||||
ti->prop |= is_dup ? MU_MSG_ITER_THREAD_PROP_DUP : 0;
|
||||
ti->prop |= has_child ? MU_MSG_ITER_THREAD_PROP_HAS_CHILD : 0;
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
static void
|
||||
thread_info_destroy (MuMsgIterThreadInfo *ti)
|
||||
{
|
||||
if (ti) {
|
||||
g_free (ti->threadpath);
|
||||
g_slice_free (MuMsgIterThreadInfo, ti);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct _ThreadInfo {
|
||||
GHashTable *hash;
|
||||
const char* format;
|
||||
};
|
||||
typedef struct _ThreadInfo ThreadInfo;
|
||||
|
||||
|
||||
static void
|
||||
add_to_thread_info_hash (GHashTable *thread_info_hash, MuContainer *c,
|
||||
char *threadpath)
|
||||
{
|
||||
gboolean is_root, first_child, empty_parent, is_dup, has_child;
|
||||
|
||||
/* 'root' means we're a child of the dummy root-container */
|
||||
is_root = (c->parent == NULL);
|
||||
|
||||
first_child = is_root ? FALSE : (c->parent->child == c);
|
||||
empty_parent = is_root ? FALSE : (!c->parent->msg);
|
||||
is_dup = c->flags & MU_CONTAINER_FLAG_DUP;
|
||||
has_child = c->child ? TRUE : FALSE;
|
||||
|
||||
g_hash_table_insert (thread_info_hash,
|
||||
GUINT_TO_POINTER(c->docid),
|
||||
thread_info_new (threadpath,
|
||||
is_root,
|
||||
first_child,
|
||||
empty_parent,
|
||||
has_child,
|
||||
is_dup));
|
||||
}
|
||||
|
||||
/* device a format string that is the minimum size to fit up to
|
||||
* matchnum matches -- returns static memory */
|
||||
static const char*
|
||||
thread_segment_format_string (size_t matchnum)
|
||||
{
|
||||
unsigned digitnum;
|
||||
static char frmt[16];
|
||||
|
||||
/* get the number of digits needed in a hex-representation of
|
||||
* matchnum */
|
||||
digitnum = (unsigned) (ceil (log(matchnum)/log(16)));
|
||||
snprintf (frmt, sizeof(frmt),"%%0%ux", digitnum);
|
||||
|
||||
return frmt;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
add_thread_info (MuContainer *c, ThreadInfo *ti, Path *path)
|
||||
{
|
||||
gchar *pathstr;
|
||||
|
||||
pathstr = path_to_string (path, ti->format);
|
||||
add_to_thread_info_hash (ti->hash, c, pathstr);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
GHashTable*
|
||||
mu_container_thread_info_hash_new (MuContainer *root_set, size_t matchnum)
|
||||
{
|
||||
ThreadInfo ti;
|
||||
|
||||
g_return_val_if_fail (root_set, NULL);
|
||||
g_return_val_if_fail (matchnum > 0, NULL);
|
||||
|
||||
/* create hash docid => thread-info */
|
||||
ti.hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
||||
NULL,
|
||||
(GDestroyNotify)thread_info_destroy);
|
||||
|
||||
ti.format = thread_segment_format_string (matchnum);
|
||||
|
||||
mu_container_path_foreach (root_set,
|
||||
(MuContainerPathForeachFunc)add_thread_info,
|
||||
&ti);
|
||||
|
||||
return ti.hash;
|
||||
}
|
||||
197
lib/mu-container.h
Normal file
197
lib/mu-container.h
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_CONTAINER_H__
|
||||
#define __MU_CONTAINER_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <mu-msg.h>
|
||||
|
||||
enum _MuContainerFlag {
|
||||
MU_CONTAINER_FLAG_NONE = 0,
|
||||
MU_CONTAINER_FLAG_DELETE = 1 << 0,
|
||||
MU_CONTAINER_FLAG_SPLICE = 1 << 1,
|
||||
MU_CONTAINER_FLAG_DUP = 1 << 2
|
||||
};
|
||||
|
||||
typedef guint8 MuContainerFlag;
|
||||
|
||||
/*
|
||||
* MuContainer data structure, as seen in JWZs document:
|
||||
* http://www.jwz.org/doc/threading.html
|
||||
*/
|
||||
struct _MuContainer {
|
||||
struct _MuContainer *parent, *child, *next;
|
||||
MuContainerFlag flags;
|
||||
MuMsg *msg;
|
||||
guint docid;
|
||||
const char* msgid;
|
||||
};
|
||||
typedef struct _MuContainer MuContainer;
|
||||
|
||||
|
||||
/**
|
||||
* create a new Container object
|
||||
*
|
||||
* @param msg a MuMsg, or NULL; when it's NULL, docid should be 0
|
||||
* @param docid a Xapian docid, or 0
|
||||
* @param msgid a message id, or NULL
|
||||
*
|
||||
* @return a new Container instance, or NULL in case of error; free
|
||||
* with mu_container_destroy
|
||||
*/
|
||||
MuContainer* mu_container_new (MuMsg *msg, guint docid, const char* msgid);
|
||||
|
||||
|
||||
/**
|
||||
* free a Container object
|
||||
*
|
||||
* @param c a Container object, or NULL
|
||||
*/
|
||||
void mu_container_destroy (MuContainer *c);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* append new child(ren) to this container; the child(ren) container's
|
||||
* parent pointer will point to this one
|
||||
*
|
||||
* @param c a Container instance
|
||||
* @param child a child
|
||||
*
|
||||
* @return the Container instance with a child added
|
||||
*/
|
||||
MuContainer* mu_container_append_children (MuContainer *c, MuContainer *child);
|
||||
|
||||
/**
|
||||
* append a new sibling to this (list of) containers; all the siblings
|
||||
* will get the same parent that @c has
|
||||
*
|
||||
* @param c a container instance
|
||||
* @param sibling a sibling
|
||||
*
|
||||
* @return the container (list) with the sibling(s) appended
|
||||
*/
|
||||
MuContainer* mu_container_append_siblings (MuContainer *c, MuContainer *sibling);
|
||||
|
||||
/**
|
||||
* remove a _single_ child container from a container
|
||||
*
|
||||
* @param c a container instance
|
||||
* @param child the child container to remove
|
||||
*
|
||||
* @return the container with the child removed; if the container did
|
||||
* have this child, nothing changes
|
||||
*/
|
||||
MuContainer* mu_container_remove_child (MuContainer *c, MuContainer *child);
|
||||
|
||||
/**
|
||||
* remove a _single_ sibling container from a container
|
||||
*
|
||||
* @param c a container instance
|
||||
* @param sibling the sibling container to remove
|
||||
*
|
||||
* @return the container with the sibling removed; if the container did
|
||||
* have this sibling, nothing changes
|
||||
*/
|
||||
MuContainer* mu_container_remove_sibling (MuContainer *c, MuContainer *sibling);
|
||||
|
||||
|
||||
/**
|
||||
* promote child's children to be parent's children and remove child
|
||||
*
|
||||
* @param parent a container instance
|
||||
* @param child a child of this container
|
||||
*
|
||||
* @return the new container with it's children's children promoted
|
||||
*/
|
||||
MuContainer* mu_container_splice_children (MuContainer *parent,
|
||||
MuContainer *child);
|
||||
|
||||
typedef gboolean (*MuContainerForeachFunc) (MuContainer*, gpointer);
|
||||
|
||||
/**
|
||||
* execute some function on all siblings an children of some container
|
||||
* (recursively) until all children have been visited or the callback
|
||||
* function returns FALSE
|
||||
*
|
||||
* @param c a container
|
||||
* @param func a function to call for each container
|
||||
* @param user_data a pointer to pass to the callback function
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
gboolean mu_container_foreach (MuContainer *c,
|
||||
MuContainerForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* check wither container needle is a child or sibling (recursively)
|
||||
* of container haystack
|
||||
*
|
||||
* @param haystack a container
|
||||
* @param needle a container
|
||||
*
|
||||
* @return TRUE if needle is reachable from haystack, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_container_reachable (MuContainer *haystack, MuContainer *needle);
|
||||
|
||||
|
||||
/**
|
||||
* dump the container to stdout (for debugging)
|
||||
*
|
||||
* @param c a container
|
||||
* @param recursive whether to include siblings, children
|
||||
*/
|
||||
void mu_container_dump (MuContainer *c, gboolean recursive);
|
||||
|
||||
|
||||
typedef int (*MuContainerCmpFunc) (MuContainer *c1, MuContainer *c2,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* sort the tree of MuContainers, recursively; ie. each of the list of
|
||||
* siblings (children) will be sorted according to @func; if the
|
||||
* container is empty, the first non-empty 'leftmost' child is used.
|
||||
*
|
||||
* @param c a container
|
||||
* @param mfid the field to sort by
|
||||
* @param revert if TRUE, revert the sorting order *
|
||||
* @param user_data a user pointer to pass to the sorting function
|
||||
*
|
||||
* @return a sorted container
|
||||
*/
|
||||
MuContainer* mu_container_sort (MuContainer *c, MuMsgFieldId mfid, gboolean revert,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
/**
|
||||
* create a hashtable with maps document-ids to information about them,
|
||||
* ie. Xapian docid => MuMsgIterThreadInfo
|
||||
*
|
||||
* @param root_set the containers @param matchnum the number of
|
||||
* matches in the list (this is needed to determine the shortest
|
||||
* possible collation keys ('threadpaths') for the messages
|
||||
*
|
||||
* @return a hash; free with g_hash_table_destroy
|
||||
*/
|
||||
GHashTable* mu_container_thread_info_hash_new (MuContainer *root_set,
|
||||
size_t matchnum);
|
||||
|
||||
#endif /*__MU_CONTAINER_H__*/
|
||||
264
lib/mu-date.c
Normal file
264
lib/mu-date.c
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
** Copyright (C) 2012 <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mu-util.h"
|
||||
#include "mu-date.h"
|
||||
|
||||
|
||||
const char*
|
||||
mu_date_str_s (const char* frm, time_t t)
|
||||
{
|
||||
struct tm *tmbuf;
|
||||
static char buf[128];
|
||||
static int is_utf8 = -1;
|
||||
|
||||
if (G_UNLIKELY(is_utf8 == -1))
|
||||
is_utf8 = mu_util_locale_is_utf8 () ? 1 : 0;
|
||||
|
||||
g_return_val_if_fail (frm, NULL);
|
||||
|
||||
tmbuf = localtime(&t);
|
||||
strftime (buf, sizeof(buf), frm, tmbuf);
|
||||
|
||||
if (!is_utf8) {
|
||||
/* charset is _not_ utf8, so we need to convert it, so
|
||||
* the date could contain locale-specific characters*/
|
||||
gchar *conv;
|
||||
GError *err;
|
||||
err = NULL;
|
||||
conv = g_locale_to_utf8 (buf, -1, NULL, NULL, &err);
|
||||
if (err) {
|
||||
g_warning ("conversion failed: %s", err->message);
|
||||
g_error_free (err);
|
||||
strcpy (buf, "<error>");
|
||||
} else
|
||||
strncpy (buf, conv, sizeof(buf));
|
||||
|
||||
g_free (conv);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
char*
|
||||
mu_date_str (const char *frm, time_t t)
|
||||
{
|
||||
return g_strdup (mu_date_str_s(frm, t));
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_date_display_s (time_t t)
|
||||
{
|
||||
time_t now;
|
||||
static const time_t SECS_IN_DAY = 24 * 60 * 60;
|
||||
|
||||
now = time (NULL);
|
||||
|
||||
if (ABS(now - t) > SECS_IN_DAY)
|
||||
return mu_date_str_s ("%x", t);
|
||||
else
|
||||
return mu_date_str_s ("%X", t);
|
||||
}
|
||||
|
||||
|
||||
|
||||
time_t
|
||||
mu_date_parse_hdwmy (const char *nptr)
|
||||
{
|
||||
long int num;
|
||||
char *endptr;
|
||||
time_t now, delta;
|
||||
time_t never = (time_t)-1;
|
||||
|
||||
g_return_val_if_fail (nptr, never);
|
||||
|
||||
num = strtol (nptr, &endptr, 10);
|
||||
if (num <= 0 || num > 9999)
|
||||
return never;
|
||||
|
||||
if (endptr == NULL || *endptr == '\0')
|
||||
return never;
|
||||
|
||||
switch (endptr[0]) {
|
||||
case 'h': /* hour */
|
||||
delta = num * 60 * 60; break;
|
||||
case 'd': /* day */
|
||||
delta = num * 24 * 60 * 60; break;
|
||||
case 'w': /* week */
|
||||
delta = num * 7 * 24 * 60 * 60; break;
|
||||
case 'm': /* month */
|
||||
delta = num * 30 * 24 * 60 * 60; break;
|
||||
case 'y': /* year */
|
||||
delta = num * 365 * 24 * 60 * 60; break;
|
||||
default:
|
||||
return never;
|
||||
}
|
||||
|
||||
now = time(NULL);
|
||||
return delta <= now ? now - delta : never;
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_date_complete_s (const char *date, gboolean is_begin)
|
||||
{
|
||||
static char fulldate[14 + 1];
|
||||
|
||||
static const char* full_begin = "00000101000000";
|
||||
static const char* full_end = "99991231235959";
|
||||
|
||||
g_return_val_if_fail (date, NULL);
|
||||
|
||||
strncpy (fulldate, is_begin ? full_begin : full_end,
|
||||
sizeof(fulldate));
|
||||
memcpy (fulldate, date, strlen(date));
|
||||
|
||||
return fulldate;
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_date_complete (const char *date, gboolean is_begin)
|
||||
{
|
||||
const char *s;
|
||||
|
||||
g_return_val_if_fail (date, NULL);
|
||||
|
||||
s = mu_date_complete_s (date, is_begin);
|
||||
return s ? g_strdup (s) : NULL;
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_date_interpret_s (const char *datespec, gboolean is_begin)
|
||||
{
|
||||
static char fulldate[14 + 1];
|
||||
time_t now;
|
||||
|
||||
g_return_val_if_fail (datespec, NULL);
|
||||
|
||||
now = time(NULL);
|
||||
if (strcmp (datespec, "today") == 0) {
|
||||
strftime(fulldate, sizeof(fulldate),
|
||||
is_begin ? "%Y%m%d000000" : "%Y%m%d235959",
|
||||
localtime(&now));
|
||||
return fulldate;
|
||||
}
|
||||
|
||||
if (strcmp (datespec, "now") == 0) {
|
||||
strftime(fulldate, sizeof(fulldate), "%Y%m%d%H%M%S",
|
||||
localtime(&now));
|
||||
return fulldate;
|
||||
}
|
||||
|
||||
{
|
||||
time_t t;
|
||||
t = mu_date_parse_hdwmy (datespec);
|
||||
if (t != (time_t)-1) {
|
||||
strftime(fulldate, sizeof(fulldate), "%Y%m%d%H%M%S",
|
||||
localtime(&t));
|
||||
return fulldate;
|
||||
}
|
||||
}
|
||||
|
||||
return datespec; /* nothing changed */
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_date_interpret (const char *datespec, gboolean is_begin)
|
||||
{
|
||||
char *s;
|
||||
|
||||
g_return_val_if_fail (datespec, NULL);
|
||||
|
||||
s = mu_date_interpret (datespec, is_begin);
|
||||
return s ? g_strdup(s) : NULL;
|
||||
}
|
||||
|
||||
|
||||
time_t
|
||||
mu_date_str_to_time_t (const char* date, gboolean local)
|
||||
{
|
||||
struct tm tm;
|
||||
char mydate[14 + 1]; /* YYYYMMDDHHMMSS */
|
||||
time_t t;
|
||||
const char *tz;
|
||||
|
||||
memset (&tm, 0, sizeof(struct tm));
|
||||
strncpy (mydate, date, 15);
|
||||
mydate[sizeof(mydate)-1]='\0';
|
||||
|
||||
g_return_val_if_fail (date, (time_t)-1);
|
||||
|
||||
tm.tm_sec = atoi (mydate + 12); mydate[12] = '\0';
|
||||
tm.tm_min = atoi (mydate + 10); mydate[10] = '\0';
|
||||
tm.tm_hour = atoi (mydate + 8); mydate[8] = '\0';
|
||||
tm.tm_mday = atoi (mydate + 6); mydate[6] = '\0';
|
||||
tm.tm_mon = atoi (mydate + 4) - 1; mydate[4] = '\0';
|
||||
tm.tm_year = atoi (mydate) - 1900;
|
||||
tm.tm_isdst = -1; /* figure out the dst */
|
||||
|
||||
if (!local) { /* temporarily switch to UTC */
|
||||
tz = getenv ("TZ");
|
||||
setenv ("TZ", "", 1);
|
||||
tzset ();
|
||||
}
|
||||
|
||||
t = mktime (&tm);
|
||||
|
||||
if (!local) { /* switch back */
|
||||
if (tz)
|
||||
setenv("TZ", tz, 1);
|
||||
else
|
||||
unsetenv("TZ");
|
||||
tzset();
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
const char*
|
||||
mu_date_time_t_to_str_s (time_t t, gboolean local)
|
||||
{
|
||||
/* static char datestr[14 + 1]; /\* YYYYMMDDHHMMSS *\/ */
|
||||
static char datestr[14+1]; /* YYYYMMDDHHMMSS */
|
||||
|
||||
static const char *frm = "%Y%m%d%H%M%S";
|
||||
|
||||
strftime (datestr, sizeof(datestr), frm,
|
||||
local ? localtime (&t) : gmtime(&t));
|
||||
|
||||
return datestr;
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_date_time_t_to_str (time_t t, gboolean local)
|
||||
{
|
||||
const char* str;
|
||||
|
||||
str = mu_date_time_t_to_str_s (t, local);
|
||||
|
||||
return str ? g_strdup(str): NULL;
|
||||
}
|
||||
147
lib/mu-date.h
Normal file
147
lib/mu-date.h
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#ifndef __MU_DATE_H__
|
||||
#define __MU_DATE_H__
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
/**
|
||||
* get a string for a given time_t
|
||||
*
|
||||
* mu_date_str_s returns a ptr to a static buffer,
|
||||
* while mu_date_str returns dynamically allocated
|
||||
* memory that must be freed after use.
|
||||
*
|
||||
* @param frm the format of the string (in strftime(3) format)
|
||||
* @param t the time as time_t
|
||||
*
|
||||
* @return a string representation of the time; see above for what to
|
||||
* do with it. Lenght is max. 128 bytes, inc. the ending \0. if the
|
||||
* format is too long, the value will be truncated. in practice this
|
||||
* should not happen.
|
||||
*/
|
||||
const char* mu_date_str_s (const char* frm, time_t t) G_GNUC_CONST;
|
||||
char* mu_date_str (const char* frm, time_t t) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get a display string for a given time_t; if the given is less than
|
||||
* 24h from the current time, we display the time, otherwise the date,
|
||||
* using the preferred date/time for the current locale
|
||||
*
|
||||
* mu_str_display_date_s returns a ptr to a static buffer,
|
||||
*
|
||||
* @param t the time as time_t
|
||||
*
|
||||
* @return a string representation of the time/date
|
||||
*/
|
||||
const char* mu_date_display_s (time_t t);
|
||||
|
||||
/**
|
||||
*
|
||||
* parse strings like 1h, 3w, 2m to mean '1 hour before now', '3 weeks
|
||||
* before now' and '2 * 30 days before now'
|
||||
*
|
||||
* the format is <n>(h|d|w|m|y), where <n> is an integer > 0, and
|
||||
* h=hour, d=day, w=week, m=30 days, year=365 days. function returns
|
||||
* *now* minus this value as time_t (UTC)
|
||||
*
|
||||
* if the number cannot be parsed, return (time_t)-1
|
||||
*
|
||||
* @param str a str
|
||||
*
|
||||
* @return the time_t of the point in time indicated by 'now' minus
|
||||
* the value, or (time_t)-1 otherwise
|
||||
*/
|
||||
time_t mu_date_parse_hdwmy (const char* str);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* complete a date (a string of the form YYYYMMDDHHMMSS with [0..14]
|
||||
* of the rightmost characters missing) to the the full YYYYMMDDHHMMSS.
|
||||
*
|
||||
* if is_begin is TRUE, add to the 'floor' (e.g,
|
||||
* 20110101=>20110101000000), otherwise go to the 'ceiling',
|
||||
* e.g. 2009=>20091231235050)
|
||||
*
|
||||
* @param date a date string (assumed to have the beginning of the
|
||||
* date, this is not checked
|
||||
* @param is_begin if TRUE go to floor (as described), otherwise to
|
||||
* the ceiling
|
||||
*
|
||||
* @return mu_date_complete: return a newly allocated string (free
|
||||
* with g_free) with the full, 14-char date; mu_date_complete_s:
|
||||
* return a statically allocated string. NOT REENTRANT.
|
||||
*/
|
||||
char* mu_date_complete (const char *date, gboolean is_begin);
|
||||
const char* mu_date_complete_s (const char *date, gboolean is_begin);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param datespec
|
||||
* @param is_begin
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
const char* mu_date_interpret_s (const char *datespec, gboolean is_begin);
|
||||
char* mu_date_interpret (const char *datespec, gboolean is_begin);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* convert a date of the form 'YYYYMMDDHHMMSS' into time_t
|
||||
*
|
||||
* @param date a date str of the form 'YYYYMMDDHHMMSS'
|
||||
* @param local if TRUE, source is assumed to bin in local time, UTC otherwise
|
||||
*
|
||||
* @return the corresponding time_t, or (time_t)-1 in case of error
|
||||
*/
|
||||
time_t mu_date_str_to_time_t (const char* date, gboolean local);
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* convert a time_t value into a date string of the form
|
||||
* 'YYYYMMDDHHMMSS'; assume UTC
|
||||
*
|
||||
* @param t a time_t value
|
||||
* @param local if TRUE, convert to local time, otherwise use UTC
|
||||
*
|
||||
* @return mu_date_time_t_to_str_s: a static string (don't modify,
|
||||
* non-reentrant) of the form 'YYYYMMDDHHMMSS'; mu_date_time_t_to_str:
|
||||
* return a newly allocated string with the same.
|
||||
*/
|
||||
const char* mu_date_time_t_to_str_s (time_t t, gboolean local);
|
||||
char* mu_date_time_t_to_str (time_t t, gboolean local);
|
||||
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_DATE_H__*/
|
||||
230
lib/mu-flags.c
Normal file
230
lib/mu-flags.c
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
** Copyright (C) 2011 <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
#include "mu-flags.h"
|
||||
|
||||
struct _FlagInfo {
|
||||
MuFlags flag;
|
||||
char kar;
|
||||
const char *name;
|
||||
MuFlagType flag_type;
|
||||
};
|
||||
typedef struct _FlagInfo FlagInfo;
|
||||
|
||||
static const FlagInfo FLAG_INFO[] = {
|
||||
|
||||
/* NOTE: order of this is significant, due to optimizations
|
||||
* below */
|
||||
|
||||
{ MU_FLAG_DRAFT, 'D', "draft", MU_FLAG_TYPE_MAILFILE },
|
||||
{ MU_FLAG_FLAGGED, 'F', "flagged", MU_FLAG_TYPE_MAILFILE },
|
||||
{ MU_FLAG_PASSED, 'P', "passed", MU_FLAG_TYPE_MAILFILE },
|
||||
{ MU_FLAG_REPLIED, 'R', "replied", MU_FLAG_TYPE_MAILFILE },
|
||||
{ MU_FLAG_SEEN, 'S', "seen", MU_FLAG_TYPE_MAILFILE },
|
||||
{ MU_FLAG_TRASHED, 'T', "trashed", MU_FLAG_TYPE_MAILFILE },
|
||||
|
||||
{ MU_FLAG_NEW, 'N', "new", MU_FLAG_TYPE_MAILDIR },
|
||||
|
||||
{ MU_FLAG_SIGNED, 'z', "signed", MU_FLAG_TYPE_CONTENT },
|
||||
{ MU_FLAG_ENCRYPTED, 'x', "encrypted", MU_FLAG_TYPE_CONTENT },
|
||||
{ MU_FLAG_HAS_ATTACH, 'a', "attach", MU_FLAG_TYPE_CONTENT },
|
||||
|
||||
{ MU_FLAG_UNREAD, 'u', "unread", MU_FLAG_TYPE_PSEUDO }
|
||||
};
|
||||
|
||||
/* does not use FLAG_INFO, optimized */
|
||||
MuFlagType
|
||||
mu_flag_type (MuFlags flag)
|
||||
{
|
||||
if (flag >= MU_FLAG_DRAFT && flag <= MU_FLAG_TRASHED)
|
||||
return MU_FLAG_TYPE_MAILFILE;
|
||||
if (flag == MU_FLAG_NEW)
|
||||
return MU_FLAG_TYPE_MAILDIR;
|
||||
if (flag == MU_FLAG_UNREAD)
|
||||
return MU_FLAG_TYPE_PSEUDO;
|
||||
if (flag >= MU_FLAG_SIGNED && flag <= MU_FLAG_HAS_ATTACH)
|
||||
return MU_FLAG_TYPE_CONTENT;
|
||||
|
||||
return MU_FLAG_TYPE_INVALID;
|
||||
}
|
||||
|
||||
|
||||
/* does not use FLAG_INFO, optimized */
|
||||
char
|
||||
mu_flag_char (MuFlags flag)
|
||||
{
|
||||
switch (flag) {
|
||||
|
||||
case MU_FLAG_DRAFT: return 'D';
|
||||
case MU_FLAG_FLAGGED: return 'F';
|
||||
case MU_FLAG_PASSED: return 'P';
|
||||
case MU_FLAG_REPLIED: return 'R';
|
||||
case MU_FLAG_SEEN: return 'S';
|
||||
case MU_FLAG_TRASHED: return 'T';
|
||||
|
||||
case MU_FLAG_NEW: return 'N';
|
||||
|
||||
case MU_FLAG_SIGNED: return 'z';
|
||||
case MU_FLAG_ENCRYPTED: return 'x';
|
||||
case MU_FLAG_HAS_ATTACH: return 'a';
|
||||
|
||||
case MU_FLAG_UNREAD: return 'u';
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static MuFlags
|
||||
mu_flag_from_char (char kar)
|
||||
{
|
||||
switch (kar) {
|
||||
|
||||
case 'D': return MU_FLAG_DRAFT;
|
||||
case 'F': return MU_FLAG_FLAGGED;
|
||||
case 'P': return MU_FLAG_PASSED;
|
||||
case 'R': return MU_FLAG_REPLIED;
|
||||
case 'S': return MU_FLAG_SEEN;
|
||||
case 'T': return MU_FLAG_TRASHED;
|
||||
|
||||
case 'N': return MU_FLAG_NEW;
|
||||
|
||||
case 'z': return MU_FLAG_SIGNED;
|
||||
case 'x': return MU_FLAG_ENCRYPTED;
|
||||
case 'a': return MU_FLAG_HAS_ATTACH;
|
||||
|
||||
case 'u': return MU_FLAG_UNREAD;
|
||||
|
||||
default:
|
||||
return MU_FLAG_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* does not use FLAG_INFO, optimized */
|
||||
const char*
|
||||
mu_flag_name (MuFlags flag)
|
||||
{
|
||||
switch (flag) {
|
||||
case MU_FLAG_DRAFT: return "draft";
|
||||
case MU_FLAG_FLAGGED: return "flagged";
|
||||
case MU_FLAG_PASSED: return "passed";
|
||||
case MU_FLAG_REPLIED: return "replied";
|
||||
case MU_FLAG_SEEN: return "seen";
|
||||
case MU_FLAG_TRASHED: return "trashed";
|
||||
|
||||
case MU_FLAG_NEW: return "new";
|
||||
|
||||
case MU_FLAG_SIGNED: return "signed";
|
||||
case MU_FLAG_ENCRYPTED: return "encrypted";
|
||||
case MU_FLAG_HAS_ATTACH: return "attach";
|
||||
|
||||
case MU_FLAG_UNREAD: return "unread";
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_flags_to_str_s (MuFlags flags, MuFlagType types)
|
||||
{
|
||||
unsigned u,v;
|
||||
static char str[sizeof(FLAG_INFO) + 1];
|
||||
|
||||
for (u = 0, v = 0; u != G_N_ELEMENTS(FLAG_INFO); ++u)
|
||||
if (flags & FLAG_INFO[u].flag &&
|
||||
types & FLAG_INFO[u].flag_type)
|
||||
str[v++] = FLAG_INFO[u].kar;
|
||||
str[v] = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
MuFlags
|
||||
mu_flags_from_str (const char *str, MuFlagType types)
|
||||
{
|
||||
const char *cur;
|
||||
MuFlags flag;
|
||||
|
||||
g_return_val_if_fail (str, MU_FLAG_INVALID);
|
||||
|
||||
for (cur = str, flag = MU_FLAG_NONE; *cur; ++cur) {
|
||||
|
||||
MuFlags f;
|
||||
|
||||
f = mu_flag_from_char (*cur);
|
||||
if (mu_flag_type (f) & types)
|
||||
flag |= f;
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
mu_flags_foreach (MuFlagsForeachFunc func, gpointer user_data)
|
||||
{
|
||||
unsigned u;
|
||||
|
||||
g_return_if_fail (func);
|
||||
|
||||
for (u = 0; u != G_N_ELEMENTS(FLAG_INFO); ++u)
|
||||
func (FLAG_INFO[u].flag, user_data);
|
||||
}
|
||||
|
||||
|
||||
MuFlags
|
||||
mu_flags_from_str_delta (const char *str, MuFlags oldflags,
|
||||
MuFlagType types)
|
||||
{
|
||||
const char *cur;
|
||||
MuFlags newflags;
|
||||
|
||||
g_return_val_if_fail (str, MU_FLAG_INVALID);
|
||||
|
||||
for (cur = str, newflags = oldflags; *cur; ++cur) {
|
||||
|
||||
MuFlags f;
|
||||
if (*cur == '+' || *cur == '-') {
|
||||
f = mu_flag_from_char (cur[1]);
|
||||
if (f == 0)
|
||||
goto error;
|
||||
if (*cur == '+')
|
||||
newflags |= f;
|
||||
else
|
||||
newflags &= ~f;
|
||||
++cur;
|
||||
continue;
|
||||
}
|
||||
|
||||
goto error;
|
||||
}
|
||||
|
||||
return newflags;
|
||||
error:
|
||||
g_warning ("invalid flag string");
|
||||
return MU_FLAG_INVALID;
|
||||
|
||||
}
|
||||
157
lib/mu-flags.h
Normal file
157
lib/mu-flags.h
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __MU_FLAGS_H__
|
||||
#define __MU_FLAGS_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
enum _MuFlags {
|
||||
MU_FLAG_NONE = 0,
|
||||
|
||||
/* next 6 are seen in the file-info part of maildir message
|
||||
* file names, ie., in a name like "1234345346:2,<fileinfo>",
|
||||
* <fileinfo> consists of zero or more of the following
|
||||
* characters (in ascii order) */
|
||||
MU_FLAG_DRAFT = 1 << 0,
|
||||
MU_FLAG_FLAGGED = 1 << 1,
|
||||
MU_FLAG_PASSED = 1 << 2,
|
||||
MU_FLAG_REPLIED = 1 << 3,
|
||||
MU_FLAG_SEEN = 1 << 4,
|
||||
MU_FLAG_TRASHED = 1 << 5,
|
||||
|
||||
/* decides on cur/ or new/ in the maildir */
|
||||
MU_FLAG_NEW = 1 << 6,
|
||||
|
||||
/* content flags -- not visible in the filename, but used for
|
||||
* searching */
|
||||
MU_FLAG_SIGNED = 1 << 7,
|
||||
MU_FLAG_ENCRYPTED = 1 << 8,
|
||||
MU_FLAG_HAS_ATTACH = 1 << 9,
|
||||
|
||||
/* pseudo-flag, only for queries, so we can search for
|
||||
* flag:unread, which is equivalent to 'flag:new OR NOT
|
||||
* flag:seen' */
|
||||
MU_FLAG_UNREAD = 1 << 10
|
||||
};
|
||||
typedef enum _MuFlags MuFlags;
|
||||
|
||||
#define MU_FLAG_INVALID ((MuFlags)-1)
|
||||
|
||||
enum _MuFlagType {
|
||||
MU_FLAG_TYPE_MAILFILE = 1 << 0,
|
||||
MU_FLAG_TYPE_MAILDIR = 1 << 1,
|
||||
MU_FLAG_TYPE_CONTENT = 1 << 2,
|
||||
MU_FLAG_TYPE_PSEUDO = 1 << 3
|
||||
};
|
||||
typedef enum _MuFlagType MuFlagType;
|
||||
|
||||
#define MU_FLAG_TYPE_ANY ((MuFlagType)-1)
|
||||
#define MU_FLAG_TYPE_INVALID ((MuFlagType)-1)
|
||||
|
||||
|
||||
/**
|
||||
* Get the type of flag (mailfile, maildir, pseudo or content)
|
||||
*
|
||||
* @param flag a MuFlag
|
||||
*
|
||||
* @return the flag type or MU_FLAG_TYPE_INVALID in case of error
|
||||
*/
|
||||
MuFlagType mu_flag_type (MuFlags flag) G_GNUC_CONST;
|
||||
|
||||
|
||||
/**
|
||||
* Get the flag character
|
||||
*
|
||||
* @param flag a MuFlag (single)
|
||||
*
|
||||
* @return the character, or 0 in case of error
|
||||
*/
|
||||
char mu_flag_char (MuFlags flag) G_GNUC_CONST;
|
||||
|
||||
|
||||
/**
|
||||
* Get the flag name
|
||||
*
|
||||
* @param flag a single MuFlag
|
||||
*
|
||||
* @return the name (don't free) as string or NULL in case of error
|
||||
*/
|
||||
const char* mu_flag_name (MuFlags flag) G_GNUC_CONST;
|
||||
|
||||
|
||||
/**
|
||||
* Get the string representation of an OR'ed set of flags
|
||||
*
|
||||
* @param flags MuFlag (OR'ed)
|
||||
* @param types allowable types (OR'ed) for the result; the rest is ignored
|
||||
*
|
||||
* @return The string representation (static, don't free), or NULL in
|
||||
* case of error
|
||||
*/
|
||||
const char* mu_flags_to_str_s (MuFlags flags, MuFlagType types);
|
||||
|
||||
|
||||
/**
|
||||
* Get the (OR'ed) flags corresponding to a string representation
|
||||
*
|
||||
* @param str the string representation
|
||||
* @param types the flag types to acceps (other will be ignored)
|
||||
*
|
||||
* @return the (OR'ed) flags
|
||||
*/
|
||||
MuFlags mu_flags_from_str (const char *str, MuFlagType types);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Update #oldflags with the flags in #str, where #str consists of the
|
||||
* the normal flag characters, but prefixed with either '+' or '-',
|
||||
* which means resp. "add this flag" or "remove this flag" from
|
||||
* oldflags. So, e.g. "-N+S" would unset the NEW flag and set the
|
||||
* SEEN flag, without affecting other flags.
|
||||
*
|
||||
* @param str the string representation
|
||||
* @param old flags to update
|
||||
* @param types the flag types to accept (other will be ignored)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
MuFlags mu_flags_from_str_delta (const char *str, MuFlags oldflags,
|
||||
MuFlagType types);
|
||||
|
||||
|
||||
|
||||
|
||||
typedef void (*MuFlagsForeachFunc) (MuFlags flag, gpointer user_data);
|
||||
|
||||
/**
|
||||
* call a function for each available flag
|
||||
*
|
||||
* @param func a function to call
|
||||
* @param user_data a user pointer to pass to the function
|
||||
*/
|
||||
void mu_flags_foreach (MuFlagsForeachFunc func, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_FLAGS_H__*/
|
||||
463
lib/mu-index.c
Normal file
463
lib/mu-index.c
Normal file
@ -0,0 +1,463 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
1** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include "mu-index.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "mu-maildir.h"
|
||||
#include "mu-store.h"
|
||||
#include "mu-util.h"
|
||||
|
||||
#define MU_LAST_USED_MAILDIR_KEY "last_used_maildir"
|
||||
#define MU_INDEX_MAX_FILE_SIZE (50*1000*1000) /* 50 Mb */
|
||||
|
||||
struct _MuIndex {
|
||||
MuStore *_store;
|
||||
gboolean _needs_reindex;
|
||||
guint _max_filesize;
|
||||
};
|
||||
|
||||
MuIndex*
|
||||
mu_index_new (MuStore *store, GError **err)
|
||||
{
|
||||
MuIndex *index;
|
||||
unsigned count;
|
||||
|
||||
g_return_val_if_fail (store, NULL);
|
||||
g_return_val_if_fail (!mu_store_is_read_only(store), NULL);
|
||||
|
||||
index = g_new0 (MuIndex, 1);
|
||||
|
||||
index->_store = mu_store_ref (store);
|
||||
|
||||
/* set the default max file size */
|
||||
index->_max_filesize = MU_INDEX_MAX_FILE_SIZE;
|
||||
|
||||
count = mu_store_count (store, err);
|
||||
if (count == (unsigned)-1)
|
||||
return NULL;
|
||||
else if (count == 0)
|
||||
index->_needs_reindex = FALSE;
|
||||
|
||||
/* FIXME */
|
||||
/* else */
|
||||
/* index->_needs_reindex = */
|
||||
/* mu_store_database_needs_upgrade (xpath); */
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void
|
||||
mu_index_destroy (MuIndex *index)
|
||||
{
|
||||
if (!index)
|
||||
return;
|
||||
|
||||
mu_store_unref (index->_store);
|
||||
g_free (index);
|
||||
}
|
||||
|
||||
|
||||
struct _MuIndexCallbackData {
|
||||
MuIndexMsgCallback _idx_msg_cb;
|
||||
MuIndexDirCallback _idx_dir_cb;
|
||||
MuStore* _store;
|
||||
void* _user_data;
|
||||
MuIndexStats* _stats;
|
||||
gboolean _reindex;
|
||||
time_t _dirstamp;
|
||||
guint _max_filesize;
|
||||
};
|
||||
typedef struct _MuIndexCallbackData MuIndexCallbackData;
|
||||
|
||||
|
||||
/* checks to determine if we need to (re)index this message note:
|
||||
* simply checking timestamps is not good enough because message may
|
||||
* be moved from other dirs (e.g. from 'new' to 'cur') and the time
|
||||
* stamps won't change. */
|
||||
static inline gboolean
|
||||
needs_index (MuIndexCallbackData *data, const char *fullpath,
|
||||
time_t filestamp)
|
||||
{
|
||||
/* unconditionally reindex */
|
||||
if (data->_reindex)
|
||||
return TRUE;
|
||||
|
||||
/* it's not in the database yet (FIXME: GError)*/
|
||||
if (!mu_store_contains_message (data->_store, fullpath, NULL))
|
||||
return TRUE;
|
||||
|
||||
/* it's there, but it's not up to date */
|
||||
if ((unsigned)filestamp >= (unsigned)data->_dirstamp)
|
||||
return TRUE;
|
||||
|
||||
return FALSE; /* index not needed */
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
insert_or_update_maybe (const char* fullpath, const char* mdir,
|
||||
time_t filestamp, MuIndexCallbackData *data,
|
||||
gboolean *updated)
|
||||
{
|
||||
MuMsg *msg;
|
||||
GError *err;
|
||||
gboolean rv;
|
||||
|
||||
*updated = FALSE;
|
||||
if (!needs_index (data, fullpath, filestamp))
|
||||
return MU_OK; /* nothing to do for this one */
|
||||
|
||||
err = NULL;
|
||||
msg = mu_msg_new_from_file (fullpath, mdir, &err);
|
||||
if (!msg) {
|
||||
g_warning ("error creating message object: %s",
|
||||
err ? err->message : "cause unknown");
|
||||
/* warn, then simply continue */
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
/* we got a valid id; scan the message contents as well */
|
||||
rv = mu_store_add_msg (data->_store, msg, &err);
|
||||
mu_msg_unref (msg);
|
||||
|
||||
if (!rv) {
|
||||
g_warning ("error storing message object: %s",
|
||||
err ? err->message : "cause unknown");
|
||||
g_clear_error (&err);
|
||||
return MU_ERROR;
|
||||
}
|
||||
|
||||
*updated = TRUE;
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
run_msg_callback_maybe (MuIndexCallbackData *data)
|
||||
{
|
||||
MuError result;
|
||||
|
||||
if (!data || !data->_idx_msg_cb)
|
||||
return MU_OK;
|
||||
|
||||
result = data->_idx_msg_cb (data->_stats, data->_user_data);
|
||||
if (G_UNLIKELY(result != MU_OK && result != MU_STOP))
|
||||
g_warning ("error in callback");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
on_run_maildir_msg (const char* fullpath, const char* mdir,
|
||||
struct stat *statbuf, MuIndexCallbackData *data)
|
||||
{
|
||||
MuError result;
|
||||
gboolean updated;
|
||||
|
||||
/* protect against too big messages */
|
||||
if (G_UNLIKELY(statbuf->st_size > data->_max_filesize)) {
|
||||
g_warning ("ignoring because bigger than %u bytes: %s",
|
||||
data->_max_filesize, fullpath);
|
||||
return MU_OK; /* not an error */
|
||||
}
|
||||
|
||||
result = run_msg_callback_maybe (data);
|
||||
if (result != MU_OK)
|
||||
return result;
|
||||
|
||||
/* see if we need to update/insert anything...
|
||||
* use the ctime, so any status change will be visible (perms,
|
||||
* filename etc.)*/
|
||||
result = insert_or_update_maybe (fullpath, mdir, statbuf->st_ctime,
|
||||
data, &updated);
|
||||
|
||||
if (result == MU_OK && data && data->_stats) { /* update statistics */
|
||||
++data->_stats->_processed;
|
||||
updated ? ++data->_stats->_updated : ++data->_stats->_uptodate;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
on_run_maildir_dir (const char* fullpath, gboolean enter,
|
||||
MuIndexCallbackData *data)
|
||||
{
|
||||
GError *err;
|
||||
err = NULL;
|
||||
|
||||
/* xapian stores a per-dir timestamp; we use this timestamp
|
||||
* to determine whether a message is up-to-data
|
||||
*/
|
||||
if (enter) {
|
||||
data->_dirstamp =
|
||||
mu_store_get_timestamp (data->_store, fullpath, &err);
|
||||
g_debug ("entering %s (ts==%u)",
|
||||
fullpath, (unsigned)data->_dirstamp);
|
||||
} else {
|
||||
time_t now;
|
||||
now = time (NULL);
|
||||
|
||||
mu_store_set_timestamp (data->_store, fullpath,
|
||||
now, &err);
|
||||
g_debug ("leaving %s (ts=%u)",
|
||||
fullpath, (unsigned)data->_dirstamp);
|
||||
}
|
||||
|
||||
if (data->_idx_dir_cb)
|
||||
return data->_idx_dir_cb (fullpath, enter,
|
||||
data->_user_data);
|
||||
|
||||
if (err) {
|
||||
MU_WRITE_LOG ("%s: %s", __FUNCTION__, err->message);
|
||||
g_clear_error(&err);
|
||||
}
|
||||
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
check_path (const char* path)
|
||||
{
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
if (!g_path_is_absolute (path)) {
|
||||
g_warning ("%s: not an absolute path: %s",
|
||||
__FUNCTION__, path);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (access (path, R_OK) != 0) {
|
||||
g_warning ("%s: cannot open '%s': %s",
|
||||
__FUNCTION__, path, strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
init_cb_data (MuIndexCallbackData *cb_data, MuStore *xapian,
|
||||
gboolean reindex, guint max_filesize, MuIndexStats *stats,
|
||||
MuIndexMsgCallback msg_cb, MuIndexDirCallback dir_cb,
|
||||
void *user_data)
|
||||
{
|
||||
cb_data->_idx_msg_cb = msg_cb;
|
||||
cb_data->_idx_dir_cb = dir_cb;
|
||||
|
||||
cb_data->_user_data = user_data;
|
||||
cb_data->_store = xapian;
|
||||
|
||||
cb_data->_reindex = reindex;
|
||||
cb_data->_dirstamp = 0;
|
||||
cb_data->_max_filesize = max_filesize;
|
||||
|
||||
cb_data->_stats = stats;
|
||||
if (cb_data->_stats)
|
||||
memset (cb_data->_stats, 0, sizeof(MuIndexStats));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_index_set_max_msg_size (MuIndex *index, guint max_size)
|
||||
{
|
||||
g_return_if_fail (index);
|
||||
|
||||
if (max_size == 0)
|
||||
index->_max_filesize = MU_INDEX_MAX_FILE_SIZE;
|
||||
else
|
||||
index->_max_filesize = max_size;
|
||||
}
|
||||
|
||||
void
|
||||
mu_index_set_xbatch_size (MuIndex *index, guint xbatchsize)
|
||||
{
|
||||
g_return_if_fail (index);
|
||||
mu_store_set_batch_size (index->_store, xbatchsize);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MuError
|
||||
mu_index_run (MuIndex *index, const char* path,
|
||||
gboolean reindex, MuIndexStats *stats,
|
||||
MuIndexMsgCallback msg_cb, MuIndexDirCallback dir_cb,
|
||||
void *user_data)
|
||||
{
|
||||
MuIndexCallbackData cb_data;
|
||||
MuError rv;
|
||||
|
||||
g_return_val_if_fail (index && index->_store, MU_ERROR);
|
||||
g_return_val_if_fail (msg_cb, MU_ERROR);
|
||||
|
||||
if (!check_path (path))
|
||||
return MU_ERROR;
|
||||
|
||||
if (!reindex && index->_needs_reindex) {
|
||||
g_warning ("database not up-to-date; needs full reindex");
|
||||
return MU_ERROR;
|
||||
}
|
||||
|
||||
init_cb_data (&cb_data, index->_store, reindex,
|
||||
index->_max_filesize, stats,
|
||||
msg_cb, dir_cb, user_data);
|
||||
|
||||
rv = mu_maildir_walk (path,
|
||||
(MuMaildirWalkMsgCallback)on_run_maildir_msg,
|
||||
(MuMaildirWalkDirCallback)on_run_maildir_dir,
|
||||
&cb_data);
|
||||
|
||||
mu_store_flush (index->_store);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static MuError
|
||||
on_stats_maildir_file (const char *fullpath, const char* mdir,
|
||||
struct stat *statbuf,
|
||||
MuIndexCallbackData *cb_data)
|
||||
{
|
||||
MuError result;
|
||||
|
||||
if (cb_data && cb_data->_idx_msg_cb)
|
||||
result = cb_data->_idx_msg_cb (cb_data->_stats,
|
||||
cb_data->_user_data);
|
||||
else
|
||||
result = MU_OK;
|
||||
|
||||
if (result == MU_OK) {
|
||||
if (cb_data->_stats)
|
||||
++cb_data->_stats->_processed;
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
return result; /* MU_STOP or MU_OK */
|
||||
}
|
||||
|
||||
|
||||
MuError
|
||||
mu_index_stats (MuIndex *index, const char* path,
|
||||
MuIndexStats *stats, MuIndexMsgCallback cb_msg,
|
||||
MuIndexDirCallback cb_dir, void *user_data)
|
||||
{
|
||||
MuIndexCallbackData cb_data;
|
||||
|
||||
g_return_val_if_fail (index, MU_ERROR);
|
||||
g_return_val_if_fail (cb_msg, MU_ERROR);
|
||||
|
||||
if (!check_path (path))
|
||||
return MU_ERROR;
|
||||
|
||||
if (stats)
|
||||
memset (stats, 0, sizeof(MuIndexStats));
|
||||
|
||||
cb_data._idx_msg_cb = cb_msg;
|
||||
cb_data._idx_dir_cb = cb_dir;
|
||||
|
||||
cb_data._stats = stats;
|
||||
cb_data._user_data = user_data;
|
||||
|
||||
cb_data._dirstamp = 0;
|
||||
|
||||
return mu_maildir_walk (path,
|
||||
(MuMaildirWalkMsgCallback)on_stats_maildir_file,
|
||||
NULL,&cb_data);
|
||||
}
|
||||
|
||||
struct _CleanupData {
|
||||
MuStore *_store;
|
||||
MuIndexStats *_stats;
|
||||
MuIndexCleanupDeleteCallback _cb;
|
||||
void *_user_data;
|
||||
|
||||
};
|
||||
typedef struct _CleanupData CleanupData;
|
||||
|
||||
|
||||
static MuError
|
||||
foreach_doc_cb (const char* path, CleanupData *cudata)
|
||||
{
|
||||
if (access (path, R_OK) != 0) {
|
||||
if (errno != EACCES)
|
||||
g_debug ("cannot access %s: %s", path, strerror(errno));
|
||||
if (!mu_store_remove_path (cudata->_store, path))
|
||||
return MU_ERROR; /* something went wrong... bail out */
|
||||
if (cudata->_stats)
|
||||
++cudata->_stats->_cleaned_up;
|
||||
}
|
||||
|
||||
if (cudata->_stats)
|
||||
++cudata->_stats->_processed;
|
||||
|
||||
if (!cudata->_cb)
|
||||
return MU_OK;
|
||||
|
||||
return cudata->_cb (cudata->_stats, cudata->_user_data);
|
||||
}
|
||||
|
||||
|
||||
MuError
|
||||
mu_index_cleanup (MuIndex *index, MuIndexStats *stats,
|
||||
MuIndexCleanupDeleteCallback cb,
|
||||
void *user_data, GError **err)
|
||||
{
|
||||
MuError rv;
|
||||
CleanupData cudata;
|
||||
|
||||
g_return_val_if_fail (index, MU_ERROR);
|
||||
|
||||
cudata._store = index->_store;
|
||||
cudata._stats = stats;
|
||||
cudata._cb = cb;
|
||||
cudata._user_data = user_data;
|
||||
|
||||
rv = mu_store_foreach (index->_store,
|
||||
(MuStoreForeachFunc)foreach_doc_cb,
|
||||
&cudata, err);
|
||||
|
||||
mu_store_flush (index->_store);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_index_stats_clear (MuIndexStats *stats)
|
||||
{
|
||||
if (!stats)
|
||||
return FALSE;
|
||||
|
||||
memset (stats, 0, sizeof(MuIndexStats));
|
||||
return TRUE;
|
||||
}
|
||||
206
lib/mu-index.h
Normal file
206
lib/mu-index.h
Normal file
@ -0,0 +1,206 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_INDEX_H__
|
||||
#define __MU_INDEX_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <glib.h>
|
||||
#include <mu-util.h> /* for MuResult */
|
||||
#include <mu-store.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* opaque structure */
|
||||
struct _MuIndex;
|
||||
typedef struct _MuIndex MuIndex;
|
||||
|
||||
struct _MuIndexStats {
|
||||
unsigned _processed; /* number of msgs processed or counted */
|
||||
unsigned _updated; /* number of msgs new or updated */
|
||||
unsigned _cleaned_up; /* number of msgs cleaned up */
|
||||
unsigned _uptodate; /* number of msgs already uptodate */
|
||||
};
|
||||
typedef struct _MuIndexStats MuIndexStats;
|
||||
|
||||
/**
|
||||
* create a new MuIndex instance. NOTE(1): the database does not have
|
||||
* to exist yet, but the directory must already exist; NOTE(2): before
|
||||
* doing anything with the returned Index object, make sure you haved
|
||||
* called g_type_init, and mu_msg_init somewhere in your code.
|
||||
*
|
||||
* @param store a writable MuStore object
|
||||
* @param err to receive error or NULL; there are only errors when this
|
||||
* function returns NULL. Possible errors: see mu-error.h
|
||||
*
|
||||
* @return a new MuIndex instance, or NULL in case of error
|
||||
*/
|
||||
MuIndex* mu_index_new (MuStore *store, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* destroy the index instance
|
||||
*
|
||||
* @param index a MuIndex instance, or NULL
|
||||
*/
|
||||
void mu_index_destroy (MuIndex *index);
|
||||
|
||||
|
||||
/**
|
||||
* change the maximum file size that mu-index considers from its
|
||||
* default (MU_INDEX_MAX_FILE_SIZE). Note that the maximum size is a
|
||||
* protection against mu (or the libraries it uses) allocating too
|
||||
* much memory, which can lead to problems
|
||||
*
|
||||
* @param index a mu index object
|
||||
* @param max_size the maximum msg size, or 0 to reset to the default
|
||||
*/
|
||||
void mu_index_set_max_msg_size (MuIndex *index, guint max_size);
|
||||
|
||||
|
||||
/**
|
||||
* change batch size for Xapian store transaction (see
|
||||
* 'mu_store_set_batch_size')
|
||||
*
|
||||
* @param index a mu index object
|
||||
* @param max_size the batch size, or 0 to reset to the default
|
||||
*/
|
||||
void mu_index_set_xbatch_size (MuIndex *index, guint xbatchsize);
|
||||
|
||||
|
||||
/**
|
||||
* callback function for mu_index_(run|stats|cleanup), for each message
|
||||
*
|
||||
* @param stats pointer to structure to receive statistics data
|
||||
* @param user_data pointer to user data
|
||||
*
|
||||
* @return MU_OK to continue, MU_STOP to stop, or MU_ERROR in
|
||||
* case of some error.
|
||||
*/
|
||||
typedef MuError (*MuIndexMsgCallback) (MuIndexStats* stats, void *user_data);
|
||||
|
||||
|
||||
/**
|
||||
* callback function for mu_index_(run|stats|cleanup), for each dir enter/leave
|
||||
*
|
||||
* @param path dirpath we just entered / left
|
||||
* @param enter did we enter (TRUE) or leave(FALSE) the dir?
|
||||
* @param user_data pointer to user data
|
||||
*
|
||||
* @return MU_OK to contiue, MU_STOP to stopd or MU_ERROR in
|
||||
* case of some error.
|
||||
*/
|
||||
typedef MuError (*MuIndexDirCallback) (const char* path, gboolean enter,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* start the indexing process
|
||||
*
|
||||
* @param index a valid MuIndex instance
|
||||
* @param path the path to index. This must be an absolute path
|
||||
* @param force if != 0, force re-indexing already index messages; this is
|
||||
* obviously a lot slower than only indexing new/changed messages
|
||||
* @param stats a structure with some statistics about the results;
|
||||
* note that this function does *not* reset the struct values to allow
|
||||
* for cumulative stats from multiple calls. If needed, you can use
|
||||
* @mu_index_stats_clear before calling this function
|
||||
* @param cb_msg a callback function called for every msg indexed;
|
||||
* @param cb_dir a callback function called for every dir entered/left or NULL
|
||||
* @param user_data a user pointer that will be passed to the callback function
|
||||
*
|
||||
* @return MU_OK if the stats gathering was completed succesfully,
|
||||
* MU_STOP if the user stopped or MU_ERROR in
|
||||
* case of some error.
|
||||
*/
|
||||
MuError mu_index_run (MuIndex *index, const char* path, gboolean force,
|
||||
MuIndexStats *stats, MuIndexMsgCallback msg_cb,
|
||||
MuIndexDirCallback dir_cb, void *user_data);
|
||||
|
||||
/**
|
||||
* gather some statistics about the Maildir; this is usually much faster
|
||||
* than mu_index_run, and can thus be used to provide some information to the user
|
||||
* note though that the statistics may be different from the reality that
|
||||
* mu_index_run sees, when there are updates in the Maildir
|
||||
*
|
||||
* @param index a valid MuIndex instance
|
||||
* @param path the path to get stats for; this must be an absolute path
|
||||
* @param stats a structure with some statistics about the results;
|
||||
* note that this function does *not* reset the struct values to allow
|
||||
* for cumulative stats from multiple calls. If needed, you can use
|
||||
* @mu_index_stats_clear before calling this function
|
||||
* @param msg_cb a callback function which will be called for every msg;
|
||||
* @param dir_cb a callback function which will be called for every dir or NULL
|
||||
* @param user_data a user pointer that will be passed to the callback function
|
||||
* xb
|
||||
* @return MU_OK if the stats gathering was completed succesfully,
|
||||
* MU_STOP if the user stopped or MU_ERROR in
|
||||
* case of some error.
|
||||
*/
|
||||
MuError mu_index_stats (MuIndex *index, const char* path, MuIndexStats *stats,
|
||||
MuIndexMsgCallback msg_cb, MuIndexDirCallback dir_cb,
|
||||
void *user_data);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* callback function called for each message
|
||||
*
|
||||
* @param MuIndexCleanupCallback
|
||||
*
|
||||
* @return a MuResult
|
||||
*/
|
||||
typedef MuError (*MuIndexCleanupDeleteCallback) (MuIndexStats *stats,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* cleanup the database; ie. remove entries for which no longer a corresponding
|
||||
* file exists in the maildir
|
||||
*
|
||||
* @param index a valid MuIndex instance
|
||||
* @param stats a structure with some statistics about the results;
|
||||
* note that this function does *not* reset the struct values to allow
|
||||
* for cumulative stats from multiple calls. If needed, you can use
|
||||
* @mu_index_stats_clear before calling this function
|
||||
* @param cb a callback function which will be called for every msg;
|
||||
* @param user_data a user pointer that will be passed to the callback function
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return MU_OK if the stats gathering was completed succesfully,
|
||||
* MU_STOP if the user stopped or MU_ERROR in
|
||||
* case of some error.
|
||||
*/
|
||||
MuError mu_index_cleanup (MuIndex *index, MuIndexStats *stats,
|
||||
MuIndexCleanupDeleteCallback cb,
|
||||
void *user_data, GError **err);
|
||||
|
||||
/**
|
||||
* clear the stats structure
|
||||
*
|
||||
* @param stats a MuIndexStats object
|
||||
*
|
||||
* @return TRUE if stats != NULL, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_index_stats_clear (MuIndexStats *stats);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_INDEX_H__*/
|
||||
264
lib/mu-log.c
Normal file
264
lib/mu-log.c
Normal file
@ -0,0 +1,264 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include "mu-log.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mu-util.h"
|
||||
|
||||
#define MU_MAX_LOG_FILE_SIZE 1000 * 1000 /* 1 MB (SI units) */
|
||||
#define MU_LOG_FILE "mu.log"
|
||||
|
||||
struct _MuLog {
|
||||
int _fd; /* log file descriptor */
|
||||
|
||||
gboolean _own; /* close _fd with log_destroy? */
|
||||
gboolean _debug; /* add debug-level stuff? */
|
||||
gboolean _quiet; /* don't write non-error to stdout/stderr */
|
||||
|
||||
GLogFunc _old_log_func;
|
||||
};
|
||||
typedef struct _MuLog MuLog;
|
||||
|
||||
/* we use globals, because logging is a global operation as it
|
||||
* globally modifies the behaviour of g_warning and friends
|
||||
*/
|
||||
static MuLog* MU_LOG = NULL;
|
||||
static void log_write (const char* domain, GLogLevelFlags level,
|
||||
const gchar *msg);
|
||||
|
||||
static void
|
||||
try_close (int fd)
|
||||
{
|
||||
if (fd < 0)
|
||||
return;
|
||||
|
||||
if (close (fd) < 0)
|
||||
g_printerr ("%s: close() of fd %d failed: %s\n",
|
||||
__FUNCTION__, fd, strerror(errno));
|
||||
}
|
||||
|
||||
static void
|
||||
silence (void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_log_init_silence (void)
|
||||
{
|
||||
g_return_val_if_fail (!MU_LOG, FALSE);
|
||||
|
||||
MU_LOG = g_new(MuLog, 1);
|
||||
MU_LOG->_fd = -1;
|
||||
MU_LOG->_own = FALSE; /* nobody owns silence */
|
||||
|
||||
MU_LOG->_old_log_func =
|
||||
g_log_set_default_handler ((GLogFunc)silence, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
log_handler (const gchar* log_domain, GLogLevelFlags log_level,
|
||||
const gchar* msg)
|
||||
{
|
||||
if ((log_level & G_LOG_LEVEL_DEBUG) && !(MU_LOG->_debug))
|
||||
return;
|
||||
|
||||
log_write (log_domain ? log_domain : "mu", log_level, msg);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_log_init_with_fd (int fd, gboolean doclose,
|
||||
gboolean quiet, gboolean debug)
|
||||
{
|
||||
g_return_val_if_fail (!MU_LOG, FALSE);
|
||||
|
||||
MU_LOG = g_new(MuLog, 1);
|
||||
|
||||
MU_LOG->_fd = fd;
|
||||
MU_LOG->_quiet = quiet;
|
||||
MU_LOG->_debug = debug;
|
||||
MU_LOG->_own = doclose; /* if we now own the fd, close it
|
||||
* in _destroy */
|
||||
MU_LOG->_old_log_func =
|
||||
g_log_set_default_handler ((GLogFunc)log_handler, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
move_log_file (const char *logfile)
|
||||
{
|
||||
gchar *logfile_old;
|
||||
int rv;
|
||||
|
||||
logfile_old = g_strdup_printf ("%s.old", logfile);
|
||||
rv = rename (logfile, logfile_old);
|
||||
g_free (logfile_old);
|
||||
|
||||
if (rv != 0) {
|
||||
g_warning ("failed to move %s to %s.old: %s",
|
||||
logfile, logfile, strerror(rv));
|
||||
return FALSE;
|
||||
} else
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
log_file_backup_maybe (const char *logfile)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (stat (logfile, &statbuf) != 0) {
|
||||
if (errno == ENOENT)
|
||||
return TRUE; /* it did not exist yet, no problem */
|
||||
else {
|
||||
g_warning ("failed to stat(2) %s: %s",
|
||||
logfile, strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* log file is still below the max size? */
|
||||
if (statbuf.st_size <= MU_MAX_LOG_FILE_SIZE)
|
||||
return TRUE;
|
||||
|
||||
/* log file is too big!; we move it to <logfile>.old, overwriting */
|
||||
return move_log_file (logfile);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_log_init (const char* logfile, gboolean backup,
|
||||
gboolean quiet, gboolean debug)
|
||||
{
|
||||
int fd;
|
||||
|
||||
/* only init once... */
|
||||
g_return_val_if_fail (!MU_LOG, FALSE);
|
||||
g_return_val_if_fail (logfile, FALSE);
|
||||
|
||||
if (backup && !log_file_backup_maybe(logfile)) {
|
||||
g_warning ("failed to backup log file");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
fd = open (logfile, O_WRONLY|O_CREAT|O_APPEND, 00600);
|
||||
if (fd < 0)
|
||||
g_warning ("%s: open() of '%s' failed: %s", __FUNCTION__,
|
||||
logfile, strerror(errno));
|
||||
|
||||
if (fd < 0 || !mu_log_init_with_fd (fd, FALSE, quiet, debug)) {
|
||||
try_close (fd);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
mu_log_uninit (void)
|
||||
{
|
||||
if (!MU_LOG)
|
||||
return;
|
||||
|
||||
if (MU_LOG->_own)
|
||||
try_close (MU_LOG->_fd);
|
||||
|
||||
g_free (MU_LOG);
|
||||
|
||||
MU_LOG = NULL;
|
||||
}
|
||||
|
||||
|
||||
static const char*
|
||||
pfx (GLogLevelFlags level)
|
||||
{
|
||||
switch (level) {
|
||||
case G_LOG_LEVEL_WARNING: return "WARN";
|
||||
case G_LOG_LEVEL_ERROR : return "ERR ";
|
||||
case G_LOG_LEVEL_DEBUG: return "DBG ";
|
||||
case G_LOG_LEVEL_CRITICAL: return "CRIT";
|
||||
case G_LOG_LEVEL_MESSAGE: return "MSG ";
|
||||
case G_LOG_LEVEL_INFO : return "INFO";
|
||||
default: return "LOG ";
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
log_write (const char* domain, GLogLevelFlags level,
|
||||
const gchar *msg)
|
||||
{
|
||||
time_t now;
|
||||
ssize_t len;
|
||||
|
||||
/* log lines will be truncated at 768 chars */
|
||||
char buf [768], timebuf [22];
|
||||
|
||||
g_return_if_fail (MU_LOG);
|
||||
|
||||
/* get the time/date string */
|
||||
now = time(NULL);
|
||||
strftime (timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S",
|
||||
localtime(&now));
|
||||
|
||||
/* now put it all together */
|
||||
len = snprintf (buf, sizeof(buf), "%s [%s] %s\n", timebuf,
|
||||
pfx(level), msg);
|
||||
|
||||
if (write (MU_LOG->_fd, buf, (size_t)len) < 0)
|
||||
fprintf (stderr, "%s: failed to write to log: %s\n",
|
||||
__FUNCTION__, strerror(errno));
|
||||
|
||||
if (!(MU_LOG->_quiet) && (level & G_LOG_LEVEL_MESSAGE)) {
|
||||
fputs ("mu: ", stdout);
|
||||
fputs (msg, stdout);
|
||||
fputs ("\n", stdout);
|
||||
}
|
||||
|
||||
/* for serious errors, log them to stderr as well */
|
||||
if (level & G_LOG_LEVEL_ERROR ||
|
||||
level & G_LOG_LEVEL_CRITICAL ||
|
||||
level & G_LOG_LEVEL_WARNING ||
|
||||
level & G_LOG_LEVEL_DEBUG) {
|
||||
fputs ("mu: ", stderr);
|
||||
fputs (msg, stderr);
|
||||
fputs ("\n", stderr);
|
||||
}
|
||||
}
|
||||
77
lib/mu-log.h
Normal file
77
lib/mu-log.h
Normal file
@ -0,0 +1,77 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_LOG_H__
|
||||
#define __MU_LOG_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/* mu log is the global logging system */
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* write logging information to a log file
|
||||
*
|
||||
* @param full path to the log file (does not have to exist yet, but
|
||||
* it's directory must)
|
||||
* @param backup if TRUE and size of log file > MU_MAX_LOG_FILE_SIZE, move
|
||||
* the log file to <log file>.old and start a new one. The .old file will overwrite
|
||||
* existing files of that name
|
||||
* @param quiet don't log non-errors to stdout/stderr
|
||||
* @param debug include debug-level information.
|
||||
*
|
||||
* @return TRUE if initialization succeeds, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_log_init (const char *logfile, gboolean backup,
|
||||
gboolean quiet, gboolean debug)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* write logging information to a file descriptor
|
||||
*
|
||||
* @param fd an open file descriptor
|
||||
* @param doclose if true, mu-log will close it upon mu_log_uninit
|
||||
* @param quiet don't log non-errors to stdout/stderr
|
||||
* @param debug include debug-level info
|
||||
*
|
||||
* @return TRUE if initialization succeeds, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_log_init_with_fd (int fd, gboolean doclose, gboolean quiet,
|
||||
gboolean debug) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* be silent except for runtime errors, which will be written to
|
||||
* stderr.
|
||||
*
|
||||
* @return TRUE if initialization succeeds, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_log_init_silence (void) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* unitialize the logging system, and free all resources
|
||||
*/
|
||||
void mu_log_uninit (void);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_LOG_H__*/
|
||||
895
lib/mu-maildir.c
Normal file
895
lib/mu-maildir.c
Normal file
@ -0,0 +1,895 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* hopefully, the should get us a sane PATH_MAX */
|
||||
#include <limits.h>
|
||||
/* not all systems provide PATH_MAX in limits.h */
|
||||
#ifndef PATH_MAX
|
||||
#include <sys/param.h>
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX MAXPATHLEN
|
||||
#endif /*!PATH_MAX */
|
||||
#endif /*PATH_MAX */
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <glib/gprintf.h>
|
||||
|
||||
#include "mu-util.h"
|
||||
#include "mu-maildir.h"
|
||||
#include "mu-str.h"
|
||||
|
||||
#define MU_MAILDIR_NOINDEX_FILE ".noindex"
|
||||
|
||||
/* On Linux (and some BSD), we have entry->d_type, but some file
|
||||
* systems (XFS, ReiserFS) do not support it, and set it DT_UNKNOWN.
|
||||
* On other OSs, notably Solaris, entry->d_type is not present at all.
|
||||
* For these cases, we use lstat (in get_dtype) as a slower fallback,
|
||||
* and return it in the d_type parameter
|
||||
*/
|
||||
#ifdef HAVE_STRUCT_DIRENT_D_TYPE
|
||||
#define GET_DTYPE(DE,FP) \
|
||||
((DE)->d_type == DT_UNKNOWN ? mu_util_get_dtype_with_lstat((FP)) : \
|
||||
(DE)->d_type)
|
||||
#else
|
||||
#define GET_DTYPE(DE,FP) \
|
||||
mu_util_get_dtype_with_lstat((FP))
|
||||
#endif /*HAVE_STRUCT_DIRENT_D_TYPE*/
|
||||
|
||||
|
||||
static gboolean
|
||||
create_maildir (const char *path, mode_t mode, GError **err)
|
||||
{
|
||||
int i;
|
||||
const gchar* subdirs[] = {"new", "cur", "tmp"};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(subdirs); ++i) {
|
||||
|
||||
const char *fullpath;
|
||||
int rv;
|
||||
|
||||
/* static buffer */
|
||||
fullpath = mu_str_fullpath_s (path, subdirs[i]);
|
||||
|
||||
/* if subdir already exists, don't try to re-create
|
||||
* it */
|
||||
if (mu_util_check_dir (fullpath, TRUE, TRUE))
|
||||
continue;
|
||||
|
||||
rv = g_mkdir_with_parents (fullpath, (int)mode);
|
||||
|
||||
/* note, g_mkdir_with_parents won't detect an error if
|
||||
* there's already such a dir, but with the wrong
|
||||
* permissions; so we need to check */
|
||||
if (rv != 0 || !mu_util_check_dir(fullpath, TRUE, TRUE)) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_MKDIR,
|
||||
"creating dir failed for %s: %s",
|
||||
fullpath,
|
||||
strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
create_noindex (const char *path, GError **err)
|
||||
{
|
||||
/* create a noindex file if requested */
|
||||
int fd;
|
||||
const char *noindexpath;
|
||||
|
||||
/* static buffer */
|
||||
noindexpath = mu_str_fullpath_s (path, MU_MAILDIR_NOINDEX_FILE);
|
||||
|
||||
fd = creat (noindexpath, 0644);
|
||||
|
||||
/* note, if the 'close' failed, creation may still have
|
||||
* succeeded...*/
|
||||
if (fd < 0 || close (fd) != 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_CREATE,
|
||||
"error in create_noindex: %s",
|
||||
strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_maildir_mkdir (const char* path, mode_t mode, gboolean noindex, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
MU_WRITE_LOG ("%s (%s, %o, %s)", __FUNCTION__,
|
||||
path, mode, noindex ? "TRUE" : "FALSE");
|
||||
|
||||
if (!create_maildir (path, mode, err))
|
||||
return FALSE;
|
||||
|
||||
if (noindex && !create_noindex (path, err))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* determine whether the source message is in 'new' or in 'cur';
|
||||
* we ignore messages in 'tmp' for obvious reasons */
|
||||
static gboolean
|
||||
check_subdir (const char *src, gboolean *in_cur, GError **err)
|
||||
{
|
||||
gchar *srcpath;
|
||||
|
||||
srcpath = g_path_get_dirname (src);
|
||||
|
||||
if (g_str_has_suffix (srcpath, "new"))
|
||||
*in_cur = FALSE;
|
||||
else if (g_str_has_suffix (srcpath, "cur"))
|
||||
*in_cur = TRUE;
|
||||
else {
|
||||
g_set_error(err, 0, MU_ERROR_FILE_INVALID_SOURCE,
|
||||
"invalid source message '%s'", src);
|
||||
return FALSE;
|
||||
}
|
||||
g_free (srcpath);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gchar*
|
||||
get_target_fullpath (const char* src, const gchar *targetpath, GError **err)
|
||||
{
|
||||
gchar *targetfullpath, *srcfile;
|
||||
gboolean in_cur;
|
||||
|
||||
if (!check_subdir (src, &in_cur, err))
|
||||
return NULL;
|
||||
|
||||
srcfile = g_path_get_basename (src);
|
||||
|
||||
/* create targetpath; note: make the filename cough* unique by
|
||||
*including a hash * of the srcname in the targetname. This
|
||||
*helps if there are * copies of a message (which all have the
|
||||
*same basename)*/
|
||||
targetfullpath = g_strdup_printf ("%s%c%s%c%u_%s",
|
||||
targetpath,
|
||||
G_DIR_SEPARATOR,
|
||||
in_cur ? "cur" : "new",
|
||||
G_DIR_SEPARATOR,
|
||||
g_str_hash(src),
|
||||
srcfile);
|
||||
g_free (srcfile);
|
||||
|
||||
return targetfullpath;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_maildir_link (const char* src, const char *targetpath, GError **err)
|
||||
{
|
||||
gchar *targetfullpath;
|
||||
int rv;
|
||||
|
||||
g_return_val_if_fail (src, FALSE);
|
||||
g_return_val_if_fail (targetpath, FALSE);
|
||||
|
||||
targetfullpath = get_target_fullpath (src, targetpath, err);
|
||||
if (!targetfullpath)
|
||||
return FALSE;
|
||||
|
||||
rv = symlink (src, targetfullpath);
|
||||
|
||||
if (rv != 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_LINK,
|
||||
"error creating link %s => %s: %s",
|
||||
targetfullpath, src, strerror (errno));
|
||||
g_free (targetfullpath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free (targetfullpath);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
process_dir (const char* path, const gchar *mdir,
|
||||
MuMaildirWalkMsgCallback msg_cb,
|
||||
MuMaildirWalkDirCallback dir_cb, void *data);
|
||||
|
||||
static MuError
|
||||
process_file (const char* fullpath, const gchar* mdir,
|
||||
MuMaildirWalkMsgCallback msg_cb, void *data)
|
||||
{
|
||||
MuError result;
|
||||
struct stat statbuf;
|
||||
|
||||
if (!msg_cb)
|
||||
return MU_OK;
|
||||
|
||||
if (G_UNLIKELY(access(fullpath, R_OK) != 0)) {
|
||||
g_warning ("cannot access %s: %s", fullpath,
|
||||
strerror(errno));
|
||||
return MU_ERROR;
|
||||
}
|
||||
|
||||
if (G_UNLIKELY(stat (fullpath, &statbuf) != 0)) {
|
||||
g_warning ("cannot stat %s: %s", fullpath, strerror(errno));
|
||||
return MU_ERROR;
|
||||
}
|
||||
|
||||
result = (msg_cb)(fullpath, mdir, &statbuf, data);
|
||||
if (result == MU_STOP)
|
||||
g_debug ("callback said 'MU_STOP' for %s", fullpath);
|
||||
else if (result == MU_ERROR)
|
||||
g_warning ("%s: error in callback (%s)",
|
||||
__FUNCTION__, fullpath);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* determine if path is a maildir leaf-dir; ie. if it's 'cur' or 'new'
|
||||
* (we're skipping 'tmp' for obvious reasons)
|
||||
*/
|
||||
G_GNUC_CONST static gboolean
|
||||
is_maildir_new_or_cur (const char *path)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
/* path is the full path; it cannot possibly be shorter
|
||||
* than 4 for a maildir (/cur or /new) */
|
||||
len = strlen (path);
|
||||
if (G_UNLIKELY(len < 4))
|
||||
return FALSE;
|
||||
|
||||
/* optimization; one further idea would be cast the 4 bytes to an integer
|
||||
* and compare that -- need to think about alignment, endianness */
|
||||
|
||||
if (path[len - 4] == G_DIR_SEPARATOR &&
|
||||
path[len - 3] == 'c' &&
|
||||
path[len - 2] == 'u' &&
|
||||
path[len - 1] == 'r')
|
||||
return TRUE;
|
||||
|
||||
if (path[len - 4] == G_DIR_SEPARATOR &&
|
||||
path[len - 3] == 'n' &&
|
||||
path[len - 2] == 'e' &&
|
||||
path[len - 1] == 'w')
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* check if there is a noindex file (MU_MAILDIR_NOINDEX_FILE) in this
|
||||
* dir; */
|
||||
static gboolean
|
||||
has_noindex_file (const char *path)
|
||||
{
|
||||
const char* noindexpath;
|
||||
|
||||
/* static buffer */
|
||||
noindexpath = mu_str_fullpath_s (path, MU_MAILDIR_NOINDEX_FILE);
|
||||
|
||||
if (access (noindexpath, F_OK) == 0)
|
||||
return TRUE;
|
||||
else if (G_UNLIKELY(errno != ENOENT))
|
||||
g_warning ("error testing for noindex file %s: %s",
|
||||
noindexpath, strerror(errno));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
is_dotdir_to_ignore (const char* dir)
|
||||
{
|
||||
int i;
|
||||
const char* ignore[] = {
|
||||
".notmuch",
|
||||
".nnmaildir",
|
||||
".#evolution"
|
||||
}; /* when adding names, check the optimization below */
|
||||
|
||||
if (dir[0] != '.')
|
||||
return FALSE; /* not a dotdir */
|
||||
|
||||
if (dir[1] == '\0' || (dir[1] == '.' && dir[2] == '\0'))
|
||||
return TRUE; /* ignore '.' and '..' */
|
||||
|
||||
/* optimization: special dirs have 'n' or '#' in pos 1 */
|
||||
if (dir[1] != 'n' && dir[1] != '#')
|
||||
return FALSE; /* not special: don't ignore */
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(ignore); ++i)
|
||||
if (strcmp(dir, ignore[i]) == 0)
|
||||
return TRUE;
|
||||
|
||||
return FALSE; /* don't ignore */
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ignore_dir_entry (struct dirent *entry, unsigned char d_type)
|
||||
{
|
||||
if (G_LIKELY(d_type == DT_REG)) {
|
||||
|
||||
/* ignore emacs tempfiles */
|
||||
if (entry->d_name[0] == '#')
|
||||
return TRUE;
|
||||
/* ignore dovecot metadata */
|
||||
if (entry->d_name[0] == 'd' &&
|
||||
strncmp (entry->d_name, "dovecot", 7) == 0)
|
||||
return TRUE;
|
||||
/* ignore core files */
|
||||
if (entry->d_name[0] == 'c' &&
|
||||
strncmp (entry->d_name, "core", 4) == 0)
|
||||
return TRUE;
|
||||
|
||||
return FALSE; /* other files: don't ignore */
|
||||
|
||||
} else if (d_type == DT_DIR)
|
||||
return is_dotdir_to_ignore (entry->d_name);
|
||||
else
|
||||
return TRUE; /* ignore non-normal files, non-dirs */
|
||||
}
|
||||
|
||||
/*
|
||||
* return the maildir value for the the path - this is the directory
|
||||
* for the message (with the top-level dir as "/"), and without the
|
||||
* leaf "/cur" or "/new". In other words, contatenate old_mdir + "/" + dir,
|
||||
* unless dir is either 'new' or 'cur'. The value will be used in queries.
|
||||
*/
|
||||
static gchar*
|
||||
get_mdir_for_path (const gchar *old_mdir, const gchar *dir)
|
||||
{
|
||||
/* if the current dir is not 'new' or 'cur', contatenate
|
||||
* old_mdir an dir */
|
||||
if ((dir[0] == 'n' && strcmp(dir, "new") == 0) ||
|
||||
(dir[0] == 'c' && strcmp(dir, "cur") == 0) ||
|
||||
(dir[0] == 't' && strcmp(dir, "tmp") == 0))
|
||||
return strdup (old_mdir ? old_mdir : G_DIR_SEPARATOR_S);
|
||||
else
|
||||
return g_strconcat (old_mdir ? old_mdir : "",
|
||||
G_DIR_SEPARATOR_S, dir, NULL);
|
||||
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
process_dir_entry (const char* path, const char* mdir, struct dirent *entry,
|
||||
MuMaildirWalkMsgCallback cb_msg,
|
||||
MuMaildirWalkDirCallback cb_dir,
|
||||
void *data)
|
||||
{
|
||||
const char *fp;
|
||||
char* fullpath;
|
||||
unsigned char d_type;
|
||||
|
||||
/* we have to copy the buffer from fullpath_s, because it
|
||||
* returns a static buffer, and we maybe called reentrantly */
|
||||
fp = mu_str_fullpath_s (path, entry->d_name);
|
||||
fullpath = g_newa (char, strlen(fp) + 1);
|
||||
strcpy (fullpath, fp);
|
||||
|
||||
d_type = GET_DTYPE(entry, fullpath);
|
||||
|
||||
/* ignore special files/dirs */
|
||||
if (ignore_dir_entry (entry, d_type))
|
||||
return MU_OK;
|
||||
|
||||
switch (d_type) {
|
||||
case DT_REG: /* we only want files in cur/ and new/ */
|
||||
if (!is_maildir_new_or_cur (path))
|
||||
return MU_OK;
|
||||
|
||||
return process_file (fullpath, mdir, cb_msg, data);
|
||||
|
||||
case DT_DIR: {
|
||||
char *my_mdir;
|
||||
MuError rv;
|
||||
/* my_mdir is the search maildir (the dir starting
|
||||
* with the top-level maildir as /, and without the
|
||||
* /tmp, /cur, /new
|
||||
*/
|
||||
my_mdir = get_mdir_for_path (mdir, entry->d_name);
|
||||
rv = process_dir (fullpath, my_mdir, cb_msg, cb_dir, data);
|
||||
g_free (my_mdir);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
default:
|
||||
return MU_OK; /* ignore other types */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const size_t DIRENT_ALLOC_SIZE =
|
||||
offsetof (struct dirent, d_name) + PATH_MAX;
|
||||
|
||||
static struct dirent*
|
||||
dirent_new (void)
|
||||
{
|
||||
return (struct dirent*) g_slice_alloc (DIRENT_ALLOC_SIZE);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dirent_destroy (struct dirent *entry)
|
||||
{
|
||||
g_slice_free1 (DIRENT_ALLOC_SIZE, entry);
|
||||
}
|
||||
|
||||
#ifdef HAVE_STRUCT_DIRENT_D_INO
|
||||
static int
|
||||
dirent_cmp (struct dirent *d1, struct dirent *d2)
|
||||
{
|
||||
/* we do it his way instead of a simple d1->d_ino - d2->d_ino
|
||||
* because this way, we don't need 64-bit numbers for the
|
||||
* actual sorting */
|
||||
if (d1->d_ino < d2->d_ino)
|
||||
return -1;
|
||||
else if (d1->d_ino > d2->d_ino)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
#endif /*HAVE_STRUCT_DIRENT_D_INO*/
|
||||
|
||||
static MuError
|
||||
process_dir_entries (DIR *dir, const char* path, const char* mdir,
|
||||
MuMaildirWalkMsgCallback msg_cb,
|
||||
MuMaildirWalkDirCallback dir_cb, void *data)
|
||||
{
|
||||
MuError result;
|
||||
GSList *lst, *c;
|
||||
|
||||
for (lst = NULL;;) {
|
||||
int rv;
|
||||
struct dirent *entry, *res;
|
||||
entry = dirent_new ();
|
||||
rv = readdir_r (dir, entry, &res);
|
||||
if (rv == 0) {
|
||||
if (res)
|
||||
lst = g_slist_prepend (lst, entry);
|
||||
else {
|
||||
dirent_destroy (entry);
|
||||
break; /* last direntry reached */
|
||||
}
|
||||
} else {
|
||||
dirent_destroy (entry);
|
||||
g_warning ("error scanning dir: %s", strerror(rv));
|
||||
return MU_ERROR_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
/* we sort by inode; this makes things much faster on
|
||||
* extfs2,3 */
|
||||
#if HAVE_STRUCT_DIRENT_D_INO
|
||||
c = lst = g_slist_sort (lst, (GCompareFunc)dirent_cmp);
|
||||
#endif /*HAVE_STRUCT_DIRENT_D_INO*/
|
||||
|
||||
for (c = lst, result = MU_OK; c && result == MU_OK; c = g_slist_next(c))
|
||||
result = process_dir_entry (path, mdir, (struct dirent*)c->data,
|
||||
msg_cb, dir_cb, data);
|
||||
|
||||
g_slist_foreach (lst, (GFunc)dirent_destroy, NULL);
|
||||
g_slist_free (lst);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
process_dir (const char* path, const char* mdir,
|
||||
MuMaildirWalkMsgCallback msg_cb,
|
||||
MuMaildirWalkDirCallback dir_cb, void *data)
|
||||
{
|
||||
MuError result;
|
||||
DIR* dir;
|
||||
|
||||
/* if it has a noindex file, we ignore this dir */
|
||||
if (has_noindex_file (path)) {
|
||||
g_debug ("found .noindex: ignoring dir %s", path);
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
dir = opendir (path);
|
||||
if (G_UNLIKELY(!dir)) {
|
||||
g_warning ("%s: ignoring %s: %s", __FUNCTION__,
|
||||
path, strerror(errno));
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
if (dir_cb) {
|
||||
MuError rv;
|
||||
rv = dir_cb (path, TRUE, data);
|
||||
if (rv != MU_OK) {
|
||||
closedir (dir);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
result = process_dir_entries (dir, path, mdir, msg_cb, dir_cb, data);
|
||||
closedir (dir);
|
||||
|
||||
/* only run dir_cb if it exists and so far, things went ok */
|
||||
if (dir_cb && result == MU_OK)
|
||||
return dir_cb (path, FALSE, data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
MuError
|
||||
mu_maildir_walk (const char *path, MuMaildirWalkMsgCallback cb_msg,
|
||||
MuMaildirWalkDirCallback cb_dir, void *data)
|
||||
{
|
||||
MuError rv;
|
||||
char *mypath;
|
||||
|
||||
g_return_val_if_fail (path && cb_msg, MU_ERROR);
|
||||
g_return_val_if_fail (mu_util_check_dir(path, TRUE, FALSE), MU_ERROR);
|
||||
|
||||
/* strip the final / or \ */
|
||||
mypath = g_strdup (path);
|
||||
if (mypath[strlen(mypath)-1] == G_DIR_SEPARATOR)
|
||||
mypath[strlen(mypath)-1] = '\0';
|
||||
|
||||
rv = process_dir (mypath, NULL, cb_msg, cb_dir, data);
|
||||
g_free (mypath);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
clear_links (const gchar* dirname, DIR *dir, GError **err)
|
||||
{
|
||||
struct dirent *entry;
|
||||
gboolean rv;
|
||||
|
||||
rv = TRUE;
|
||||
errno = 0;
|
||||
while ((entry = readdir (dir))) {
|
||||
|
||||
const char *fp;
|
||||
char *fullpath;
|
||||
unsigned char d_type;
|
||||
|
||||
/* ignore empty, dot thingies */
|
||||
if (!entry->d_name || entry->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
/* we have to copy the buffer from fullpath_s, because
|
||||
* it returns a static buffer and we are
|
||||
* recursive*/
|
||||
fp = mu_str_fullpath_s (dirname, entry->d_name);
|
||||
fullpath = g_newa (char, strlen(fp) + 1);
|
||||
strcpy (fullpath, fp);
|
||||
|
||||
d_type = GET_DTYPE (entry, fullpath);
|
||||
|
||||
/* ignore non-links / non-dirs */
|
||||
if (d_type != DT_LNK && d_type != DT_DIR)
|
||||
continue;
|
||||
|
||||
if (d_type == DT_LNK) {
|
||||
if (unlink (fullpath) != 0) {
|
||||
/* don't use err */
|
||||
g_warning ("error unlinking %s: %s",
|
||||
fullpath, strerror(errno));
|
||||
rv = FALSE;
|
||||
}
|
||||
} else /* DT_DIR, see check before*/
|
||||
rv = mu_maildir_clear_links (fullpath, err);
|
||||
}
|
||||
|
||||
if (errno != 0)
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"file error: %s", strerror(errno));
|
||||
|
||||
return (rv == FALSE && errno == 0);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_maildir_clear_links (const gchar* path, GError **err)
|
||||
{
|
||||
DIR *dir;
|
||||
gboolean rv;
|
||||
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
dir = opendir (path);
|
||||
if (!dir) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_OPEN,
|
||||
"failed to open %s: %s", path,
|
||||
strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
rv = clear_links (path, dir, err);
|
||||
closedir (dir);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
MuFlags
|
||||
mu_maildir_get_flags_from_path (const char *path)
|
||||
{
|
||||
g_return_val_if_fail (path, MU_FLAG_INVALID);
|
||||
|
||||
/* try to find the info part */
|
||||
/* note that we can use either the ':' or '!' as separator;
|
||||
* the former is the official, but as it does not work on e.g. VFAT
|
||||
* file systems, some Maildir implementations use the latter instead
|
||||
* (or both). For example, Tinymail/modest does this. The python
|
||||
* documentation at http://docs.python.org/lib/mailbox-maildir.html
|
||||
* mentions the '!' as well as a 'popular choice'
|
||||
*/
|
||||
|
||||
/* we check the dir -- */
|
||||
if (strstr (path, G_DIR_SEPARATOR_S "new" G_DIR_SEPARATOR_S)) {
|
||||
|
||||
char *dir, *dir2;
|
||||
MuFlags flags;
|
||||
|
||||
dir = g_path_get_dirname (path);
|
||||
dir2 = g_path_get_basename (dir);
|
||||
|
||||
if (g_strcmp0 (dir2, "new") == 0)
|
||||
flags = MU_FLAG_NEW;
|
||||
|
||||
g_free (dir);
|
||||
g_free (dir2);
|
||||
|
||||
/* NOTE: new/ message should not have :2,-stuff, as
|
||||
* per http://cr.yp.to/proto/maildir.html. If they, do
|
||||
* we ignore it
|
||||
*/
|
||||
if (flags == MU_FLAG_NEW)
|
||||
return flags;
|
||||
}
|
||||
|
||||
/* get the file flags */
|
||||
{
|
||||
char *info;
|
||||
|
||||
info = strrchr (path, '2');
|
||||
if (!info || info == path ||
|
||||
(info[-1] != ':' && info[-1] != '!') ||
|
||||
(info[1] != ','))
|
||||
return MU_FLAG_NONE;
|
||||
else
|
||||
return mu_flags_from_str (&info[2],
|
||||
MU_FLAG_TYPE_MAILFILE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* take an exising message path, and return a new path, based on
|
||||
* whether it should be in 'new' or 'cur'; ie.
|
||||
*
|
||||
* /home/user/Maildir/foo/bar/cur/abc:2,F and flags == MU_FLAG_NEW
|
||||
* => /home/user/Maildir/foo/bar/new
|
||||
* and
|
||||
* /home/user/Maildir/foo/bar/new/abc and flags == MU_FLAG_REPLIED
|
||||
* => /home/user/Maildir/foo/bar/cur
|
||||
*
|
||||
* so only difference is whether MuFlags matches MU_FLAG_NEW is set or not
|
||||
*
|
||||
*/
|
||||
static gchar*
|
||||
get_new_path (const char *mdir, const char *mfile, MuFlags flags)
|
||||
{
|
||||
if (flags & MU_FLAG_NEW)
|
||||
return g_strdup_printf ("%s%cnew%c%s",
|
||||
mdir, G_DIR_SEPARATOR, G_DIR_SEPARATOR,
|
||||
mfile);
|
||||
else {
|
||||
const char *flagstr;
|
||||
flagstr = mu_flags_to_str_s (flags, MU_FLAG_TYPE_MAILFILE);
|
||||
|
||||
return g_strdup_printf ("%s%ccur%c%s:2,%s",
|
||||
mdir, G_DIR_SEPARATOR, G_DIR_SEPARATOR,
|
||||
mfile, flagstr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_maildir_get_maildir_from_path (const char* path)
|
||||
{
|
||||
gchar *mdir;
|
||||
|
||||
/* determine the maildir */
|
||||
mdir = g_path_get_dirname (path);
|
||||
if (!g_str_has_suffix (mdir, "cur") &&
|
||||
!g_str_has_suffix (mdir, "new")) {
|
||||
g_warning ("%s: not a valid maildir path: %s",
|
||||
__FUNCTION__, path);
|
||||
g_free (mdir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* remove the 'cur' or 'new' */
|
||||
mdir[strlen(mdir) - 4] = '\0';
|
||||
|
||||
return mdir;
|
||||
}
|
||||
|
||||
|
||||
|
||||
char*
|
||||
mu_maildir_get_new_path (const char *oldpath, const char *new_mdir,
|
||||
MuFlags newflags)
|
||||
{
|
||||
char *mfile, *mdir, *newpath, *cur;
|
||||
|
||||
g_return_val_if_fail (oldpath, NULL);
|
||||
|
||||
mfile = newpath = NULL;
|
||||
|
||||
/* determine the maildir */
|
||||
mdir = mu_maildir_get_maildir_from_path (oldpath);
|
||||
if (!mdir)
|
||||
return NULL;
|
||||
|
||||
/* determine the name of the mailfile, stripped of its flags */
|
||||
mfile = g_path_get_basename (oldpath);
|
||||
for (cur = &mfile[strlen(mfile)-1]; cur > mfile; --cur) {
|
||||
if ((*cur == ':' || *cur == '!') &&
|
||||
(cur[1] == '2' && cur[2] == ',')) {
|
||||
cur[0] = '\0'; /* strip the flags */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newpath = get_new_path (new_mdir ? new_mdir : mdir,
|
||||
mfile, newflags);
|
||||
|
||||
g_free (mfile);
|
||||
g_free (mdir);
|
||||
|
||||
return newpath;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
msg_move_check_pre (const gchar *src, const gchar *dst, GError **err)
|
||||
{
|
||||
if (!g_path_is_absolute(src)) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"source is not an absolute path: '%s'", src);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!g_path_is_absolute(dst)) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"target is not an absolute path: '%s'", dst);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (access (src, R_OK) != 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "cannot read %s",
|
||||
src);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (access (dst, F_OK) == 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "%s already exists",
|
||||
dst);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
msg_move_check_post (const char *src, const char *dst, GError **err)
|
||||
{
|
||||
/* double check -- is the target really there? */
|
||||
if (access (dst, F_OK) != 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "can't find target (%s)",
|
||||
dst);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (access (src, F_OK) == 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "source is still there (%s)",
|
||||
src);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
msg_move (const char* src, const char *dst, GError **err)
|
||||
{
|
||||
if (!msg_move_check_pre (src, dst, err))
|
||||
return FALSE;
|
||||
|
||||
if (rename (src, dst) != 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE, "error moving %s to %s",
|
||||
src, dst);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!msg_move_check_post (src, dst, err))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gchar*
|
||||
mu_maildir_move_message (const char* oldpath, const char* targetmdir,
|
||||
MuFlags newflags, gboolean ignore_dups,
|
||||
GError **err)
|
||||
{
|
||||
char *newfullpath;
|
||||
gboolean rv;
|
||||
gboolean src_is_target;
|
||||
|
||||
g_return_val_if_fail (oldpath, FALSE);
|
||||
|
||||
newfullpath = mu_maildir_get_new_path (oldpath, targetmdir,
|
||||
newflags);
|
||||
if (!newfullpath) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"failed to determine target full path");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
src_is_target = (g_strcmp0 (oldpath, newfullpath) == 0);
|
||||
|
||||
if (!ignore_dups && src_is_target) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_TARGET_EQUALS_SOURCE,
|
||||
"target equals source");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!src_is_target) {
|
||||
rv = msg_move (oldpath, newfullpath, err);
|
||||
if (!rv) {
|
||||
g_free (newfullpath);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return newfullpath;
|
||||
}
|
||||
204
lib/mu-maildir.h
Normal file
204
lib/mu-maildir.h
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2012 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MAILDIR_H__
|
||||
#define __MU_MAILDIR_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <time.h>
|
||||
#include <sys/types.h> /* for mode_t */
|
||||
#include <mu-util.h>
|
||||
#include <mu-flags.h>
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* create a new maildir. if parts of the maildir already exists, those
|
||||
* will simply be ignored. IOW, if you try to create the same maildir
|
||||
* twice, the second will simply be a no-op (without any errors).
|
||||
* Note, if the function fails 'halfway', it will *not* try to remove
|
||||
* the parts the were created. it *will* create any parent dirs that
|
||||
* are not yet existent.
|
||||
*
|
||||
*
|
||||
* @param path the path (missing components will be created, as in 'mkdir -p')
|
||||
* @param mode the file mode (e.g., 0755)
|
||||
* @param noindex add a .noindex file to the maildir, so it will be excluded
|
||||
* from indexing by 'mu index'
|
||||
* @param err if function returns FALSE, receives error
|
||||
* information. err may be NULL.
|
||||
*
|
||||
* @return TRUE if creation succeeded (or already existed), FALSE otherwise
|
||||
*/
|
||||
gboolean mu_maildir_mkdir (const char* path, mode_t mode, gboolean noindex,
|
||||
GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* create a symbolic link to a mail message
|
||||
*
|
||||
* @param src the full path to the source message
|
||||
* @param targetpath the path to the target maildir; ie., *not*
|
||||
* MyMaildir/cur, but just MyMaildir/. The function will figure out
|
||||
* the correct subdir then.
|
||||
* @param err if function returns FALSE, err may contain extra
|
||||
* information. if err is NULL, does nothing
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
gboolean mu_maildir_link (const char* src, const char *targetpath,
|
||||
GError **err);
|
||||
|
||||
/**
|
||||
* MuMaildirWalkMsgCallback -- callback function for
|
||||
* mu_path_walk_maildir; see the documentation there. It will be
|
||||
* called for each message found, with fullpath containing the full
|
||||
* path to the message, mdir containing the maildir -- that is, when
|
||||
* indexing ~/Maildir, a message ~/Maildir/foo/bar/cur/msg would have
|
||||
* the maildir "foo/bar". Then, the information from 'stat' of this
|
||||
* file (see stat(3)), and a user_data pointer
|
||||
*/
|
||||
typedef MuError (*MuMaildirWalkMsgCallback)
|
||||
(const char* fullpath, const char* mdir, struct stat *statinfo,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* MuPathWalkDirCallback -- callback function for mu_path_walk_maildir; see the
|
||||
* documentation there. It will be called each time a dir is entered or left,
|
||||
* with 'enter' being TRUE upon entering, FALSE otherwise
|
||||
*/
|
||||
typedef MuError (*MuMaildirWalkDirCallback)
|
||||
(const char* fullpath, gboolean enter, void *user_data);
|
||||
|
||||
/**
|
||||
* start a recursive walk of a maildir; for each file found, we call
|
||||
* callback with the path (with the Maildir path of scanner_new as
|
||||
* root), the filename, the timestamp (mtime) of the file,and the
|
||||
* *data pointer, for user data. dot-files are ignored, as well as
|
||||
* files outside cur/ and new/ dirs and unreadable files; however,
|
||||
* dotdirs are visited (ie. '.dotdir/cur'), so this enables Maildir++.
|
||||
* (http://www.inter7.com/courierimap/README.maildirquota.html, search
|
||||
* for 'Mission statement'). In addition, dirs containing a file named
|
||||
* '.noindex' are ignored, as are their subdirectories.
|
||||
*
|
||||
* mu_walk_maildir stops if the callbacks return something different
|
||||
* from MU_OK. For example, it can return MU_STOP to stop the scan, or
|
||||
* some error.
|
||||
*
|
||||
* @param path the maildir path to scan
|
||||
* @param cb_msg the callback function called for each msg
|
||||
* @param cb_dir the callback function called for each dir
|
||||
* @param data user data pointer
|
||||
*
|
||||
* @return a scanner result; MU_OK if everything went ok,
|
||||
* MU_STOP if we want to stop, or MU_ERROR in
|
||||
* case of error
|
||||
*/
|
||||
MuError mu_maildir_walk (const char *path, MuMaildirWalkMsgCallback cb_msg,
|
||||
MuMaildirWalkDirCallback cb_dir, void *data);
|
||||
/**
|
||||
* recursively delete all the symbolic links in a directory tree
|
||||
*
|
||||
* @param dir top dir
|
||||
* @param err if function returns FALSE, err may contain extra
|
||||
* information. if err is NULL, does nothing
|
||||
*
|
||||
* @return TRUE if it worked, FALSE in case of error
|
||||
*/
|
||||
gboolean mu_maildir_clear_links (const gchar* dir, GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* get the Maildir flags from the full path of a mailfile. The flags
|
||||
* are as specified in http://cr.yp.to/proto/maildir.html, plus
|
||||
* MU_MSG_FLAG_NEW for new messages, ie the ones that live in
|
||||
* new/. The flags are logically OR'ed. Note that the file does not
|
||||
* have to exist; the flags are based on the path only.
|
||||
*
|
||||
* @param pathname of a mailfile; it does not have to refer to an
|
||||
* actual message
|
||||
*
|
||||
* @return the flags, or MU_MSG_FILE_FLAG_UNKNOWN in case of error
|
||||
*/
|
||||
MuFlags mu_maildir_get_flags_from_path (const char* pathname);
|
||||
|
||||
/**
|
||||
* get the new pathname for a message, based on the old path and the
|
||||
* new flags and (optionally) a new maildir. Note that
|
||||
* setting/removing the MU_FLAG_NEW will change the directory in which
|
||||
* a message lives. The flags are as specified in
|
||||
* http://cr.yp.to/proto/maildir.html, plus MU_FLAG_NEW for new
|
||||
* messages, ie the ones that live in new/. The flags are logically
|
||||
* OR'ed. Note that the file does not have to exist; the flags are
|
||||
* based on the path only.
|
||||
*
|
||||
*
|
||||
* @param oldpath the old (current) full path to the message
|
||||
* (including the filename)
|
||||
* @param new_mdir the new maildir for this message, or NULL to keep
|
||||
* it in the current one. The maildir is the absolute file system
|
||||
* path, without the 'cur' or 'new'
|
||||
* @param new_flags the new flags for this message
|
||||
*
|
||||
* @return a new path name; use g_free when done with. NULL in case of
|
||||
* error.
|
||||
*/
|
||||
char* mu_maildir_get_new_path (const char *oldpath, const char *new_mdir,
|
||||
MuFlags new_flags);
|
||||
|
||||
/**
|
||||
* get the maildir for a certain message path, ie, the path *before*
|
||||
* cur/ or new/
|
||||
*
|
||||
* @param path path for some message
|
||||
*
|
||||
* @return the maildir (free with g_free), or NULL in case of error
|
||||
*/
|
||||
char* mu_maildir_get_maildir_from_path (const char* path);
|
||||
|
||||
|
||||
/**
|
||||
* move a message file to another maildir; the function returns the full
|
||||
* path to the new message.
|
||||
*
|
||||
* @param msgpath an absolute file system path to an existing message in an
|
||||
* actual maildir
|
||||
* @param targetmdir the target maildir; note that this the base-level
|
||||
* Maildir, ie. /home/user/Maildir/archive, and must _not_ include the
|
||||
* 'cur' or 'new' part. Note that the target maildir must be on the
|
||||
* same filesystem. If you specify NULL for targetmdir, only the flags
|
||||
* of the message are affected; note that this may still involve a
|
||||
* moved to another directory (say, from new/ to cur/)
|
||||
* @param flags to set for the target (influences the filename, path)
|
||||
* @param ignore_dups whether to silent ignore the src=target case (and return TRUE)
|
||||
* @param err (may be NULL) may contain error information; note if the
|
||||
* function return FALSE, err is not set for all error condition
|
||||
* (ie. not for parameter errors)
|
||||
*
|
||||
* @return return the full path name of the target file (g_free) if
|
||||
* the move succeeded, NULL otherwise
|
||||
*/
|
||||
gchar* mu_maildir_move_message (const char* oldpath, const char* targetmdir,
|
||||
MuFlags newflags, gboolean ignore_dups,
|
||||
GError **err);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MAILDIR_H__*/
|
||||
262
lib/mu-msg-cache.c
Normal file
262
lib/mu-msg-cache.c
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include "mu-flags.h"
|
||||
#include "mu-msg-prio.h"
|
||||
#include "mu-msg-cache.h"
|
||||
#include "mu-str.h"
|
||||
|
||||
struct _MuMsgCache {
|
||||
|
||||
/* all string properties */
|
||||
char *_str[MU_MSG_STRING_FIELD_ID_NUM];
|
||||
|
||||
GSList *_refs, *_tags;
|
||||
|
||||
time_t _timestamp, _date;
|
||||
size_t _size;
|
||||
MuFlags _flags;
|
||||
MuMsgPrio _prio;
|
||||
|
||||
/* <private> */
|
||||
unsigned _cached, _allocated;
|
||||
};
|
||||
|
||||
/* _cached and _allocated have a bit for each MuMsgFieldId to remember
|
||||
* which ones have been cached, and which ones have been allocated. */
|
||||
|
||||
#define is_allocated(C,MFID) ((C)->_allocated & (1 << (MFID)))
|
||||
#define is_cached(C,MFID) ((C)->_cached & (1 << (MFID)))
|
||||
|
||||
#define set_allocated(C,MFID) ((C)->_allocated |= (1 << (MFID)))
|
||||
#define set_cached(C,MFID) ((C)->_cached |= (1 << (MFID)))
|
||||
|
||||
#define reset_allocated(C,MFID) ((C)->_allocated &= ~(1 << (MFID)))
|
||||
#define reset_cached(C,MFID) ((C)->_cached &= ~(1 << (MFID)))
|
||||
|
||||
|
||||
static void
|
||||
cache_clear (MuMsgCache *self)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i != MU_MSG_STRING_FIELD_ID_NUM; ++i)
|
||||
self->_str[i] = NULL;
|
||||
|
||||
self->_timestamp = (time_t)-1;
|
||||
self->_size = (size_t)-1;
|
||||
self->_flags = MU_FLAG_NONE;
|
||||
self->_prio = MU_MSG_PRIO_NONE;
|
||||
self->_date = (time_t)-1;
|
||||
|
||||
self->_refs = self->_tags = NULL;
|
||||
|
||||
self->_cached = 0;
|
||||
self->_allocated = 0;
|
||||
}
|
||||
|
||||
|
||||
MuMsgCache*
|
||||
mu_msg_cache_new (void)
|
||||
{
|
||||
MuMsgCache *self;
|
||||
|
||||
self = g_slice_new0 (MuMsgCache);
|
||||
cache_clear (self);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_cache_destroy (MuMsgCache *self)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
g_return_if_fail (self);
|
||||
|
||||
for (i = 0; i != MU_MSG_STRING_FIELD_ID_NUM; ++i)
|
||||
if (is_allocated(self, i))
|
||||
g_free (self->_str[i]);
|
||||
|
||||
mu_str_free_list (self->_tags);
|
||||
mu_str_free_list (self->_refs);
|
||||
|
||||
g_slice_free (MuMsgCache, self);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_cache_set_str (MuMsgCache *self, MuMsgFieldId mfid, char *str,
|
||||
gboolean do_free)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (mu_msg_field_is_string(mfid), NULL);
|
||||
|
||||
/* maybe there was an old, allocated value there already? */
|
||||
if (is_allocated(self, mfid))
|
||||
g_free (self->_str[mfid]);
|
||||
|
||||
self->_str[mfid] = str;
|
||||
set_cached (self, mfid);
|
||||
|
||||
if (do_free)
|
||||
set_allocated (self, mfid);
|
||||
else
|
||||
reset_allocated (self, mfid);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_cache_str (MuMsgCache *cache, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_is_string(mfid), NULL);
|
||||
return cache->_str[mfid];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const GSList*
|
||||
mu_msg_cache_set_str_list (MuMsgCache *self, MuMsgFieldId mfid,
|
||||
GSList *lst, gboolean do_free)
|
||||
{
|
||||
g_return_val_if_fail(self, NULL);
|
||||
g_return_val_if_fail(mu_msg_field_is_string_list(mfid), NULL);
|
||||
|
||||
switch (mfid) {
|
||||
case MU_MSG_FIELD_ID_REFS:
|
||||
if (is_allocated(self, mfid))
|
||||
mu_str_free_list (self->_refs);
|
||||
self->_refs = lst;
|
||||
break;
|
||||
|
||||
case MU_MSG_FIELD_ID_TAGS:
|
||||
if (is_allocated(self, mfid))
|
||||
mu_str_free_list (self->_tags);
|
||||
self->_tags = lst;
|
||||
break;
|
||||
default:
|
||||
g_return_val_if_reached(NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
set_cached (self, mfid);
|
||||
|
||||
if (do_free)
|
||||
set_allocated (self, mfid);
|
||||
else
|
||||
reset_allocated (self, mfid);
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
|
||||
const GSList*
|
||||
mu_msg_cache_str_list (MuMsgCache *self, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_is_string_list(mfid), NULL);
|
||||
|
||||
switch (mfid) {
|
||||
case MU_MSG_FIELD_ID_REFS:
|
||||
return self->_refs;
|
||||
default:
|
||||
g_return_val_if_reached(NULL);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gint64
|
||||
mu_msg_cache_set_num (MuMsgCache *self, MuMsgFieldId mfid, gint64 val)
|
||||
{
|
||||
g_return_val_if_fail(self, -1);
|
||||
g_return_val_if_fail(mu_msg_field_is_numeric(mfid), -1);
|
||||
|
||||
switch (mfid) {
|
||||
case MU_MSG_FIELD_ID_DATE:
|
||||
self->_date = (time_t)val;
|
||||
break;
|
||||
case MU_MSG_FIELD_ID_PRIO:
|
||||
self->_prio = (MuMsgPrio)val;
|
||||
break;
|
||||
case MU_MSG_FIELD_ID_FLAGS:
|
||||
self->_flags = (MuFlags)val;
|
||||
break;
|
||||
case MU_MSG_FIELD_ID_SIZE:
|
||||
self->_size = (size_t)val;
|
||||
break;
|
||||
default:
|
||||
g_return_val_if_reached(-1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_cached (self, mfid);
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
gint64
|
||||
mu_msg_cache_num (MuMsgCache *self, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail(mu_msg_field_is_numeric(mfid), -1);
|
||||
|
||||
switch (mfid) {
|
||||
case MU_MSG_FIELD_ID_DATE:
|
||||
return (gint64)self->_date;
|
||||
case MU_MSG_FIELD_ID_PRIO:
|
||||
return (gint64)self->_prio;
|
||||
case MU_MSG_FIELD_ID_FLAGS:
|
||||
return (gint64)self->_flags;
|
||||
case MU_MSG_FIELD_ID_SIZE:
|
||||
return (gint64)self->_size;
|
||||
default: g_return_val_if_reached(-1);
|
||||
}
|
||||
|
||||
set_cached (self, mfid);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_cache_cached (MuMsgCache *self, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (mfid < MU_MSG_FIELD_ID_NUM, FALSE);
|
||||
return is_cached(self, mfid) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_cache_allocate_all (MuMsgCache *self)
|
||||
{
|
||||
int mfid;
|
||||
|
||||
g_return_if_fail (self);
|
||||
|
||||
for (mfid = 0; mfid != MU_MSG_STRING_FIELD_ID_NUM; ++mfid) {
|
||||
if (self->_str[mfid] && !is_allocated(self, mfid)) {
|
||||
self->_str[mfid] = g_strdup (self->_str[mfid]);
|
||||
set_allocated(self, mfid);
|
||||
}
|
||||
}
|
||||
}
|
||||
159
lib/mu-msg-cache.h
Normal file
159
lib/mu-msg-cache.h
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_CACHE_H__
|
||||
#define __MU_MSG_CACHE_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <mu-msg-fields.h>
|
||||
|
||||
/* MuMsgCache caches all values for one specific MuMsg, whether its
|
||||
* backend is a MuMsgFile or MuMsgDb. The idea is to take all the
|
||||
* values from the MuMsg so we can release the backend (file or db).
|
||||
*
|
||||
* It is specifically designed minimize memory allocations; you can
|
||||
* either store dynamically-allocated strings, of which the cache will
|
||||
* take ownership (and g_free in the end), *or* use strings, either
|
||||
* static or owned elsewhere. In the later case, no copy will be made
|
||||
* until mu_msg_cache_allocate_all is called
|
||||
*
|
||||
* Minimizing allocations in esp. necessary when storing
|
||||
* search-results as a list of MuMsg instances
|
||||
*/
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuMsgCache;
|
||||
typedef struct _MuMsgCache MuMsgCache;
|
||||
|
||||
/**
|
||||
* initialize the cache
|
||||
*
|
||||
* @param self ptr to a cache struct
|
||||
*/
|
||||
MuMsgCache *mu_msg_cache_new (void);
|
||||
|
||||
/**
|
||||
* free the strings in the cache
|
||||
*
|
||||
* @param self ptr to a cache struct
|
||||
*/
|
||||
void mu_msg_cache_destroy (MuMsgCache *self);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* add a string value to the cache; it will *not* be freed
|
||||
*
|
||||
* @param self a cache struc
|
||||
* @param mfid the MessageFieldId
|
||||
* @param str the string value to set
|
||||
* @param do_free whether the cache should free this value (i.e, take ownership)
|
||||
*
|
||||
* @return the cached value (this is for nesting function calls)
|
||||
*/
|
||||
const char* mu_msg_cache_set_str (MuMsgCache *self, MuMsgFieldId mfid,
|
||||
char *str, gboolean do_free);
|
||||
|
||||
|
||||
/**
|
||||
* get a string value from the cache
|
||||
*
|
||||
* @param self ptr to a MuMsgCache
|
||||
* @param mfid the MessageFieldId for a string value
|
||||
*
|
||||
* @return the string, or NULL. Don't modify.
|
||||
*/
|
||||
const char* mu_msg_cache_str (MuMsgCache *cache, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* cache a string-list value
|
||||
*
|
||||
* @param self a mumsgcache
|
||||
* @param mfid the MessageFieldId for a string-list value
|
||||
* @param lst the list
|
||||
* @param do_free whether the cache should free this value (i.e, take ownership)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
const GSList* mu_msg_cache_set_str_list (MuMsgCache *self, MuMsgFieldId mfid,
|
||||
GSList *lst, gboolean do_free);
|
||||
|
||||
/**
|
||||
* get a string-list value from the cache
|
||||
*
|
||||
* @param cache a MuMsgCache
|
||||
* @param mfid the MessageFieldId for string-list value
|
||||
*
|
||||
* @return a list, which should not be modified
|
||||
*/
|
||||
const GSList* mu_msg_cache_str_list (MuMsgCache *cache, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
/**
|
||||
* add a numeric value to the cache
|
||||
*
|
||||
* @param self a MuMsgCache ptr
|
||||
* @param mfid the MessageFieldId for a numeric value
|
||||
* @param val the value
|
||||
*
|
||||
* @return the cached value (this is for nesting function calls)
|
||||
*
|
||||
*/
|
||||
gint64 mu_msg_cache_set_num (MuMsgCache *self, MuMsgFieldId mfid, gint64 val);
|
||||
|
||||
|
||||
/**
|
||||
* get a numeric value from the cache
|
||||
*
|
||||
* @param self a MuMsgCache ptr
|
||||
* @param mfid the MessageFieldId for a numeric value
|
||||
*
|
||||
* @return a numeric value
|
||||
*/
|
||||
gint64 mu_msg_cache_num (MuMsgCache *self, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* is the value cached already?
|
||||
*
|
||||
* @param self a MuMsgCache ptr
|
||||
* @param mfid the MessageFieldId for a numeric value
|
||||
*
|
||||
* @return TRUE if the value is cached, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_cache_cached (MuMsgCache *self, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* copy all data that was not already owned by the cache
|
||||
*
|
||||
* @param self a MuMsgCache ptr
|
||||
*/
|
||||
void mu_msg_cache_allocate_all (MuMsgCache *self);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_CACHE_H__*/
|
||||
|
||||
126
lib/mu-msg-doc.cc
Normal file
126
lib/mu-msg-doc.cc
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <xapian.h>
|
||||
|
||||
#include "mu-util.h"
|
||||
#include "mu-msg-fields.h"
|
||||
#include "mu-msg-doc.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-date.h"
|
||||
|
||||
struct _MuMsgDoc {
|
||||
|
||||
_MuMsgDoc (Xapian::Document *doc): _doc (doc) { }
|
||||
~_MuMsgDoc () { delete _doc; }
|
||||
const Xapian::Document doc() const { return *_doc; }
|
||||
private:
|
||||
Xapian::Document *_doc;
|
||||
};
|
||||
|
||||
|
||||
MuMsgDoc*
|
||||
mu_msg_doc_new (XapianDocument *doc, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (doc, NULL);
|
||||
|
||||
try {
|
||||
return new MuMsgDoc ((Xapian::Document*)doc);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN, NULL);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
mu_msg_doc_destroy (MuMsgDoc *self)
|
||||
{
|
||||
try {
|
||||
delete self;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_msg_doc_get_str_field (MuMsgDoc *self, MuMsgFieldId mfid,
|
||||
gboolean *do_free)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), NULL);
|
||||
g_return_val_if_fail (mu_msg_field_is_string(mfid), NULL);
|
||||
|
||||
*do_free = TRUE;
|
||||
|
||||
try {
|
||||
const std::string s (self->doc().get_value(mfid));
|
||||
return s.empty() ? NULL : g_strdup (s.c_str());
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(NULL);
|
||||
}
|
||||
|
||||
|
||||
GSList*
|
||||
mu_msg_doc_get_str_list_field (MuMsgDoc *self, MuMsgFieldId mfid,
|
||||
gboolean *do_free)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), NULL);
|
||||
g_return_val_if_fail (mu_msg_field_is_string_list(mfid), NULL);
|
||||
|
||||
*do_free = TRUE;
|
||||
|
||||
try {
|
||||
/* return a comma-separated string as a GSList */
|
||||
const std::string s (self->doc().get_value(mfid));
|
||||
return s.empty() ? NULL : mu_str_to_list(s.c_str(),',',TRUE);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(NULL);
|
||||
}
|
||||
|
||||
|
||||
gint64
|
||||
mu_msg_doc_get_num_field (MuMsgDoc *self, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (self, -1);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), -1);
|
||||
g_return_val_if_fail (mu_msg_field_is_numeric(mfid), -1);
|
||||
|
||||
/* date is a special case, because we store dates as
|
||||
* strings */
|
||||
try {
|
||||
const std::string s (self->doc().get_value(mfid));
|
||||
if (s.empty())
|
||||
return 0;
|
||||
else if (mfid == MU_MSG_FIELD_ID_DATE) {
|
||||
time_t t;
|
||||
t = mu_date_str_to_time_t (s.c_str(), FALSE/*utc*/);
|
||||
return static_cast<gint64>(t);
|
||||
} else {
|
||||
return static_cast<gint64>(Xapian::sortable_unserialise(s));
|
||||
}
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(-1);
|
||||
}
|
||||
|
||||
|
||||
105
lib/mu-msg-doc.h
Normal file
105
lib/mu-msg-doc.h
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_DOC_H__
|
||||
#define __MU_MSG_DOC_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <mu-util.h> /* for XapianDocument */
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuMsgDoc;
|
||||
typedef struct _MuMsgDoc MuMsgDoc;
|
||||
|
||||
/**
|
||||
* create a new MuMsgDoc instance
|
||||
*
|
||||
* @param doc a Xapian::Document* (you'll need to cast the
|
||||
* Xapian::Document* to XapianDocument*, because only C (not C++) is
|
||||
* allowed in this header file. MuMsgDoc takes _ownership_ of this pointer;
|
||||
* don't touch it afterwards
|
||||
* @param err receives error info, or NULL
|
||||
*
|
||||
* @return a new MuMsgDoc instance (free with mu_msg_doc_destroy), or
|
||||
* NULL in case of error.
|
||||
*/
|
||||
MuMsgDoc* mu_msg_doc_new (XapianDocument *doc, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* destroy a MuMsgDoc instance -- free all the resources. Note, after
|
||||
* destroying, any strings returned from mu_msg_doc_get_str_field with
|
||||
* do_free==FALSE are no longer valid
|
||||
*
|
||||
* @param self a MuMsgDoc instance
|
||||
*/
|
||||
void mu_msg_doc_destroy (MuMsgDoc *self);
|
||||
|
||||
|
||||
/**
|
||||
* get a string parameter from the msgdoc
|
||||
*
|
||||
* @param self a MuMsgDoc instance
|
||||
* @param mfid a MuMsgFieldId for a string field
|
||||
* @param do_free receives either TRUE or FALSE, where TRUE means that
|
||||
* the caller owns the string, and has to free it (g_free) when done
|
||||
* with it; FALSE means that the MuMsgDoc owns the string, and it is
|
||||
* only valid as long as the MuMsgDoc is valid (ie., before
|
||||
* mu_msg_doc_destroy).
|
||||
*
|
||||
* @return a string for the given field (see do_free), or NULL in case of error
|
||||
*/
|
||||
gchar* mu_msg_doc_get_str_field (MuMsgDoc *self, MuMsgFieldId mfid,
|
||||
gboolean *do_free)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* get a string-list parameter from the msgdoc
|
||||
*
|
||||
* @param self a MuMsgDoc instance
|
||||
* @param mfid a MuMsgFieldId for a string-list field
|
||||
* @param do_free receives either TRUE or FALSE, where TRUE means that
|
||||
* the caller owns the string, and has to free it (mu_str_free_list) when done
|
||||
* with it; FALSE means that the MuMsgDoc owns the list, and it is
|
||||
* only valid as long as the MuMsgDoc is valid (ie., before
|
||||
* mu_msg_doc_destroy).
|
||||
*
|
||||
* @return a list for the given field (see do_free), or NULL in case of error
|
||||
*/
|
||||
GSList* mu_msg_doc_get_str_list_field (MuMsgDoc *self, MuMsgFieldId mfid,
|
||||
gboolean *do_free) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* get a numeric parameter from the msgdoc
|
||||
*
|
||||
* @param self a MuMsgDoc instance
|
||||
* @param mfid a MuMsgFieldId for a numeric field
|
||||
*
|
||||
* @return the numerical value, or -1 in case of error. You'll need to
|
||||
* cast this value to the actual type (e.g. time_t for MU_MSG_FIELD_ID_DATE)
|
||||
*/
|
||||
gint64 mu_msg_doc_get_num_field (MuMsgDoc *self, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_DOC_H__*/
|
||||
447
lib/mu-msg-fields.c
Normal file
447
lib/mu-msg-fields.c
Normal file
@ -0,0 +1,447 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "mu-msg-fields.h"
|
||||
|
||||
/*
|
||||
* note: the differences for our purposes between a xapian field and a
|
||||
* term: - there is only a single value for some item in per document
|
||||
* (msg), ie. one value containing the list of To: addresses - there
|
||||
* can be multiple terms, each containing e.g. one of the To:
|
||||
* addresses - searching uses terms, but to display some field, it
|
||||
* must be in the value (at least when using MuMsgIter)
|
||||
*/
|
||||
enum _FieldFlags {
|
||||
FLAG_GMIME = 1 << 0, /* field retrieved through
|
||||
* gmime */
|
||||
FLAG_XAPIAN_INDEX = 1 << 1, /* field is indexed in
|
||||
* xapian (i.e., the text
|
||||
* is processed */
|
||||
FLAG_XAPIAN_TERM = 1 << 2, /* field stored as term in
|
||||
* xapian (so it can be searched) */
|
||||
FLAG_XAPIAN_VALUE = 1 << 3, /* field stored as value in
|
||||
* xapian (so the literal
|
||||
* value can be
|
||||
* retrieved) */
|
||||
FLAG_XAPIAN_CONTACT = 1 << 4, /* field contains one or more
|
||||
* e-mail-addresses */
|
||||
FLAG_XAPIAN_ESCAPE = 1 << 5, /* field needs escaping for
|
||||
* xapian (so the xapian
|
||||
* query does not get
|
||||
* confused) */
|
||||
FLAG_XAPIAN_BOOLEAN = 1 << 6, /* use 'add_boolean_prefix'
|
||||
* for Xapian queries;
|
||||
* wildcards do NOT WORK
|
||||
* for such fields */
|
||||
FLAG_XAPIAN_PREFIX_ONLY = 1 << 7, /* whether this fields
|
||||
* matches only when the
|
||||
* prefix is explicitly
|
||||
* included in the search
|
||||
* query -- e.g., the text
|
||||
* body */
|
||||
FLAG_NORMALIZE = 1 << 8, /* field needs flattening for
|
||||
* case/accents */
|
||||
FLAG_DONT_CACHE = 1 << 9, /* don't cache this field in
|
||||
* the MuMsg cache */
|
||||
FLAG_RANGE_FIELD = 1 << 10 /* whether this is a range field */
|
||||
|
||||
};
|
||||
typedef enum _FieldFlags FieldFlags;
|
||||
|
||||
/*
|
||||
* this struct describes the fields of an e-mail
|
||||
/*/
|
||||
struct _MuMsgField {
|
||||
MuMsgFieldId _id; /* the id of the field */
|
||||
MuMsgFieldType _type; /* the type of the field */
|
||||
const char *_name; /* the name of the field */
|
||||
const char _shortcut; /* the shortcut for use in
|
||||
* --fields and sorting */
|
||||
const char _xprefix; /* the Xapian-prefix */
|
||||
FieldFlags _flags; /* the flags that tells us
|
||||
* what to do */
|
||||
|
||||
|
||||
};
|
||||
typedef struct _MuMsgField MuMsgField;
|
||||
|
||||
/* the name and shortcut fields must be lower case, or they might be
|
||||
* misinterpreted by the query-preprocesser which turns queries into
|
||||
* lowercase */
|
||||
static const MuMsgField FIELD_DATA[] = {
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_BCC,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"bcc" , 'h', 'H', /* 'hidden */
|
||||
FLAG_GMIME | FLAG_XAPIAN_CONTACT |
|
||||
FLAG_XAPIAN_VALUE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_BODY_TEXT,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"body", 'b', 'B',
|
||||
FLAG_GMIME | FLAG_XAPIAN_INDEX | FLAG_NORMALIZE |
|
||||
FLAG_DONT_CACHE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_BODY_HTML,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"bodyhtml", 'h', 0,
|
||||
FLAG_GMIME | FLAG_DONT_CACHE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_CC,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"cc", 'c', 'C',
|
||||
FLAG_GMIME | FLAG_XAPIAN_CONTACT | FLAG_XAPIAN_VALUE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_DATE,
|
||||
MU_MSG_FIELD_TYPE_TIME_T,
|
||||
"date", 'd', 'D',
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_BOOLEAN | FLAG_XAPIAN_PREFIX_ONLY | FLAG_RANGE_FIELD
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_EMBEDDED_TEXT,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"embed", 'e', 'E',
|
||||
FLAG_GMIME | FLAG_XAPIAN_INDEX | FLAG_NORMALIZE |
|
||||
FLAG_DONT_CACHE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_FILE,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"file" , 'j', 'J',
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_ESCAPE |
|
||||
FLAG_DONT_CACHE | FLAG_XAPIAN_PREFIX_ONLY
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_FLAGS,
|
||||
MU_MSG_FIELD_TYPE_INT,
|
||||
"flag", 'g', 'G', /* flaGs */
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_PREFIX_ONLY
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_FROM,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"from", 'f', 'F',
|
||||
FLAG_GMIME | FLAG_XAPIAN_CONTACT | FLAG_XAPIAN_VALUE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_PATH,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"path", 'l', 'L', /* 'l' for location */
|
||||
FLAG_GMIME | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_BOOLEAN | FLAG_XAPIAN_PREFIX_ONLY |
|
||||
FLAG_XAPIAN_ESCAPE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_MAILDIR,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"maildir", 'm', 'M',
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_ESCAPE | FLAG_XAPIAN_PREFIX_ONLY
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_MIME,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"mime" , 'y', 'Y',
|
||||
FLAG_XAPIAN_TERM | FLAG_XAPIAN_ESCAPE | FLAG_XAPIAN_PREFIX_ONLY
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_PRIO,
|
||||
MU_MSG_FIELD_TYPE_INT,
|
||||
"prio", 'p', 'P',
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_PREFIX_ONLY
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_SIZE,
|
||||
MU_MSG_FIELD_TYPE_BYTESIZE,
|
||||
"size", 'z', 'Z', /* siZe */
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_PREFIX_ONLY | FLAG_RANGE_FIELD
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_SUBJECT,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"subject", 's', 'S',
|
||||
FLAG_GMIME | FLAG_XAPIAN_INDEX | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_TERM | FLAG_XAPIAN_ESCAPE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_TO,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"to", 't', 'T',
|
||||
FLAG_GMIME | FLAG_XAPIAN_CONTACT | FLAG_XAPIAN_VALUE
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_MSGID,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"msgid", 'i', 'I', /* 'i' for Id */
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_VALUE |
|
||||
FLAG_XAPIAN_ESCAPE | FLAG_XAPIAN_PREFIX_ONLY
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_REFS,
|
||||
MU_MSG_FIELD_TYPE_STRING_LIST,
|
||||
NULL, 'r', 'R',
|
||||
FLAG_GMIME | FLAG_XAPIAN_VALUE | FLAG_XAPIAN_PREFIX_ONLY
|
||||
},
|
||||
|
||||
{
|
||||
MU_MSG_FIELD_ID_TAGS,
|
||||
MU_MSG_FIELD_TYPE_STRING_LIST,
|
||||
"tag", 'x', 'X',
|
||||
FLAG_GMIME | FLAG_XAPIAN_TERM | FLAG_XAPIAN_PREFIX_ONLY |
|
||||
FLAG_XAPIAN_ESCAPE
|
||||
},
|
||||
|
||||
{ /* special, internal field, to get a unique key */
|
||||
MU_MSG_FIELD_ID_UID,
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
"uid", 0, 'U',
|
||||
FLAG_XAPIAN_TERM | FLAG_XAPIAN_PREFIX_ONLY
|
||||
}
|
||||
|
||||
/* note, mu-store also use the 'Q' internal prefix for its uids */
|
||||
};
|
||||
|
||||
/* the MsgField data in an array, indexed by the MsgFieldId;
|
||||
* this allows for O(1) access
|
||||
*/
|
||||
static MuMsgField* _msg_field_data[MU_MSG_FIELD_ID_NUM];
|
||||
static const MuMsgField* mu_msg_field (MuMsgFieldId id)
|
||||
{
|
||||
static gboolean _initialized = FALSE;
|
||||
|
||||
/* initialize the array, but only once... */
|
||||
if (G_UNLIKELY(!_initialized)) {
|
||||
int i;
|
||||
for (i = 0; i != G_N_ELEMENTS(FIELD_DATA); ++i)
|
||||
_msg_field_data[FIELD_DATA[i]._id] =
|
||||
(MuMsgField*)&FIELD_DATA[i];
|
||||
_initialized = TRUE;
|
||||
}
|
||||
|
||||
return _msg_field_data[id];
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_field_foreach (MuMsgFieldForeachFunc func, gconstpointer data)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i != MU_MSG_FIELD_ID_NUM; ++i)
|
||||
func (i, data);
|
||||
}
|
||||
|
||||
|
||||
MuMsgFieldId
|
||||
mu_msg_field_id_from_name (const char* str, gboolean err)
|
||||
{
|
||||
int i;
|
||||
|
||||
g_return_val_if_fail (str, MU_MSG_FIELD_ID_NONE);
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(FIELD_DATA); ++i)
|
||||
if (g_strcmp0(str, FIELD_DATA[i]._name) == 0)
|
||||
return FIELD_DATA[i]._id;
|
||||
|
||||
if (err)
|
||||
g_return_val_if_reached (MU_MSG_FIELD_ID_NONE);
|
||||
|
||||
return MU_MSG_FIELD_ID_NONE;
|
||||
}
|
||||
|
||||
|
||||
MuMsgFieldId
|
||||
mu_msg_field_id_from_shortcut (char kar, gboolean err)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i != G_N_ELEMENTS(FIELD_DATA); ++i)
|
||||
if (kar == FIELD_DATA[i]._shortcut)
|
||||
return FIELD_DATA[i]._id;
|
||||
|
||||
if (err)
|
||||
g_return_val_if_reached (MU_MSG_FIELD_ID_NONE);
|
||||
|
||||
return MU_MSG_FIELD_ID_NONE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_gmime (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_GMIME ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_xapian_index (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_XAPIAN_INDEX ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_msg_field_xapian_value (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_XAPIAN_VALUE ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_msg_field_xapian_term (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_XAPIAN_TERM ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_is_range_field (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_RANGE_FIELD ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_uses_boolean_prefix (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_XAPIAN_BOOLEAN?TRUE:FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_needs_prefix (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags &
|
||||
FLAG_XAPIAN_PREFIX_ONLY ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_is_cacheable (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
/* note the FALSE: TRUE */
|
||||
return mu_msg_field(id)->_flags & FLAG_DONT_CACHE ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_msg_field_xapian_contact (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_XAPIAN_CONTACT ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_normalize (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_NORMALIZE ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_msg_field_xapian_escape (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),FALSE);
|
||||
return mu_msg_field(id)->_flags & FLAG_XAPIAN_ESCAPE ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_field_is_numeric (MuMsgFieldId mfid)
|
||||
{
|
||||
MuMsgFieldType type;
|
||||
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(mfid),FALSE);
|
||||
|
||||
type = mu_msg_field_type (mfid);
|
||||
|
||||
return type == MU_MSG_FIELD_TYPE_BYTESIZE ||
|
||||
type == MU_MSG_FIELD_TYPE_TIME_T ||
|
||||
type == MU_MSG_FIELD_TYPE_INT;
|
||||
}
|
||||
|
||||
const char*
|
||||
mu_msg_field_name (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),NULL);
|
||||
return mu_msg_field(id)->_name;
|
||||
}
|
||||
|
||||
|
||||
char
|
||||
mu_msg_field_shortcut (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),0);
|
||||
return mu_msg_field(id)->_shortcut;
|
||||
}
|
||||
|
||||
|
||||
char
|
||||
mu_msg_field_xapian_prefix (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),0);
|
||||
return mu_msg_field(id)->_xprefix;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
MuMsgFieldType
|
||||
mu_msg_field_type (MuMsgFieldId id)
|
||||
{
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(id),
|
||||
MU_MSG_FIELD_TYPE_NONE);
|
||||
return mu_msg_field(id)->_type;
|
||||
}
|
||||
318
lib/mu-msg-fields.h
Normal file
318
lib/mu-msg-fields.h
Normal file
@ -0,0 +1,318 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_FIELDS_H__
|
||||
#define __MU_MSG_FIELDS_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* don't change the order, add new types at the end, as these numbers
|
||||
* are used in the database */
|
||||
enum _MuMsgFieldId {
|
||||
|
||||
/* first all the string-based ones */
|
||||
MU_MSG_FIELD_ID_BCC = 0,
|
||||
MU_MSG_FIELD_ID_BODY_HTML,
|
||||
MU_MSG_FIELD_ID_BODY_TEXT,
|
||||
MU_MSG_FIELD_ID_CC,
|
||||
MU_MSG_FIELD_ID_EMBEDDED_TEXT,
|
||||
MU_MSG_FIELD_ID_FILE,
|
||||
MU_MSG_FIELD_ID_FROM,
|
||||
MU_MSG_FIELD_ID_MAILDIR,
|
||||
MU_MSG_FIELD_ID_MIME, /* mime-type */
|
||||
MU_MSG_FIELD_ID_MSGID,
|
||||
MU_MSG_FIELD_ID_PATH,
|
||||
MU_MSG_FIELD_ID_SUBJECT,
|
||||
MU_MSG_FIELD_ID_TO,
|
||||
|
||||
MU_MSG_FIELD_ID_UID, /* special, generated from path */
|
||||
|
||||
/* MU_MSG_STRING_FIELD_ID_NUM, see below */
|
||||
|
||||
/* string list items... */
|
||||
MU_MSG_FIELD_ID_REFS,
|
||||
MU_MSG_FIELD_ID_TAGS,
|
||||
|
||||
/* then the numerical ones */
|
||||
MU_MSG_FIELD_ID_DATE,
|
||||
MU_MSG_FIELD_ID_FLAGS,
|
||||
MU_MSG_FIELD_ID_PRIO,
|
||||
MU_MSG_FIELD_ID_SIZE,
|
||||
|
||||
MU_MSG_FIELD_ID_NUM
|
||||
};
|
||||
typedef guint8 MuMsgFieldId;
|
||||
|
||||
/* some specials... */
|
||||
static const MuMsgFieldId MU_MSG_FIELD_ID_NONE = (MuMsgFieldId)-1;
|
||||
#define MU_MSG_STRING_FIELD_ID_NUM (MU_MSG_FIELD_ID_UID + 1)
|
||||
|
||||
#define mu_msg_field_id_is_valid(MFID) \
|
||||
((MFID) < MU_MSG_FIELD_ID_NUM)
|
||||
|
||||
/* don't change the order, add new types at the end (before _NUM)*/
|
||||
enum _MuMsgFieldType {
|
||||
MU_MSG_FIELD_TYPE_STRING,
|
||||
MU_MSG_FIELD_TYPE_STRING_LIST,
|
||||
|
||||
MU_MSG_FIELD_TYPE_BYTESIZE,
|
||||
MU_MSG_FIELD_TYPE_TIME_T,
|
||||
MU_MSG_FIELD_TYPE_INT,
|
||||
|
||||
MU_MSG_FIELD_TYPE_NUM
|
||||
};
|
||||
typedef guint8 MuMsgFieldType;
|
||||
static const MuMsgFieldType MU_MSG_FIELD_TYPE_NONE = (MuMsgFieldType)-1;
|
||||
|
||||
typedef void (*MuMsgFieldForeachFunc) (MuMsgFieldId id,
|
||||
gconstpointer data);
|
||||
|
||||
/**
|
||||
* iterator over all possible message fields
|
||||
*
|
||||
* @param func a function called for each field
|
||||
* @param data a user data pointer passed the callback function
|
||||
*/
|
||||
void mu_msg_field_foreach (MuMsgFieldForeachFunc func, gconstpointer data);
|
||||
|
||||
|
||||
/**
|
||||
* get the name of the field -- this a name that can be use in queries,
|
||||
* ie. 'subject:foo', with 'subject' being the name
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return the name of the field as a constant string, or
|
||||
* NULL if the field is unknown
|
||||
*/
|
||||
const char* mu_msg_field_name (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
/**
|
||||
* get the shortcut of the field -- this a shortcut that can be use in
|
||||
* queries, ie. 's:foo', with 's' meaning 'subject' being the name
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return the shortcut character, or 0 if the field is unknown
|
||||
*/
|
||||
char mu_msg_field_shortcut (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
/**
|
||||
* get the xapian prefix of the field -- that is, the prefix used in
|
||||
* the Xapian database to identify the field
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return the xapian prefix char or 0 if the field is unknown
|
||||
*/
|
||||
char mu_msg_field_xapian_prefix (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* get the type of the field (string, size, time etc.)
|
||||
*
|
||||
* @param field a MuMsgField
|
||||
*
|
||||
* @return the type of the field (a #MuMsgFieldType), or
|
||||
* MU_MSG_FIELD_TYPE_NONE if it is not found
|
||||
*/
|
||||
MuMsgFieldType mu_msg_field_type (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* is the field a string?
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if the field a string, FALSE otherwise
|
||||
*/
|
||||
#define mu_msg_field_is_string(MFID)\
|
||||
(mu_msg_field_type((MFID))==MU_MSG_FIELD_TYPE_STRING?TRUE:FALSE)
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* is the field a string-list?
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if the field a string-list, FALSE otherwise
|
||||
*/
|
||||
#define mu_msg_field_is_string_list(MFID)\
|
||||
(mu_msg_field_type((MFID))==MU_MSG_FIELD_TYPE_STRING_LIST?TRUE:FALSE)
|
||||
|
||||
/**
|
||||
* is the field numeric (has type MU_MSG_FIELD_TYPE_(BYTESIZE|TIME_T|INT))?
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if the field is numeric, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_is_numeric (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* whether the field value should be cached (in MuMsg) -- we cache
|
||||
* values so we can use the MuMsg without needing to keep the
|
||||
* underlying data source (the GMimeMessage or the database ptr) alive
|
||||
* in practice, the fields we *don't* cache are the message body
|
||||
* (html, txt), because they take too much memory
|
||||
*/
|
||||
gboolean mu_msg_field_is_cacheable (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* is the field Xapian-indexable? That is, should this field be
|
||||
* indexed in the in the Xapian database, so we can use the all the
|
||||
* phrasing, stemming etc. magic
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if the field is Xapian-enabled, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_xapian_index (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
/**
|
||||
* should this field be stored as a xapian term?
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if the field is Xapian-enabled, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_xapian_term (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
/**
|
||||
* should this field be stored as a xapian value?
|
||||
*
|
||||
* @param field a MuMsgField
|
||||
*
|
||||
* @return TRUE if the field is Xapian-enabled, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_xapian_value (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* whether we should use add_boolean_prefix (see Xapian documentation)
|
||||
* for this field in queries. Used in mu-query.cc
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if this field wants add_boolean_prefix, FALSE
|
||||
* otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_uses_boolean_prefix (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* wether this fields needs a prefix in queries -- ie,
|
||||
* 'msgid:<some-message-id>' will only match with the explicit prefix,
|
||||
* while 'subject:foo' will also match as just 'foo'. Used in
|
||||
* mu-query.cc
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if this field only matches with a prefix, FALSE
|
||||
* otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_needs_prefix (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* should this field be escaped for xapian? in practice, should
|
||||
* word-breaking chars be replaced with '_'?
|
||||
*
|
||||
* @param field a MuMsgField
|
||||
*
|
||||
* @return TRUE if the field is Xapian-escaped, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_xapian_escape (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* should this field be normalized? ie. should it be downcased and
|
||||
* accents removed when storing as Xapian term?
|
||||
*
|
||||
* @param field a MuMsgField
|
||||
*
|
||||
* @return TRUE if the field is to be normalized, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_normalize (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
/**
|
||||
* is this a range-field? ie. date, or size
|
||||
*
|
||||
* @param id a MuMsgField
|
||||
*
|
||||
* @return TRUE if this field is a range field, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_is_range_field (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* should this field be stored as contact information? This means that
|
||||
* e-mail address will be stored as terms, and names will be indexed
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if the field should be stored as contact information,
|
||||
* FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_xapian_contact (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
/**
|
||||
* is the field gmime-enabled? That is, can be field be retrieved
|
||||
* using GMime?
|
||||
*
|
||||
* @param id a MuMsgFieldId
|
||||
*
|
||||
* @return TRUE if the field is Gmime-enabled, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_field_gmime (MuMsgFieldId id) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* get the corresponding MuMsgField for a name (as in mu_msg_field_name)
|
||||
*
|
||||
* @param str a name
|
||||
* @param err, if TRUE, when the shortcut is not found, will issue a
|
||||
* g_critical warning
|
||||
*
|
||||
* @return a MuMsgField, or NULL if it could not be found
|
||||
*/
|
||||
MuMsgFieldId mu_msg_field_id_from_name (const char* str,
|
||||
gboolean err) G_GNUC_PURE;
|
||||
|
||||
|
||||
/**
|
||||
* get the corresponding MuMsgField for a shortcut (as in mu_msg_field_shortcut)
|
||||
*
|
||||
* @param kar a shortcut character
|
||||
* @param err, if TRUE, when the shortcut is not found, will issue a
|
||||
* g_critical warning
|
||||
*
|
||||
* @return a MuMsgField, or NULL if it could not be found
|
||||
*/
|
||||
MuMsgFieldId mu_msg_field_id_from_shortcut (char kar,
|
||||
gboolean err) G_GNUC_PURE;
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_FIELDS_H__*/
|
||||
890
lib/mu-msg-file.c
Normal file
890
lib/mu-msg-file.c
Normal file
@ -0,0 +1,890 @@
|
||||
/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
**
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* hopefully, the should get us a sane PATH_MAX */
|
||||
#include <limits.h>
|
||||
/* not all systems provide PATH_MAX in limits.h */
|
||||
#ifndef PATH_MAX
|
||||
#include <sys/param.h>
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX MAXPATHLEN
|
||||
#endif /*!PATH_MAX */
|
||||
#endif /*PATH_MAX */
|
||||
|
||||
#include <gmime/gmime.h>
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-maildir.h"
|
||||
#include "mu-msg-priv.h"
|
||||
|
||||
|
||||
static gboolean init_file_metadata (MuMsgFile *self, const char* path,
|
||||
const char *mdir, GError **err);
|
||||
static gboolean init_mime_msg (MuMsgFile *msg, const char *path, GError **err);
|
||||
|
||||
|
||||
MuMsgFile*
|
||||
mu_msg_file_new (const char* filepath, const char *mdir, GError **err)
|
||||
{
|
||||
MuMsgFile *self;
|
||||
|
||||
g_return_val_if_fail (filepath, NULL);
|
||||
|
||||
self = g_slice_new0 (MuMsgFile);
|
||||
|
||||
if (!init_file_metadata (self, filepath, mdir, err)) {
|
||||
mu_msg_file_destroy (self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!init_mime_msg (self, filepath, err)) {
|
||||
mu_msg_file_destroy (self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_file_destroy (MuMsgFile *self)
|
||||
{
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
if (self->_mime_msg)
|
||||
g_object_unref (self->_mime_msg);
|
||||
|
||||
mu_str_free_list (self->_free_later);
|
||||
|
||||
g_slice_free (MuMsgFile, self);
|
||||
}
|
||||
|
||||
static const gchar*
|
||||
free_string_later (MuMsgFile *self, gchar *str)
|
||||
{
|
||||
self->_free_later = g_slist_prepend (self->_free_later, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
init_file_metadata (MuMsgFile *self, const char* path, const gchar* mdir,
|
||||
GError **err)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (access (path, R_OK) != 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"cannot read file %s: %s",
|
||||
path, strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (stat (path, &statbuf) < 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"cannot stat %s: %s",
|
||||
path, strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!S_ISREG(statbuf.st_mode)) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"not a regular file: %s", path);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
self->_timestamp = statbuf.st_mtime;
|
||||
self->_size = (size_t)statbuf.st_size;
|
||||
|
||||
strncpy (self->_path, path, PATH_MAX);
|
||||
strncpy (self->_maildir, mdir ? mdir : "", PATH_MAX);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static GMimeStream*
|
||||
get_mime_stream (MuMsgFile *self, const char *path, GError **err)
|
||||
{
|
||||
FILE *file;
|
||||
GMimeStream *stream;
|
||||
|
||||
file = fopen (path, "r");
|
||||
if (!file) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"cannot open %s: %s",
|
||||
path, strerror (errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stream = g_mime_stream_file_new (file);
|
||||
if (!stream) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"cannot create mime stream for %s",
|
||||
path);
|
||||
fclose (file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
init_mime_msg (MuMsgFile *self, const char* path, GError **err)
|
||||
{
|
||||
GMimeStream *stream;
|
||||
GMimeParser *parser;
|
||||
|
||||
stream = get_mime_stream (self, path, err);
|
||||
if (!stream)
|
||||
return FALSE;
|
||||
|
||||
parser = g_mime_parser_new_with_stream (stream);
|
||||
g_object_unref (stream);
|
||||
if (!parser) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"%s: cannot create mime parser for %s",
|
||||
__FUNCTION__, path);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
self->_mime_msg = g_mime_parser_construct_message (parser);
|
||||
g_object_unref (parser);
|
||||
if (!self->_mime_msg) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"%s: cannot construct mime message for %s",
|
||||
__FUNCTION__, path);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static char*
|
||||
get_recipient (MuMsgFile *self, GMimeRecipientType rtype)
|
||||
{
|
||||
char *recip;
|
||||
InternetAddressList *recips;
|
||||
|
||||
recips = g_mime_message_get_recipients (self->_mime_msg, rtype);
|
||||
|
||||
/* FALSE --> don't encode */
|
||||
recip = (char*)internet_address_list_to_string (recips, FALSE);
|
||||
|
||||
if (recip && !g_utf8_validate (recip, -1, NULL)) {
|
||||
g_debug ("invalid recipient in %s\n", self->_path);
|
||||
mu_str_asciify_in_place (recip); /* ugly... */
|
||||
}
|
||||
|
||||
if (mu_str_is_empty(recip)) {
|
||||
g_free (recip);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return recip;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
looks_like_attachment (GMimeObject *part)
|
||||
{
|
||||
const char *str;
|
||||
GMimeContentDisposition *disp;
|
||||
GMimeContentType *ct;
|
||||
|
||||
disp = g_mime_object_get_content_disposition (GMIME_OBJECT(part));
|
||||
if (!GMIME_IS_CONTENT_DISPOSITION(disp))
|
||||
return FALSE;
|
||||
|
||||
str = g_mime_content_disposition_get_disposition (disp);
|
||||
if (!str)
|
||||
return FALSE;
|
||||
|
||||
ct = g_mime_object_get_content_type (part);
|
||||
if (!ct)
|
||||
return FALSE; /* ignore this part... */
|
||||
|
||||
/* note, some mailers use ATTACHMENT, INLINE instead of their
|
||||
* more common lower-case counterparts */
|
||||
if (g_ascii_strcasecmp(str, GMIME_DISPOSITION_ATTACHMENT) == 0)
|
||||
return TRUE;
|
||||
|
||||
if (g_ascii_strcasecmp(str, GMIME_DISPOSITION_INLINE) == 0) {
|
||||
/* some inline parts are also considered attachments... */
|
||||
int i;
|
||||
const char* att_types[][2] = {
|
||||
{"image", "*"},
|
||||
{"application", "*"},
|
||||
{"message", "*"}};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS (att_types); ++i)
|
||||
if (g_mime_content_type_is_type (ct,
|
||||
att_types[i][0],
|
||||
att_types[i][1]))
|
||||
return TRUE; /* looks like an attachment */
|
||||
}
|
||||
|
||||
return FALSE; /* does not look like an attachment */
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
msg_cflags_cb (GMimeObject *parent, GMimeObject *part, MuFlags *flags)
|
||||
{
|
||||
if (*flags & MU_FLAG_HAS_ATTACH)
|
||||
return;
|
||||
|
||||
if (!GMIME_IS_PART(part))
|
||||
return;
|
||||
|
||||
if (!(*flags & MU_FLAG_HAS_ATTACH) && looks_like_attachment(part))
|
||||
*flags |= MU_FLAG_HAS_ATTACH;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static MuFlags
|
||||
get_content_flags (MuMsgFile *self)
|
||||
{
|
||||
GMimeContentType *ctype;
|
||||
MuFlags flags;
|
||||
GMimeObject *part;
|
||||
|
||||
if (!GMIME_IS_MESSAGE(self->_mime_msg))
|
||||
return MU_FLAG_NONE;
|
||||
|
||||
flags = 0;
|
||||
g_mime_message_foreach (self->_mime_msg,
|
||||
(GMimeObjectForeachFunc)msg_cflags_cb,
|
||||
&flags);
|
||||
|
||||
/* note: signed or encrypted status for a message is determined by
|
||||
* the top-level mime-part
|
||||
*/
|
||||
if ((part = g_mime_message_get_mime_part(self->_mime_msg))) {
|
||||
ctype = g_mime_object_get_content_type
|
||||
(GMIME_OBJECT(part));
|
||||
if (!ctype) {
|
||||
g_warning ("not a content type!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ctype) {
|
||||
if (g_mime_content_type_is_type
|
||||
(ctype,"*", "signed"))
|
||||
flags |= MU_FLAG_SIGNED;
|
||||
if (g_mime_content_type_is_type
|
||||
(ctype,"*", "encrypted"))
|
||||
flags |= MU_FLAG_ENCRYPTED;
|
||||
}
|
||||
} else
|
||||
g_warning ("no top level mime part found");
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
static MuFlags
|
||||
get_flags (MuMsgFile *self)
|
||||
{
|
||||
MuFlags flags;
|
||||
|
||||
g_return_val_if_fail (self, MU_FLAG_INVALID);
|
||||
|
||||
flags = mu_maildir_get_flags_from_path (self->_path);
|
||||
flags |= get_content_flags (self);
|
||||
|
||||
/* pseudo-flag --> unread means either NEW or NOT SEEN, just
|
||||
* for searching convenience */
|
||||
if ((flags & MU_FLAG_NEW) || !(flags & MU_FLAG_SEEN))
|
||||
flags |= MU_FLAG_UNREAD;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
static size_t
|
||||
get_size (MuMsgFile *self)
|
||||
{
|
||||
g_return_val_if_fail (self, 0);
|
||||
|
||||
return self->_size;
|
||||
}
|
||||
|
||||
|
||||
static char*
|
||||
to_lower (char *s)
|
||||
{
|
||||
char *t = s;
|
||||
while (t&&*t) {
|
||||
t[0] = g_ascii_tolower(t[0]);
|
||||
++t;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
static char*
|
||||
get_prio_header_field (MuMsgFile *self)
|
||||
{
|
||||
const char *str;
|
||||
GMimeObject *obj;
|
||||
|
||||
obj = GMIME_OBJECT(self->_mime_msg);
|
||||
|
||||
str = g_mime_object_get_header (obj, "Precedence");
|
||||
if (!str)
|
||||
str = g_mime_object_get_header (obj, "X-Priority");
|
||||
if (!str)
|
||||
str = g_mime_object_get_header (obj, "Importance");
|
||||
/* NOTE: "X-MSMail-Priority" is never seen without "X-Priority" */
|
||||
/* if (!str) */
|
||||
/* str = g_mime_object_get_header (obj, "X-MSMail-Priority"); */
|
||||
if (str)
|
||||
return (to_lower(g_strdup(str)));
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static MuMsgPrio
|
||||
parse_prio_str (const char* priostr)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char* _str;
|
||||
MuMsgPrio _prio;
|
||||
} str_prio[] = {
|
||||
{ "high", MU_MSG_PRIO_HIGH },
|
||||
{ "1", MU_MSG_PRIO_HIGH },
|
||||
{ "2", MU_MSG_PRIO_HIGH },
|
||||
|
||||
{ "normal", MU_MSG_PRIO_NORMAL },
|
||||
{ "3", MU_MSG_PRIO_NORMAL },
|
||||
|
||||
{ "low", MU_MSG_PRIO_LOW },
|
||||
{ "list", MU_MSG_PRIO_LOW },
|
||||
{ "bulk", MU_MSG_PRIO_LOW },
|
||||
{ "4", MU_MSG_PRIO_LOW },
|
||||
{ "5", MU_MSG_PRIO_LOW }
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(str_prio); ++i)
|
||||
if (g_strstr_len (priostr, -1, str_prio[i]._str) != NULL)
|
||||
return str_prio[i]._prio;
|
||||
|
||||
/* e.g., last-fm uses 'fm-user'... as precedence */
|
||||
return MU_MSG_PRIO_NORMAL;
|
||||
}
|
||||
|
||||
static MuMsgPrio
|
||||
get_prio (MuMsgFile *self)
|
||||
{
|
||||
MuMsgPrio prio;
|
||||
char* priostr;
|
||||
|
||||
g_return_val_if_fail (self, MU_MSG_PRIO_NONE);
|
||||
|
||||
priostr = get_prio_header_field (self);
|
||||
if (!priostr)
|
||||
return MU_MSG_PRIO_NORMAL;
|
||||
|
||||
prio = parse_prio_str (priostr);
|
||||
g_free (priostr);
|
||||
|
||||
return prio;
|
||||
}
|
||||
|
||||
|
||||
struct _GetBodyData {
|
||||
GMimeObject *_txt_part, *_html_part;
|
||||
gboolean _want_html;
|
||||
};
|
||||
typedef struct _GetBodyData GetBodyData;
|
||||
|
||||
|
||||
|
||||
static void
|
||||
get_body_cb (GMimeObject *parent, GMimeObject *part, GetBodyData *data)
|
||||
{
|
||||
GMimeContentType *ct;
|
||||
|
||||
/* already found what we're looking for? */
|
||||
if ((data->_want_html && data->_html_part != NULL) ||
|
||||
(!data->_want_html && data->_txt_part != NULL))
|
||||
return;
|
||||
|
||||
ct = g_mime_object_get_content_type (part);
|
||||
if (!GMIME_IS_CONTENT_TYPE(ct)) {
|
||||
g_warning ("not a content type!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (looks_like_attachment (part))
|
||||
return; /* not the body */
|
||||
|
||||
/* is it right content type? */
|
||||
if (g_mime_content_type_is_type (ct, "text", "plain"))
|
||||
data->_txt_part = part;
|
||||
else if (g_mime_content_type_is_type (ct, "text", "html"))
|
||||
data->_html_part = part;
|
||||
else
|
||||
return; /* wrong type */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* NOTE: buffer will be *freed* or returned unchanged */
|
||||
static char*
|
||||
convert_to_utf8 (GMimePart *part, char *buffer)
|
||||
{
|
||||
GMimeContentType *ctype;
|
||||
const char* charset;
|
||||
unsigned char *cur;
|
||||
|
||||
/* optimization: if the buffer is plain ascii, no conversion
|
||||
* is done... */
|
||||
for (cur = (unsigned char*)buffer; *cur && *cur < 0x80; ++cur);
|
||||
if (*cur == '\0')
|
||||
return buffer;
|
||||
|
||||
ctype = g_mime_object_get_content_type (GMIME_OBJECT(part));
|
||||
g_return_val_if_fail (GMIME_IS_CONTENT_TYPE(ctype), NULL);
|
||||
|
||||
/* of course, the charset specified may be incorrect... */
|
||||
charset = g_mime_content_type_get_parameter (ctype, "charset");
|
||||
if (charset) {
|
||||
char *utf8;
|
||||
utf8 = mu_str_convert_to_utf8
|
||||
(buffer,
|
||||
g_mime_charset_iconv_name (charset));
|
||||
if (utf8) {
|
||||
g_free (buffer);
|
||||
return utf8;
|
||||
}
|
||||
} else if (g_utf8_validate (buffer, -1, NULL)) {
|
||||
/* check if the buffer is valid utf8, even if it doesn't
|
||||
* say so explicitly... if that is the case, return it as-is */
|
||||
|
||||
/* nothing to do, buffer is already utf8 */
|
||||
|
||||
} else {
|
||||
/* hmmm.... no charset at all, or conversion failed; ugly
|
||||
* hack: replace all non-ascii chars with '.' */
|
||||
mu_str_asciify_in_place (buffer);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
static gchar*
|
||||
stream_to_string (GMimeStream *stream, size_t buflen)
|
||||
{
|
||||
char *buffer;
|
||||
ssize_t bytes;
|
||||
|
||||
buffer = g_new(char, buflen + 1);
|
||||
g_mime_stream_reset (stream);
|
||||
|
||||
/* we read everything in one go */
|
||||
bytes = g_mime_stream_read (stream, buffer, buflen);
|
||||
if (bytes < 0) {
|
||||
g_warning ("%s: failed to read from stream", __FUNCTION__);
|
||||
g_free (buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer[bytes]='\0';
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_msg_mime_part_to_string (GMimePart *part, gboolean *err)
|
||||
{
|
||||
GMimeDataWrapper *wrapper;
|
||||
GMimeStream *stream = NULL;
|
||||
ssize_t buflen;
|
||||
char *buffer = NULL;
|
||||
|
||||
*err = TRUE; /* guilty until proven innocent */
|
||||
g_return_val_if_fail (GMIME_IS_PART(part), NULL);
|
||||
|
||||
wrapper = g_mime_part_get_content_object (part);
|
||||
if (!wrapper) {
|
||||
/* this happens with invalid mails */
|
||||
g_debug ("failed to create data wrapper");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
stream = g_mime_stream_mem_new ();
|
||||
if (!stream) {
|
||||
g_warning ("failed to create mem stream");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
buflen = g_mime_data_wrapper_write_to_stream (wrapper, stream);
|
||||
if (buflen <= 0) {/* empty buffer, not an error */
|
||||
*err = FALSE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
buffer = stream_to_string (stream, (size_t)buflen);
|
||||
|
||||
/* convert_to_utf8 will free the old 'buffer' if needed */
|
||||
buffer = convert_to_utf8 (part, buffer);
|
||||
|
||||
*err = FALSE;
|
||||
|
||||
cleanup:
|
||||
if (stream)
|
||||
g_object_unref (G_OBJECT(stream));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
GMimePart*
|
||||
mu_msg_mime_get_body_part (GMimeMessage *msg, gboolean want_html)
|
||||
{
|
||||
GetBodyData data;
|
||||
|
||||
g_return_val_if_fail (GMIME_IS_MESSAGE(msg), NULL);
|
||||
|
||||
memset (&data, 0, sizeof(GetBodyData));
|
||||
data._want_html = want_html;
|
||||
|
||||
g_mime_message_foreach (msg,
|
||||
(GMimeObjectForeachFunc)get_body_cb,
|
||||
&data);
|
||||
if (want_html)
|
||||
return (GMimePart*)data._html_part;
|
||||
else
|
||||
return (GMimePart*)data._txt_part;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static char*
|
||||
get_body (MuMsgFile *self, gboolean want_html)
|
||||
{
|
||||
GMimePart *part;
|
||||
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (GMIME_IS_MESSAGE(self->_mime_msg), NULL);
|
||||
|
||||
part = mu_msg_mime_get_body_part (self->_mime_msg, want_html);
|
||||
if (GMIME_IS_PART(part)) {
|
||||
gboolean err;
|
||||
gchar *str;
|
||||
|
||||
err = FALSE;
|
||||
str = mu_msg_mime_part_to_string (part, &err);
|
||||
|
||||
/* note, str may be NULL (no body), but that's not necessarily
|
||||
* an error; we only warn when an actual error occured */
|
||||
if (err)
|
||||
g_warning ("error occured while retrieving %s body "
|
||||
"for message %s",
|
||||
want_html ? "html" : "text", self->_path);
|
||||
return str;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
append_text (GMimeObject *parent, GMimeObject *part, gchar **txt)
|
||||
{
|
||||
GMimeContentType *ct;
|
||||
GMimeContentDisposition *disp;
|
||||
gchar *parttxt, *tmp;
|
||||
gboolean err;
|
||||
|
||||
if (!GMIME_IS_PART(part))
|
||||
return;
|
||||
|
||||
ct = g_mime_object_get_content_type (part);
|
||||
if (!GMIME_IS_CONTENT_TYPE(ct) ||
|
||||
!g_mime_content_type_is_type (ct, "text", "plain"))
|
||||
return; /* not a text-plain part */
|
||||
|
||||
disp = g_mime_object_get_content_disposition (part);
|
||||
if (GMIME_IS_CONTENT_DISPOSITION(disp) &&
|
||||
g_strcmp0 (g_mime_content_disposition_get_disposition (disp),
|
||||
GMIME_DISPOSITION_ATTACHMENT) == 0)
|
||||
return; /* it's an attachment, don't include */
|
||||
|
||||
parttxt = mu_msg_mime_part_to_string (GMIME_PART(part), &err);
|
||||
if (err) {
|
||||
/* this happens for broken messages */
|
||||
g_debug ("%s: could not get text for part", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* it's a text part -- append it! */
|
||||
tmp = *txt;
|
||||
if (*txt) {
|
||||
*txt = g_strconcat (*txt, parttxt, NULL);
|
||||
g_free (parttxt);
|
||||
} else
|
||||
*txt = parttxt;
|
||||
|
||||
g_free (tmp);
|
||||
}
|
||||
|
||||
/* instead of just the body, this function returns a concatenation of
|
||||
* all text/plain parts with inline disposition
|
||||
*/
|
||||
static char*
|
||||
get_concatenated_text (MuMsgFile *self)
|
||||
{
|
||||
char *txt;
|
||||
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (GMIME_IS_MESSAGE(self->_mime_msg), NULL);
|
||||
|
||||
txt = NULL;
|
||||
g_mime_message_foreach (self->_mime_msg,
|
||||
(GMimeObjectForeachFunc)append_text,
|
||||
&txt);
|
||||
return txt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
contains (GSList *lst, const char *str)
|
||||
{
|
||||
for (; lst; lst = g_slist_next(lst))
|
||||
if (g_strcmp0 ((char*)lst->data, str) == 0)
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static GSList*
|
||||
get_references (MuMsgFile *self)
|
||||
{
|
||||
GSList *msgids;
|
||||
const char *str;
|
||||
unsigned u;
|
||||
const char *headers[] = { "References", "In-reply-to", NULL };
|
||||
|
||||
for (msgids = NULL, u = 0; headers[u]; ++u) {
|
||||
|
||||
const GMimeReferences *cur;
|
||||
GMimeReferences *mime_refs;
|
||||
|
||||
str = mu_msg_file_get_header (self, headers[u]);
|
||||
if (!str)
|
||||
continue;
|
||||
|
||||
mime_refs = g_mime_references_decode (str);
|
||||
for (cur = mime_refs; cur; cur = g_mime_references_get_next(cur)) {
|
||||
const char* msgid;
|
||||
msgid = g_mime_references_get_message_id (cur);
|
||||
/* don't include duplicates */
|
||||
if (msgid && !contains (msgids, msgid))
|
||||
/* explicitly ensure it's utf8-safe, as GMime
|
||||
* does not ensure that */
|
||||
msgids = g_slist_prepend (msgids, g_strdup((msgid)));
|
||||
}
|
||||
g_mime_references_free (mime_refs);
|
||||
}
|
||||
|
||||
return g_slist_reverse (msgids);
|
||||
}
|
||||
|
||||
|
||||
static GSList*
|
||||
get_tags (MuMsgFile *self)
|
||||
{
|
||||
const char *hdr;
|
||||
|
||||
hdr = mu_msg_file_get_header (self, "X-Label");
|
||||
if (!hdr)
|
||||
return NULL;
|
||||
|
||||
return mu_str_to_list (hdr, ',', TRUE);
|
||||
}
|
||||
|
||||
|
||||
/* wrongly encoded messages may cause GMime to return invalid
|
||||
* UTF8... we double check, and ensure our output is always correct
|
||||
* utf8 */
|
||||
gchar *
|
||||
maybe_cleanup (const char* str, const char *path, gboolean *do_free)
|
||||
{
|
||||
if (!str || G_LIKELY(g_utf8_validate(str, -1, NULL)))
|
||||
return (char*)str;
|
||||
|
||||
g_debug ("invalid utf8 in %s", path);
|
||||
|
||||
if (*do_free)
|
||||
return mu_str_asciify_in_place ((char*)str);
|
||||
else {
|
||||
gchar *ascii;
|
||||
ascii = mu_str_asciify_in_place(g_strdup (str));
|
||||
*do_free = TRUE;
|
||||
return ascii;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
G_GNUC_CONST static GMimeRecipientType
|
||||
recipient_type (MuMsgFieldId mfid)
|
||||
{
|
||||
switch (mfid) {
|
||||
case MU_MSG_FIELD_ID_BCC: return GMIME_RECIPIENT_TYPE_BCC;
|
||||
case MU_MSG_FIELD_ID_CC : return GMIME_RECIPIENT_TYPE_CC;
|
||||
case MU_MSG_FIELD_ID_TO : return GMIME_RECIPIENT_TYPE_TO;
|
||||
default: g_return_val_if_reached (-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_msg_file_get_str_field (MuMsgFile *self, MuMsgFieldId mfid,
|
||||
gboolean *do_free)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (mu_msg_field_is_string(mfid), NULL);
|
||||
|
||||
*do_free = FALSE; /* default */
|
||||
|
||||
switch (mfid) {
|
||||
|
||||
case MU_MSG_FIELD_ID_EMBEDDED_TEXT: *do_free = TRUE;
|
||||
return NULL; /* FIXME */
|
||||
|
||||
case MU_MSG_FIELD_ID_BCC:
|
||||
case MU_MSG_FIELD_ID_CC:
|
||||
case MU_MSG_FIELD_ID_TO: *do_free = TRUE;
|
||||
return get_recipient (self, recipient_type(mfid));
|
||||
|
||||
case MU_MSG_FIELD_ID_BODY_TEXT: *do_free = TRUE;
|
||||
return get_concatenated_text (self);
|
||||
case MU_MSG_FIELD_ID_BODY_HTML: *do_free = TRUE;
|
||||
return get_body (self, TRUE);
|
||||
|
||||
case MU_MSG_FIELD_ID_FROM:
|
||||
return (char*)maybe_cleanup
|
||||
(g_mime_message_get_sender (self->_mime_msg),
|
||||
self->_path, do_free);
|
||||
|
||||
case MU_MSG_FIELD_ID_PATH: return self->_path;
|
||||
|
||||
case MU_MSG_FIELD_ID_SUBJECT:
|
||||
return (char*)maybe_cleanup
|
||||
(g_mime_message_get_subject (self->_mime_msg),
|
||||
self->_path, do_free);
|
||||
|
||||
case MU_MSG_FIELD_ID_MSGID:
|
||||
return (char*)g_mime_message_get_message_id (self->_mime_msg);
|
||||
|
||||
case MU_MSG_FIELD_ID_MAILDIR: return self->_maildir;
|
||||
|
||||
default: g_return_val_if_reached (NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GSList*
|
||||
mu_msg_file_get_str_list_field (MuMsgFile *self, MuMsgFieldId mfid,
|
||||
gboolean *do_free)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (mu_msg_field_is_string_list(mfid), NULL);
|
||||
|
||||
switch (mfid) {
|
||||
|
||||
case MU_MSG_FIELD_ID_REFS:
|
||||
*do_free = TRUE;
|
||||
return get_references (self);
|
||||
case MU_MSG_FIELD_ID_TAGS:
|
||||
*do_free = TRUE;
|
||||
return get_tags (self);
|
||||
default:
|
||||
g_return_val_if_reached (NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gint64
|
||||
mu_msg_file_get_num_field (MuMsgFile *self, const MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (self, -1);
|
||||
g_return_val_if_fail (mu_msg_field_is_numeric(mfid), -1);
|
||||
|
||||
switch (mfid) {
|
||||
|
||||
case MU_MSG_FIELD_ID_DATE: {
|
||||
time_t t;
|
||||
g_mime_message_get_date (self->_mime_msg, &t, NULL);
|
||||
return (time_t)t;
|
||||
}
|
||||
|
||||
case MU_MSG_FIELD_ID_FLAGS:
|
||||
return (gint64)get_flags(self);
|
||||
|
||||
case MU_MSG_FIELD_ID_PRIO:
|
||||
return (gint64)get_prio(self);
|
||||
|
||||
case MU_MSG_FIELD_ID_SIZE:
|
||||
return (gint64)get_size(self);
|
||||
|
||||
default: g_return_val_if_reached (-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_file_get_header (MuMsgFile *self, const char *header)
|
||||
{
|
||||
const gchar *hdr;
|
||||
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (header, NULL);
|
||||
|
||||
/* sadly, g_mime_object_get_header may return non-ascii;
|
||||
* so, we need to ensure that
|
||||
*/
|
||||
hdr = g_mime_object_get_header (GMIME_OBJECT(self->_mime_msg),
|
||||
header);
|
||||
|
||||
return hdr ? free_string_later (self, mu_str_utf8ify(hdr)) : NULL;
|
||||
}
|
||||
114
lib/mu-msg-file.h
Normal file
114
lib/mu-msg-file.h
Normal file
@ -0,0 +1,114 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_FILE_H__
|
||||
#define __MU_MSG_FILE_H__
|
||||
|
||||
struct _MuMsgFile;
|
||||
typedef struct _MuMsgFile MuMsgFile;
|
||||
|
||||
/**
|
||||
* create a new message from a file
|
||||
*
|
||||
* @param path full path to the message
|
||||
* @param mdir
|
||||
* @param err error to receive (when function returns NULL), or NULL
|
||||
*
|
||||
* @return a new MuMsg, or NULL in case of error
|
||||
*/
|
||||
MuMsgFile *mu_msg_file_new (const char *path,
|
||||
const char* mdir, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* destroy a MuMsgFile object
|
||||
*
|
||||
* @param self object to destroy, or NULL
|
||||
*/
|
||||
void mu_msg_file_destroy (MuMsgFile *self);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get a specific header
|
||||
*
|
||||
* @param self a MuMsgFile instance
|
||||
* @param header a header (e.g. 'X-Mailer' or 'List-Id')
|
||||
*
|
||||
* @return the value of the header or NULL if not found. Note, only
|
||||
* valid as long as this MuMsgFile is valid -- before
|
||||
* mu_msg_file_destroy
|
||||
*/
|
||||
const char* mu_msg_file_get_header (MuMsgFile *self, const char *header);
|
||||
|
||||
|
||||
/**
|
||||
* get a string value for this message
|
||||
*
|
||||
* @param self a valid MuMsgFile
|
||||
* @param msfid the message field id to get (must be of type string)
|
||||
* @param do_free receives TRUE or FALSE, conveying if this string
|
||||
* should be owned & freed (TRUE) or not by caller. In case 'FALSE',
|
||||
* this function should be treated as if it were returning a const
|
||||
* char*, and note that in that case the string is only valid as long
|
||||
* as the MuMsgFile is alive, ie. before mu_msg_file_destroy
|
||||
*
|
||||
* @return a string, or NULL
|
||||
*/
|
||||
char* mu_msg_file_get_str_field (MuMsgFile *self,
|
||||
MuMsgFieldId msfid,
|
||||
gboolean *do_free)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* get a string-list value for this message
|
||||
*
|
||||
* @param self a valid MuMsgFile
|
||||
* @param msfid the message field id to get (must be of type string-list)
|
||||
* @param do_free receives TRUE or FALSE, conveying if this string
|
||||
* should be owned & freed (TRUE) or not by caller. In case 'FALSE',
|
||||
* this function should be treated as if it were returning a const
|
||||
* GSList*, and note that in that case the string is only valid as long
|
||||
* as the MuMsgFile is alive, ie. before mu_msg_file_destroy
|
||||
*
|
||||
* @return a GSList*, or NULL
|
||||
*/
|
||||
GSList* mu_msg_file_get_str_list_field (MuMsgFile *self,
|
||||
MuMsgFieldId msfid,
|
||||
gboolean *do_free)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get a numeric value for this message -- the return value should be
|
||||
* cast into the actual type, e.g., time_t, MuMsgPrio etc.
|
||||
*
|
||||
* @param self a valid MuMsgFile
|
||||
* @param msfid the message field id to get (must be string-based one)
|
||||
*
|
||||
* @return the numeric value, or -1 in case of error
|
||||
*/
|
||||
gint64 mu_msg_file_get_num_field (MuMsgFile *self, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
#endif /*__MU_MSG_FILE_H__*/
|
||||
244
lib/mu-msg-iter.cc
Normal file
244
lib/mu-msg-iter.cc
Normal file
@ -0,0 +1,244 @@
|
||||
/* -*- mode: c++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
**
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <algorithm>
|
||||
#include <xapian.h>
|
||||
#include <string>
|
||||
|
||||
#include "mu-util.h"
|
||||
#include "mu-msg.h"
|
||||
#include "mu-msg-iter.h"
|
||||
#include "mu-threader.h"
|
||||
|
||||
/* just a guess... */
|
||||
#define MAX_FETCH_SIZE 10000
|
||||
|
||||
class ThreadKeyMaker: public Xapian::KeyMaker {
|
||||
public:
|
||||
ThreadKeyMaker (GHashTable *threadinfo): _threadinfo(threadinfo) {}
|
||||
virtual std::string operator()(const Xapian::Document &doc) const {
|
||||
MuMsgIterThreadInfo *ti;
|
||||
ti = (MuMsgIterThreadInfo*)g_hash_table_lookup
|
||||
(_threadinfo,
|
||||
GUINT_TO_POINTER(doc.get_docid()));
|
||||
return std::string (ti && ti->threadpath ? ti->threadpath : "");
|
||||
}
|
||||
private:
|
||||
GHashTable *_threadinfo;
|
||||
};
|
||||
|
||||
|
||||
struct _MuMsgIter {
|
||||
public:
|
||||
_MuMsgIter (Xapian::Enquire &enq, size_t maxnum,
|
||||
gboolean threads, MuMsgFieldId sortfield, bool revert):
|
||||
_enq(enq), _thread_hash (0), _msg(0) {
|
||||
|
||||
_matches = _enq.get_mset (0, maxnum);
|
||||
|
||||
if (threads && !_matches.empty()) {
|
||||
|
||||
_matches.fetch();
|
||||
_thread_hash = mu_threader_calculate
|
||||
(this, _matches.size(), sortfield,
|
||||
revert ? TRUE: FALSE);
|
||||
|
||||
ThreadKeyMaker keymaker(_thread_hash);
|
||||
|
||||
enq.set_sort_by_key (&keymaker, false);
|
||||
_matches = _enq.get_mset (0, maxnum);
|
||||
}
|
||||
|
||||
_cursor = _matches.begin();
|
||||
|
||||
/* this seems to make search slightly faster, some
|
||||
* non-scientific testing suggests. 5-10% or so */
|
||||
if (_matches.size() <= MAX_FETCH_SIZE)
|
||||
_matches.fetch ();
|
||||
}
|
||||
|
||||
~_MuMsgIter () {
|
||||
if (_thread_hash)
|
||||
g_hash_table_destroy (_thread_hash);
|
||||
|
||||
set_msg (NULL);
|
||||
}
|
||||
|
||||
const Xapian::Enquire& enquire() const { return _enq; }
|
||||
Xapian::MSet& matches() { return _matches; }
|
||||
|
||||
Xapian::MSet::const_iterator cursor () const { return _cursor; }
|
||||
void set_cursor (Xapian::MSetIterator cur) { _cursor = cur; }
|
||||
void cursor_next () { ++_cursor; }
|
||||
|
||||
GHashTable *thread_hash () { return _thread_hash; }
|
||||
|
||||
MuMsg *msg() { return _msg; }
|
||||
MuMsg *set_msg (MuMsg *msg) {
|
||||
if (_msg)
|
||||
mu_msg_unref (_msg);
|
||||
return _msg = msg;
|
||||
}
|
||||
|
||||
private:
|
||||
const Xapian::Enquire _enq;
|
||||
Xapian::MSet _matches;
|
||||
Xapian::MSet::const_iterator _cursor;
|
||||
|
||||
GHashTable *_thread_hash;
|
||||
MuMsg *_msg;
|
||||
};
|
||||
|
||||
MuMsgIter*
|
||||
mu_msg_iter_new (XapianEnquire *enq, size_t maxnum,
|
||||
gboolean threads, MuMsgFieldId sortfield, gboolean revert)
|
||||
{
|
||||
g_return_val_if_fail (enq, NULL);
|
||||
/* sortfield should be set to .._NONE when we're not threading */
|
||||
g_return_val_if_fail (threads || sortfield == MU_MSG_FIELD_ID_NONE,
|
||||
NULL);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid (sortfield) ||
|
||||
sortfield == MU_MSG_FIELD_ID_NONE,
|
||||
FALSE);
|
||||
try {
|
||||
return new MuMsgIter ((Xapian::Enquire&)*enq, maxnum, threads,
|
||||
sortfield, revert ? true : false);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(NULL);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_iter_destroy (MuMsgIter *iter)
|
||||
{
|
||||
try { delete iter; } MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
MuMsg*
|
||||
mu_msg_iter_get_msg_floating (MuMsgIter *iter)
|
||||
{
|
||||
g_return_val_if_fail (iter, NULL);
|
||||
g_return_val_if_fail (!mu_msg_iter_is_done(iter), NULL);
|
||||
|
||||
try {
|
||||
MuMsg *msg;
|
||||
GError *err;
|
||||
Xapian::Document *docp;
|
||||
|
||||
docp = new Xapian::Document(iter->cursor().get_document());
|
||||
|
||||
err = NULL;
|
||||
msg = iter->set_msg (mu_msg_new_from_doc((XapianDocument*)docp, &err));
|
||||
if (!msg)
|
||||
MU_HANDLE_G_ERROR(err);
|
||||
|
||||
return msg;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN (NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_msg_iter_reset (MuMsgIter *iter)
|
||||
{
|
||||
g_return_val_if_fail (iter, FALSE);
|
||||
|
||||
iter->set_msg (NULL);
|
||||
|
||||
try {
|
||||
iter->set_cursor(iter->matches().begin());
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN (FALSE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_iter_next (MuMsgIter *iter)
|
||||
{
|
||||
g_return_val_if_fail (iter, FALSE);
|
||||
|
||||
iter->set_msg (NULL);
|
||||
|
||||
if (mu_msg_iter_is_done(iter))
|
||||
return FALSE;
|
||||
|
||||
try {
|
||||
iter->cursor_next();
|
||||
return iter->cursor() == iter->matches().end() ? FALSE:TRUE;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_iter_is_done (MuMsgIter *iter)
|
||||
{
|
||||
g_return_val_if_fail (iter, TRUE);
|
||||
|
||||
try {
|
||||
return iter->cursor() == iter->matches().end() ? TRUE : FALSE;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN (TRUE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* hmmm.... is it impossible to get a 0 docid, or just very improbable? */
|
||||
unsigned int
|
||||
mu_msg_iter_get_docid (MuMsgIter *iter)
|
||||
{
|
||||
g_return_val_if_fail (!mu_msg_iter_is_done(iter),
|
||||
(unsigned int)-1);
|
||||
try {
|
||||
return iter->cursor().get_document().get_docid();
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN (0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const MuMsgIterThreadInfo*
|
||||
mu_msg_iter_get_thread_info (MuMsgIter *iter)
|
||||
{
|
||||
g_return_val_if_fail (!mu_msg_iter_is_done(iter), NULL);
|
||||
g_return_val_if_fail (iter->thread_hash(), NULL);
|
||||
|
||||
try {
|
||||
const MuMsgIterThreadInfo *ti;
|
||||
unsigned int docid;
|
||||
|
||||
docid = mu_msg_iter_get_docid (iter);
|
||||
ti = (const MuMsgIterThreadInfo*)g_hash_table_lookup
|
||||
(iter->thread_hash(), GUINT_TO_POINTER(docid));
|
||||
|
||||
if (!ti)
|
||||
g_printerr ("no ti for %u\n", docid);
|
||||
|
||||
return ti;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN (NULL);
|
||||
}
|
||||
|
||||
|
||||
167
lib/mu-msg-iter.h
Normal file
167
lib/mu-msg-iter.h
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_ITER_H__
|
||||
#define __MU_MSG_ITER_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <mu-msg.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
/**
|
||||
* MuMsgIter is a structure to iterate over the results of a
|
||||
* query. You can iterate only in one-direction, and you can do it
|
||||
* only once.
|
||||
*
|
||||
*/
|
||||
|
||||
struct _MuMsgIter;
|
||||
typedef struct _MuMsgIter MuMsgIter;
|
||||
|
||||
|
||||
/**
|
||||
* create a new MuMsgIter -- basically, an iterator over the search
|
||||
* results
|
||||
*
|
||||
* @param enq a Xapian::Enquire* cast to XapianEnquire* (because this
|
||||
* is C, not C++),providing access to search results
|
||||
* @param batchsize how many results to retrieve at once
|
||||
* @param threads whether to calculate threads
|
||||
* @param sorting field when using threads; note, when 'threads' is
|
||||
* FALSE, this should be MU_MSG_FIELD_ID_NONE
|
||||
* @param if TRUE, revert the sorting order
|
||||
*
|
||||
* @return a new MuMsgIter, or NULL in case of error
|
||||
*/
|
||||
MuMsgIter *mu_msg_iter_new (XapianEnquire *enq,
|
||||
size_t batchsize, gboolean threads,
|
||||
MuMsgFieldId threadsortfield,
|
||||
gboolean revert) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* get the next message (which you got from
|
||||
* e.g. mu_query_run)
|
||||
*
|
||||
* @param iter a valid MuMsgIter iterator
|
||||
*
|
||||
* @return TRUE if it succeeded, FALSE otherwise (e.g., because there
|
||||
* are no more messages in the query result)
|
||||
*/
|
||||
gboolean mu_msg_iter_next (MuMsgIter *iter);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* reset the iterator to the beginning
|
||||
*
|
||||
* @param iter a valid MuMsgIter iterator
|
||||
*
|
||||
* @return TRUE if it succeeded, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_iter_reset (MuMsgIter *iter);
|
||||
|
||||
/**
|
||||
* does this iterator point past the end of the list?
|
||||
*
|
||||
* @param iter a valid MuMsgIter iterator
|
||||
*
|
||||
* @return TRUE if the iter points past end of the list, FALSE
|
||||
* otherwise
|
||||
*/
|
||||
gboolean mu_msg_iter_is_done (MuMsgIter *iter);
|
||||
|
||||
|
||||
/**
|
||||
* destroy the sequence of messages; ie. /all/ of them
|
||||
*
|
||||
* @param msg a valid MuMsgIter message or NULL
|
||||
*/
|
||||
void mu_msg_iter_destroy (MuMsgIter *iter);
|
||||
|
||||
|
||||
/**
|
||||
* get the corresponding MuMsg for this iter; this instance is owned
|
||||
* by MuMsgIter, and becomes invalid after either mu_msg_iter_destroy
|
||||
* or mu_msg_iter_next. _do not_ unref it; it's a floating reference.
|
||||
*
|
||||
* @param iter a valid MuMsgIter instance*
|
||||
*
|
||||
* @return a MuMsg instance, or NULL in case of error
|
||||
*/
|
||||
MuMsg* mu_msg_iter_get_msg_floating (MuMsgIter *iter)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get the document id for the current message
|
||||
*
|
||||
* @param iter a valid MuMsgIter iterator
|
||||
*
|
||||
* @return the docid or (unsigned int)-1 in case of error
|
||||
*/
|
||||
unsigned int mu_msg_iter_get_docid (MuMsgIter *iter);
|
||||
|
||||
|
||||
/**
|
||||
* calculate the message threads
|
||||
*
|
||||
* @param iter a valid MuMsgIter iterator
|
||||
*
|
||||
* @return TRUE if it worked, FALSE otherwsie.
|
||||
*/
|
||||
gboolean mu_msg_iter_calculate_threads (MuMsgIter *iter);
|
||||
|
||||
|
||||
enum _MuMsgIterThreadProp {
|
||||
MU_MSG_ITER_THREAD_PROP_ROOT = 1 << 0,
|
||||
MU_MSG_ITER_THREAD_PROP_FIRST_CHILD = 1 << 1,
|
||||
MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT = 1 << 2,
|
||||
MU_MSG_ITER_THREAD_PROP_DUP = 1 << 3,
|
||||
MU_MSG_ITER_THREAD_PROP_HAS_CHILD = 1 << 4
|
||||
};
|
||||
typedef guint8 MuMsgIterThreadProp;
|
||||
|
||||
struct _MuMsgIterThreadInfo {
|
||||
gchar *threadpath; /* a string decribing the thread-path in
|
||||
* such a way that we can sort by this
|
||||
* string to get the right order. */
|
||||
guint level; /* thread-depth -- [0...] */
|
||||
MuMsgIterThreadProp prop;
|
||||
};
|
||||
typedef struct _MuMsgIterThreadInfo MuMsgIterThreadInfo;
|
||||
|
||||
/**
|
||||
* get a the MuMsgThreaderInfo struct for this message; this only
|
||||
* works when you created the mu-msg-iter with threading enabled
|
||||
*
|
||||
* @param iter a valid MuMsgIter iterator
|
||||
*
|
||||
* @return an info struct
|
||||
*/
|
||||
const MuMsgIterThreadInfo* mu_msg_iter_get_thread_info (MuMsgIter *iter);
|
||||
|
||||
/* FIXME */
|
||||
const char* mu_msg_iter_get_path (MuMsgIter *iter);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_ITER_H__*/
|
||||
696
lib/mu-msg-part.c
Normal file
696
lib/mu-msg-part.c
Normal file
@ -0,0 +1,696 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-msg-priv.h"
|
||||
#include "mu-msg-part.h"
|
||||
|
||||
struct _FindPartData {
|
||||
guint idx, wanted_idx;
|
||||
GMimeObject *part;
|
||||
};
|
||||
typedef struct _FindPartData FindPartData;
|
||||
|
||||
|
||||
/* is this either a leaf part or an embedded message? */
|
||||
static gboolean
|
||||
is_part_or_message_part (GMimeObject *part)
|
||||
{
|
||||
return GMIME_IS_PART(part) || GMIME_IS_MESSAGE_PART (part);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
find_part_cb (GMimeObject *parent, GMimeObject *part, FindPartData *fpdata)
|
||||
{
|
||||
/* ignore other parts */
|
||||
if (!is_part_or_message_part (part))
|
||||
return;
|
||||
|
||||
/* g_printerr ("%u Type-name: %s\n", */
|
||||
/* fpdata->idx, G_OBJECT_TYPE_NAME((GObject*)part)); */
|
||||
|
||||
if (fpdata->part || fpdata->wanted_idx != fpdata->idx++)
|
||||
return; /* not yet found */
|
||||
|
||||
fpdata->part = part;
|
||||
}
|
||||
|
||||
static GMimeObject*
|
||||
find_part (MuMsg* msg, guint partidx)
|
||||
{
|
||||
FindPartData fpdata;
|
||||
|
||||
fpdata.wanted_idx = partidx;
|
||||
fpdata.idx = 0;
|
||||
fpdata.part = NULL;
|
||||
|
||||
g_mime_message_foreach (msg->_file->_mime_msg,
|
||||
(GMimeObjectForeachFunc)find_part_cb,
|
||||
&fpdata);
|
||||
return fpdata.part;
|
||||
}
|
||||
|
||||
struct _PartData {
|
||||
MuMsg *_msg;
|
||||
unsigned _idx;
|
||||
MuMsgPartForeachFunc _func;
|
||||
gpointer _user_data;
|
||||
GMimePart *_body_part;
|
||||
gboolean _recurse_rfc822;
|
||||
};
|
||||
typedef struct _PartData PartData;
|
||||
|
||||
|
||||
char*
|
||||
mu_msg_part_get_text (MuMsgPart *self, gboolean *err)
|
||||
{
|
||||
GMimeObject *mobj;
|
||||
|
||||
g_return_val_if_fail (self && self->data, NULL);
|
||||
|
||||
mobj = (GMimeObject*)self->data;
|
||||
|
||||
if (GMIME_IS_PART(mobj))
|
||||
return mu_msg_mime_part_to_string ((GMimePart*)mobj, err);
|
||||
else if (GMIME_IS_MESSAGE(mobj)) {
|
||||
/* when it's an (embedded) message, the text is just
|
||||
* of the message metadata, so we can index it.
|
||||
*/
|
||||
GString *data;
|
||||
GMimeMessage *msg;
|
||||
InternetAddressList *addresses;
|
||||
gchar *adrs;
|
||||
|
||||
msg = (GMimeMessage*)mobj;
|
||||
data = g_string_sized_new (512); /* just a guess */
|
||||
|
||||
g_string_append (data, g_mime_message_get_sender(msg));
|
||||
g_string_append_c (data, '\n');
|
||||
g_string_append (data, g_mime_message_get_subject(msg));
|
||||
g_string_append_c (data, '\n');
|
||||
|
||||
addresses = g_mime_message_get_all_recipients (msg);
|
||||
adrs = internet_address_list_to_string (addresses, FALSE);
|
||||
g_object_unref(G_OBJECT(addresses));
|
||||
|
||||
g_string_append (data, adrs);
|
||||
g_free (adrs);
|
||||
|
||||
return g_string_free (data, FALSE);
|
||||
} else
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* note: this will return -1 in case of error or if the size is
|
||||
* unknown */
|
||||
static ssize_t
|
||||
get_part_size (GMimePart *part)
|
||||
{
|
||||
GMimeDataWrapper *wrapper;
|
||||
GMimeStream *stream;
|
||||
|
||||
wrapper = g_mime_part_get_content_object (part);
|
||||
if (!wrapper)
|
||||
return -1;
|
||||
|
||||
stream = g_mime_data_wrapper_get_stream (wrapper);
|
||||
if (!stream)
|
||||
return -1; /* no stream -> size is 0 */
|
||||
else
|
||||
return g_mime_stream_length (stream);
|
||||
|
||||
/* NOTE: it seems we shouldn't unref stream/wrapper */
|
||||
}
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
init_msg_part_from_mime_part (GMimePart *part, MuMsgPart *pi)
|
||||
{
|
||||
const gchar *fname, *descr;
|
||||
GMimeContentType *ct;
|
||||
|
||||
ct = g_mime_object_get_content_type ((GMimeObject*)part);
|
||||
if (GMIME_IS_CONTENT_TYPE(ct)) {
|
||||
pi->type = (char*)g_mime_content_type_get_media_type (ct);
|
||||
pi->subtype = (char*)g_mime_content_type_get_media_subtype (ct);
|
||||
}
|
||||
|
||||
pi->disposition = (char*)g_mime_object_get_disposition
|
||||
((GMimeObject*)part);
|
||||
|
||||
fname = g_mime_part_get_filename (part);
|
||||
pi->file_name = fname ? mu_str_utf8ify (fname) : NULL;
|
||||
|
||||
descr = g_mime_part_get_content_description (part);
|
||||
pi->description = descr ? mu_str_utf8ify (descr) : NULL;
|
||||
|
||||
pi->size = get_part_size (part);
|
||||
pi->is_leaf = TRUE;
|
||||
pi->is_msg = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gchar*
|
||||
get_filename_for_mime_message_part (GMimeMessage *mmsg)
|
||||
{
|
||||
gchar *name, *cur;
|
||||
|
||||
name = (char*)g_mime_message_get_subject (mmsg);
|
||||
if (!name)
|
||||
name = "message";
|
||||
|
||||
name = g_strconcat (name, ".eml", NULL);
|
||||
|
||||
/* remove slashes... */
|
||||
for (cur = name ; *cur; ++cur) {
|
||||
if (*cur == '/' || *cur == ' ' || *cur == ':')
|
||||
*cur = '-';
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
init_msg_part_from_mime_message_part (GMimeMessage *mmsg, MuMsgPart *pi)
|
||||
{
|
||||
pi->disposition = GMIME_DISPOSITION_ATTACHMENT;
|
||||
|
||||
/* pseudo-file name... */
|
||||
pi->file_name = get_filename_for_mime_message_part (mmsg);
|
||||
pi->description = g_strdup ("message");
|
||||
|
||||
pi->type = "message";
|
||||
pi->subtype = "rfc822";
|
||||
|
||||
pi->size = 0;
|
||||
pi->is_leaf = TRUE;
|
||||
pi->is_msg = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
msg_part_free (MuMsgPart *pi)
|
||||
{
|
||||
if (!pi)
|
||||
return;
|
||||
|
||||
g_free (pi->file_name);
|
||||
g_free (pi->description);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
part_foreach_cb (GMimeObject *parent, GMimeObject *mobj, PartData *pdata)
|
||||
{
|
||||
MuMsgPart pi;
|
||||
gboolean rv;
|
||||
|
||||
/* ignore non-leaf / message parts */
|
||||
if (!is_part_or_message_part (mobj))
|
||||
return;
|
||||
|
||||
memset (&pi, 0, sizeof(pi));
|
||||
pi.index = pdata->_idx++;
|
||||
pi.content_id = (char*)g_mime_object_get_content_id (mobj);
|
||||
pi.data = (gpointer)mobj;
|
||||
/* check if this is the body part */
|
||||
pi.is_body = ((void*)pdata->_body_part == (void*)mobj);
|
||||
|
||||
if (GMIME_IS_PART(mobj))
|
||||
rv = init_msg_part_from_mime_part ((GMimePart*)mobj, &pi);
|
||||
else if (GMIME_IS_MESSAGE_PART(mobj)) {
|
||||
GMimeMessage *mmsg;
|
||||
mmsg = g_mime_message_part_get_message ((GMimeMessagePart*)mobj);
|
||||
if (!mmsg)
|
||||
return;
|
||||
rv = init_msg_part_from_mime_message_part (mmsg, &pi);
|
||||
if (rv && pdata->_recurse_rfc822)
|
||||
/* NOTE: this screws up the counting (pdata->_idx) */
|
||||
g_mime_message_foreach /* recurse */
|
||||
(mmsg, (GMimeObjectForeachFunc)part_foreach_cb,
|
||||
pdata);
|
||||
} else
|
||||
rv = FALSE; /* ignore */
|
||||
|
||||
if (rv)
|
||||
pdata->_func(pdata->_msg, &pi, pdata->_user_data);
|
||||
|
||||
msg_part_free (&pi);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
load_msg_file_maybe (MuMsg *msg)
|
||||
{
|
||||
GError *err;
|
||||
|
||||
if (msg->_file)
|
||||
return TRUE;
|
||||
|
||||
err = NULL;
|
||||
msg->_file = mu_msg_file_new (mu_msg_get_path(msg), NULL,
|
||||
&err);
|
||||
if (!msg->_file) {
|
||||
MU_HANDLE_G_ERROR(err); /* will free it */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!msg->_file->_mime_msg) {
|
||||
mu_msg_file_destroy (msg->_file);
|
||||
msg->_file = NULL;
|
||||
g_warning ("failed to create mime-msg");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_part_foreach (MuMsg *msg, gboolean recurse_rfc822,
|
||||
MuMsgPartForeachFunc func, gpointer user_data)
|
||||
{
|
||||
PartData pdata;
|
||||
GMimeMessage *mime_msg;
|
||||
|
||||
g_return_if_fail (msg);
|
||||
|
||||
if (!load_msg_file_maybe (msg))
|
||||
return;
|
||||
|
||||
mime_msg = msg->_file->_mime_msg;
|
||||
|
||||
pdata._msg = msg;
|
||||
pdata._idx = 0;
|
||||
pdata._body_part = mu_msg_mime_get_body_part (mime_msg, FALSE);
|
||||
pdata._func = func;
|
||||
pdata._user_data = user_data;
|
||||
pdata._recurse_rfc822 = recurse_rfc822;
|
||||
|
||||
g_mime_message_foreach (msg->_file->_mime_msg,
|
||||
(GMimeObjectForeachFunc)part_foreach_cb,
|
||||
&pdata);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
write_part_to_fd (GMimePart *part, int fd, GError **err)
|
||||
{
|
||||
GMimeStream *stream;
|
||||
GMimeDataWrapper *wrapper;
|
||||
gboolean rv;
|
||||
|
||||
stream = g_mime_stream_fs_new (fd);
|
||||
if (!GMIME_IS_STREAM(stream)) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"failed to create stream");
|
||||
return FALSE;
|
||||
}
|
||||
g_mime_stream_fs_set_owner (GMIME_STREAM_FS(stream), FALSE);
|
||||
|
||||
wrapper = g_mime_part_get_content_object (part);
|
||||
if (!GMIME_IS_DATA_WRAPPER(wrapper)) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"failed to create wrapper");
|
||||
g_object_unref (stream);
|
||||
return FALSE;
|
||||
}
|
||||
g_object_ref (part); /* FIXME: otherwise, the unrefs below
|
||||
* give errors...*/
|
||||
|
||||
if (g_mime_data_wrapper_write_to_stream (wrapper, stream) == -1) {
|
||||
rv = FALSE;
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"failed to write to stream");
|
||||
} else
|
||||
rv = TRUE;
|
||||
|
||||
g_object_unref (wrapper);
|
||||
g_object_unref (stream);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
write_object_to_fd (GMimeObject *obj, int fd, GError **err)
|
||||
{
|
||||
gchar *str;
|
||||
str = g_mime_object_to_string (obj);
|
||||
|
||||
if (!str) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"could not get string from object");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (write (fd, str, strlen(str)) == -1) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"failed to write object: %s",
|
||||
strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
save_mime_object (GMimeObject *obj, const char *fullpath,
|
||||
gboolean overwrite, gboolean use_existing, GError **err)
|
||||
{
|
||||
int fd;
|
||||
gboolean rv;
|
||||
|
||||
/* don't try to overwrite when we already have it; useful when
|
||||
* you're sure it's not a different file with the same name */
|
||||
if (use_existing && access (fullpath, F_OK) == 0)
|
||||
return TRUE;
|
||||
|
||||
/* ok, try to create the file */
|
||||
fd = mu_util_create_writeable_fd (fullpath, 0600, overwrite);
|
||||
if (fd == -1) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"could not open '%s' for writing: %s",
|
||||
fullpath, errno ? strerror(errno) : "error");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (GMIME_IS_PART (obj))
|
||||
rv = write_part_to_fd ((GMimePart*)obj, fd, err);
|
||||
else
|
||||
rv = write_object_to_fd (obj, fd, err);
|
||||
|
||||
if (close (fd) != 0 && !err) { /* don't write on top of old err */
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"could not close '%s': %s",
|
||||
fullpath, errno ? strerror(errno) : "error");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_msg_part_filepath (MuMsg *msg, const char* targetdir, guint partidx,
|
||||
GError **err)
|
||||
{
|
||||
char *fname, *filepath;
|
||||
GMimeObject* mobj;
|
||||
|
||||
if (!load_msg_file_maybe (msg))
|
||||
return NULL;
|
||||
|
||||
if (!(mobj = find_part (msg, partidx))) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "cannot find part %u", partidx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (GMIME_IS_PART (mobj)) {
|
||||
/* the easy case: the part has a filename */
|
||||
fname = (gchar*)g_mime_part_get_filename (GMIME_PART(mobj));
|
||||
if (fname) /* security: don't include any directory components... */
|
||||
fname = g_path_get_basename (fname);
|
||||
else
|
||||
fname = g_strdup_printf ("%x-part-%u",
|
||||
g_str_hash (mu_msg_get_path (msg)),
|
||||
partidx);
|
||||
} else if (GMIME_IS_MESSAGE_PART(mobj))
|
||||
fname = get_filename_for_mime_message_part
|
||||
(g_mime_message_part_get_message((GMimeMessagePart*)mobj));
|
||||
else {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME, "part %u cannot be saved",
|
||||
partidx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
filepath = g_build_path (G_DIR_SEPARATOR_S, targetdir ? targetdir : "",
|
||||
fname, NULL);
|
||||
g_free (fname);
|
||||
|
||||
return filepath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
gchar*
|
||||
mu_msg_part_filepath_cache (MuMsg *msg, guint partid)
|
||||
{
|
||||
char *dirname, *filepath;
|
||||
const char* path;
|
||||
|
||||
g_return_val_if_fail (msg, NULL);
|
||||
|
||||
if (!load_msg_file_maybe (msg))
|
||||
return NULL;
|
||||
|
||||
path = mu_msg_get_path (msg);
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
/* g_compute_checksum_for_string may be better, but requires
|
||||
* rel. new glib (2.16) */
|
||||
dirname = g_strdup_printf ("%s%c%x%c%u",
|
||||
mu_util_cache_dir(), G_DIR_SEPARATOR,
|
||||
g_str_hash (path), G_DIR_SEPARATOR,
|
||||
partid);
|
||||
|
||||
if (!mu_util_create_dir_maybe (dirname, 0700, FALSE)) {
|
||||
g_free (dirname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
filepath = mu_msg_part_filepath (msg, dirname, partid, NULL);
|
||||
g_free (dirname);
|
||||
if (!filepath)
|
||||
g_warning ("%s: could not get filename", __FUNCTION__);
|
||||
|
||||
return filepath;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_part_save (MuMsg *msg, const char *fullpath, guint partidx,
|
||||
gboolean overwrite, gboolean use_cached, GError **err)
|
||||
{
|
||||
GMimeObject *part;
|
||||
|
||||
g_return_val_if_fail (msg, FALSE);
|
||||
g_return_val_if_fail (fullpath, FALSE);
|
||||
g_return_val_if_fail (!overwrite||!use_cached, FALSE);
|
||||
|
||||
if (!load_msg_file_maybe (msg))
|
||||
return FALSE;
|
||||
|
||||
part = find_part (msg, partidx);
|
||||
if (!is_part_or_message_part (part)) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"unexpected type %s for part %u",
|
||||
G_OBJECT_TYPE_NAME((GObject*)part),
|
||||
partidx);
|
||||
return FALSE;
|
||||
} else
|
||||
return save_mime_object (part, fullpath, overwrite, use_cached, err);
|
||||
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_msg_part_save_temp (MuMsg *msg, guint partidx, GError **err)
|
||||
{
|
||||
gchar *filepath;
|
||||
gboolean rv;
|
||||
|
||||
filepath = mu_msg_part_filepath_cache (msg, partidx);
|
||||
if (!filepath) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"Could not get temp filepath");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rv = mu_msg_part_save (msg, filepath, partidx, FALSE, TRUE, err);
|
||||
if (!rv) {
|
||||
g_free (filepath);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return filepath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
typedef gboolean (*MatchFunc) (GMimeObject *part, gpointer data);
|
||||
|
||||
struct _MatchData {
|
||||
MatchFunc _matcher;
|
||||
gpointer _user_data;
|
||||
gint _idx, _found_idx;
|
||||
};
|
||||
typedef struct _MatchData MatchData;
|
||||
|
||||
static void
|
||||
part_match_foreach_cb (GMimeObject *parent, GMimeObject *part, MatchData *mdata)
|
||||
{
|
||||
if (mdata->_found_idx < 0)
|
||||
if (mdata->_matcher (part, mdata->_user_data))
|
||||
mdata->_found_idx = mdata->_idx;
|
||||
|
||||
++mdata->_idx;
|
||||
}
|
||||
|
||||
static int
|
||||
msg_part_find_idx (GMimeMessage *msg, MatchFunc func, gpointer user_data)
|
||||
{
|
||||
MatchData mdata;
|
||||
|
||||
g_return_val_if_fail (msg, -1);
|
||||
g_return_val_if_fail (GMIME_IS_MESSAGE(msg), -1);
|
||||
|
||||
mdata._idx = 0;
|
||||
mdata._found_idx = -1;
|
||||
mdata._matcher = func;
|
||||
mdata._user_data = user_data;
|
||||
|
||||
g_mime_message_foreach (msg,
|
||||
(GMimeObjectForeachFunc)part_match_foreach_cb,
|
||||
&mdata);
|
||||
|
||||
return mdata._found_idx;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
match_content_id (GMimeObject *part, const char *cid)
|
||||
{
|
||||
return g_strcmp0 (g_mime_object_get_content_id (part),
|
||||
cid) == 0 ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
int
|
||||
mu_msg_part_find_cid (MuMsg *msg, const char* sought_cid)
|
||||
{
|
||||
const char* cid;
|
||||
|
||||
g_return_val_if_fail (msg, -1);
|
||||
g_return_val_if_fail (sought_cid, -1);
|
||||
|
||||
if (!load_msg_file_maybe (msg))
|
||||
return -1;
|
||||
|
||||
cid = g_str_has_prefix (sought_cid, "cid:") ?
|
||||
sought_cid + 4 : sought_cid;
|
||||
|
||||
return msg_part_find_idx (msg->_file->_mime_msg,
|
||||
(MatchFunc)match_content_id,
|
||||
(gpointer)(char*)cid);
|
||||
}
|
||||
|
||||
struct _MatchData2 {
|
||||
GSList *_lst;
|
||||
const GRegex *_rx;
|
||||
guint _idx;
|
||||
};
|
||||
typedef struct _MatchData2 MatchData2;
|
||||
|
||||
|
||||
static void
|
||||
match_filename_rx (GMimeObject *parent, GMimeObject *part, MatchData2 *mdata)
|
||||
{
|
||||
const char *fname;
|
||||
|
||||
/* ignore other parts -- we need this guard so the counting of
|
||||
* parts is the same as in other functions for dealing with
|
||||
* msg parts (this is needed since we expose the numbers to
|
||||
* the user) */
|
||||
if (!is_part_or_message_part (part))
|
||||
return;
|
||||
|
||||
fname = g_mime_part_get_filename (GMIME_PART(part));
|
||||
if (!fname) {
|
||||
++mdata->_idx;
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_regex_match (mdata->_rx, fname, 0, NULL))
|
||||
mdata->_lst = g_slist_prepend (mdata->_lst,
|
||||
GUINT_TO_POINTER(mdata->_idx++));
|
||||
}
|
||||
|
||||
|
||||
GSList*
|
||||
mu_msg_part_find_files (MuMsg *msg, const GRegex *pattern)
|
||||
{
|
||||
MatchData2 mdata;
|
||||
|
||||
g_return_val_if_fail (msg, NULL);
|
||||
g_return_val_if_fail (pattern, NULL);
|
||||
|
||||
if (!load_msg_file_maybe (msg))
|
||||
return NULL;
|
||||
|
||||
mdata._lst = NULL;
|
||||
mdata._rx = pattern;
|
||||
mdata._idx = 0;
|
||||
|
||||
g_mime_message_foreach (msg->_file->_mime_msg,
|
||||
(GMimeObjectForeachFunc)match_filename_rx,
|
||||
&mdata);
|
||||
return mdata._lst;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_part_looks_like_attachment (MuMsgPart *part, gboolean include_inline)
|
||||
{
|
||||
g_return_val_if_fail (part, FALSE);
|
||||
|
||||
if (part->is_body||!part->disposition||!part->type)
|
||||
return FALSE;
|
||||
|
||||
if (include_inline ||
|
||||
g_ascii_strcasecmp (part->disposition,
|
||||
GMIME_DISPOSITION_ATTACHMENT) == 0)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
225
lib/mu-msg-part.h
Normal file
225
lib/mu-msg-part.h
Normal file
@ -0,0 +1,225 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_PART_H__
|
||||
#define __MU_MSG_PART_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuMsgPart {
|
||||
|
||||
/* index of this message */
|
||||
unsigned index;
|
||||
|
||||
/* cid */
|
||||
char *content_id;
|
||||
|
||||
/* content-type: type/subtype, ie. text/plain */
|
||||
char *type;
|
||||
char *subtype;
|
||||
/* full content-type, e.g. image/jpeg */
|
||||
/* char *content_type; */
|
||||
|
||||
/* the file name (if any) */
|
||||
char *file_name;
|
||||
|
||||
/* description (if any) */
|
||||
char *description;
|
||||
|
||||
/* usually, "attachment" or "inline" */
|
||||
char *disposition;
|
||||
|
||||
/* size of the part; or < 0 if unknown */
|
||||
ssize_t size;
|
||||
|
||||
gpointer data; /* opaque data */
|
||||
|
||||
gboolean is_body; /* TRUE if this is probably the
|
||||
* message body*/
|
||||
gboolean is_leaf; /* if the body is a leaf part (MIME
|
||||
* Part), not eg. a multipart/ */
|
||||
gboolean is_msg; /* part is a message/rfc822 */
|
||||
|
||||
/* if TRUE, mu_msg_part_destroy will free the member vars
|
||||
* as well*/
|
||||
gboolean own_members;
|
||||
};
|
||||
typedef struct _MuMsgPart MuMsgPart;
|
||||
|
||||
/**
|
||||
* macro to get the file name for this mime-part
|
||||
*
|
||||
* @param pi a MuMsgPart instance
|
||||
*
|
||||
* @return the file name
|
||||
*/
|
||||
#define mu_msg_part_file_name(pi) ((pi)->file_name)
|
||||
|
||||
|
||||
/**
|
||||
* macro to get the description for this mime-part
|
||||
*
|
||||
* @param pi a MuMsgPart instance
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
#define mu_msg_part_description(pi) ((pi)->description)
|
||||
|
||||
|
||||
/**
|
||||
* macro to get the content-id (cid) for this mime-part
|
||||
*
|
||||
* @param pi a MuMsgPart instance
|
||||
*
|
||||
* @return the file name
|
||||
*/
|
||||
#define mu_msg_part_content_id(pi) ((pi)->content_id)
|
||||
|
||||
|
||||
/**
|
||||
* get the text in the MuMsgPart (ie. in its GMimePart)
|
||||
*
|
||||
* @param part a MuMsgPart
|
||||
* @param err will receive TRUE if there was an error, FALSE otherwise
|
||||
*
|
||||
* @return utf8 string for this MIME part, to be freed by caller
|
||||
*/
|
||||
char* mu_msg_part_get_text (MuMsgPart *part, gboolean *err);
|
||||
|
||||
|
||||
/**
|
||||
* does this msg part look like an attachment?
|
||||
*
|
||||
* @param part a message part
|
||||
* @param include_inline consider 'inline' parts also as attachments
|
||||
*
|
||||
* @return TRUE if it looks like an attachment, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_part_looks_like_attachment (MuMsgPart *part,
|
||||
gboolean include_inline);
|
||||
|
||||
/**
|
||||
* save a specific attachment to some targetdir
|
||||
*
|
||||
* @param msg a valid MuMsg instance
|
||||
* @gchar filepath the filepath to save
|
||||
* @param partidx index of the attachment you want to save
|
||||
* @param overwrite overwrite existing files?
|
||||
* @param don't raise error when the file already exists
|
||||
* @param err receives error information (when function returns NULL)
|
||||
*
|
||||
* @return full path to the message part saved or NULL in case or error; free with g_free
|
||||
*/
|
||||
gboolean mu_msg_part_save (MuMsg *msg, const char *filepath, guint partidx,
|
||||
gboolean overwrite, gboolean use_cached, GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* save a message part to a temporary file and return the full path to
|
||||
* this file
|
||||
*
|
||||
* @param msg a MuMsg message
|
||||
* @param partidx index of the part to save
|
||||
* @param err receives error information if any
|
||||
*
|
||||
* @return the full path to the temp file, or NULL in case of error
|
||||
*/
|
||||
gchar* mu_msg_part_save_temp (MuMsg *msg, guint partidx, GError **err);
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get a filename for the saving the message part; try the filename
|
||||
* specified for the message part if any, otherwise determine a unique
|
||||
* name based on the partidx and the message path
|
||||
*
|
||||
* @param msg a msg
|
||||
* @param targetdir where to store the part
|
||||
* @param partidx the part for which to determine a filename
|
||||
* @param err receives error information (when function returns NULL)
|
||||
*
|
||||
* @return a filepath (g_free when done with it) or NULL in case of error
|
||||
*/
|
||||
gchar* mu_msg_part_filepath (MuMsg *msg, const char* targetdir,
|
||||
guint partidx, GError **err) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* get a full path name for a file for saving the message part INDEX;
|
||||
* this path is unique (1:1) for this particular message and part for
|
||||
* this user. Thus, it can be used as a cache.
|
||||
*
|
||||
* Will create the directory if needed.
|
||||
*
|
||||
* @param msg a msg
|
||||
* @param partidx the part for which to determine a filename
|
||||
*
|
||||
* @return a filepath (g_free when done with it) or NULL in case of error
|
||||
*/
|
||||
gchar* mu_msg_part_filepath_cache (MuMsg *msg, guint partid)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* get the part index for the message part with a certain content-id
|
||||
*
|
||||
* @param msg a message
|
||||
* @param content_id a content-id to search
|
||||
*
|
||||
* @return the part index number of the found part, or -1 if it was not found
|
||||
*/
|
||||
int mu_msg_part_find_cid (MuMsg *msg, const char* content_id);
|
||||
|
||||
/**
|
||||
* retrieve a list of indices for mime-parts with filenames matching a regex
|
||||
*
|
||||
* @param msg a message
|
||||
* @param a regular expression to match the filename with
|
||||
*
|
||||
* @return a list with indices for the files matching the pattern; the
|
||||
* indices are the GPOINTER_TO_UINT(lst->data) of the list. They must
|
||||
* be freed with g_slist_free
|
||||
*/
|
||||
GSList* mu_msg_part_find_files (MuMsg *msg, const GRegex *pattern);
|
||||
|
||||
|
||||
typedef void (*MuMsgPartForeachFunc) (MuMsg*, MuMsgPart*, gpointer);
|
||||
/**
|
||||
* call a function for each of the mime part in a message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
* @param recurse_rfc822 whether to recurse into message/rfc822 parts
|
||||
* generallly, this is only needed when indexing message contents
|
||||
* @param func a callback function to call for each contact; when
|
||||
* the callback does not return TRUE, it won't be called again
|
||||
* @param user_data a user-provide pointer that will be passed to the callback
|
||||
*
|
||||
*/
|
||||
void mu_msg_part_foreach (MuMsg *msg, gboolean recurse_rfc822,
|
||||
MuMsgPartForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_PART_H__*/
|
||||
65
lib/mu-msg-prio.c
Normal file
65
lib/mu-msg-prio.c
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include "mu-msg-prio.h"
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_prio_name (MuMsgPrio prio)
|
||||
{
|
||||
switch (prio) {
|
||||
case MU_MSG_PRIO_LOW : return "low";
|
||||
case MU_MSG_PRIO_NORMAL : return "normal";
|
||||
case MU_MSG_PRIO_HIGH : return "high";
|
||||
default : g_return_val_if_reached (NULL);
|
||||
}
|
||||
}
|
||||
|
||||
MuMsgPrio
|
||||
mu_msg_prio_from_char (char k)
|
||||
{
|
||||
g_return_val_if_fail (k == 'l' || k == 'n' || k == 'h',
|
||||
MU_MSG_PRIO_NONE);
|
||||
return (MuMsgPrio)k;
|
||||
}
|
||||
|
||||
char
|
||||
mu_msg_prio_char (MuMsgPrio prio)
|
||||
{
|
||||
if (!(prio == 'l' || prio == 'n' || prio == 'h')) {
|
||||
g_warning ("prio: %c", (char)prio);
|
||||
}
|
||||
|
||||
|
||||
g_return_val_if_fail (prio == 'l' || prio == 'n' || prio == 'h',
|
||||
0);
|
||||
|
||||
return (char)prio;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_prio_foreach (MuMsgPrioForeachFunc func, gpointer user_data)
|
||||
{
|
||||
g_return_if_fail (func);
|
||||
|
||||
func (MU_MSG_PRIO_LOW, user_data);
|
||||
func (MU_MSG_PRIO_NORMAL, user_data);
|
||||
func (MU_MSG_PRIO_HIGH, user_data);
|
||||
}
|
||||
84
lib/mu-msg-prio.h
Normal file
84
lib/mu-msg-prio.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_PRIO_H__
|
||||
#define __MU_MSG_PRIO_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
enum _MuMsgPrio {
|
||||
MU_MSG_PRIO_LOW = 'l',
|
||||
MU_MSG_PRIO_NORMAL = 'n',
|
||||
MU_MSG_PRIO_HIGH = 'h'
|
||||
};
|
||||
typedef enum _MuMsgPrio MuMsgPrio;
|
||||
|
||||
static const MuMsgPrio MU_MSG_PRIO_NONE = (MuMsgPrio)0;
|
||||
|
||||
|
||||
/**
|
||||
* get a printable name for the message priority
|
||||
* (ie., MU_MSG_PRIO_LOW=>"low" etc.)
|
||||
*
|
||||
* @param prio a message priority
|
||||
*
|
||||
* @return a printable name for this priority
|
||||
*/
|
||||
const char* mu_msg_prio_name (MuMsgPrio prio) G_GNUC_CONST;
|
||||
|
||||
|
||||
/**
|
||||
* get the MuMsgPriority corresponding to a one-character shortcut
|
||||
* ('l'=>MU_MSG_PRIO_, 'n'=>MU_MSG_PRIO_NORMAL or
|
||||
* 'h'=>MU_MSG_PRIO_HIGH)
|
||||
*
|
||||
* @param k a character
|
||||
*
|
||||
* @return a message priority
|
||||
*/
|
||||
MuMsgPrio mu_msg_prio_from_char (char k) G_GNUC_CONST;
|
||||
|
||||
|
||||
/**
|
||||
* get the one-character shortcut corresponding to a message priority
|
||||
* ('l'=>MU_MSG_PRIO_, 'n'=>MU_MSG_PRIO_NORMAL or
|
||||
* 'h'=>MU_MSG_PRIO_HIGH)
|
||||
*
|
||||
* @param prio a mesage priority
|
||||
*
|
||||
* @return a shortcut character or 0 in case of error
|
||||
*/
|
||||
char mu_msg_prio_char (MuMsgPrio prio) G_GNUC_CONST;
|
||||
|
||||
typedef void (*MuMsgPrioForeachFunc) (MuMsgPrio prio, gpointer user_data);
|
||||
/**
|
||||
* call a function for each message priority
|
||||
*
|
||||
* @param func a callback function
|
||||
* @param user_data a user pointer to pass to the callback
|
||||
*/
|
||||
void mu_msg_prio_foreach (MuMsgPrioForeachFunc func, gpointer user_data);
|
||||
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_PRIO_H__*/
|
||||
87
lib/mu-msg-priv.h
Normal file
87
lib/mu-msg-priv.h
Normal file
@ -0,0 +1,87 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_PRIV_H__
|
||||
#define __MU_MSG_PRIV_H__
|
||||
|
||||
#include <gmime/gmime.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mu-msg.h>
|
||||
#include <mu-msg-file.h>
|
||||
#include <mu-msg-doc.h>
|
||||
#include <mu-msg-cache.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuMsgFile {
|
||||
GMimeMessage *_mime_msg;
|
||||
time_t _timestamp;
|
||||
size_t _size;
|
||||
char _path [PATH_MAX + 1];
|
||||
char _maildir [PATH_MAX + 1];
|
||||
|
||||
/* list where we push allocated strings so we can
|
||||
* free them when the struct gets destroyed
|
||||
*/
|
||||
GSList *_free_later;
|
||||
};
|
||||
|
||||
|
||||
/* we put the the MuMsg definition in this separate -priv file, so we
|
||||
* can split the mu_msg implementations over separate files */
|
||||
struct _MuMsg {
|
||||
|
||||
guint _refcount;
|
||||
|
||||
/* our two backend */
|
||||
MuMsgFile *_file; /* based on GMime, ie. a file on disc */
|
||||
MuMsgDoc *_doc; /* based on Xapian::Document */
|
||||
|
||||
MuMsgCache *_cache;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* convert a GMimePart to a string
|
||||
*
|
||||
* @param part a GMimePart
|
||||
* @param err will receive TRUE if there was an error, FALSE otherwise
|
||||
*
|
||||
* @return utf8 string for this MIME part, to be freed by caller
|
||||
*/
|
||||
gchar* mu_msg_mime_part_to_string (GMimePart *part, gboolean *err);
|
||||
|
||||
|
||||
/**
|
||||
* get the MIME part that's probably the body of the message (heuristic)
|
||||
*
|
||||
* @param self a MuMsg
|
||||
* @param want_html whether it should be a html type of body
|
||||
*
|
||||
* @return the MIME part, or NULL in case of error.
|
||||
*/
|
||||
GMimePart* mu_msg_mime_get_body_part (GMimeMessage *msg, gboolean want_html);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_PRIV_H__*/
|
||||
379
lib/mu-msg-sexp.c
Normal file
379
lib/mu-msg-sexp.c
Normal file
@ -0,0 +1,379 @@
|
||||
/*
|
||||
** Copyright (C) 2011-2012 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
#include "mu-str.h"
|
||||
#include "mu-msg.h"
|
||||
#include "mu-msg-iter.h"
|
||||
#include "mu-msg-part.h"
|
||||
#include "mu-maildir.h"
|
||||
|
||||
static void
|
||||
append_sexp_attr_list (GString *gstr, const char* elm, const GSList *lst)
|
||||
{
|
||||
const GSList *cur;
|
||||
|
||||
if (!lst)
|
||||
return; /* empty list, don't include */
|
||||
|
||||
g_string_append_printf (gstr, "\t:%s ( ", elm);
|
||||
|
||||
for (cur = lst; cur; cur = g_slist_next(cur)) {
|
||||
char *str;
|
||||
str = mu_str_escape_c_literal
|
||||
((const gchar*)cur->data, TRUE);
|
||||
g_string_append_printf (gstr, "%s ", str);
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
g_string_append (gstr, ")\n");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
append_sexp_attr (GString *gstr, const char* elm, const char *str)
|
||||
{
|
||||
gchar *esc;
|
||||
|
||||
if (!str || strlen(str) == 0)
|
||||
return; /* empty: don't include */
|
||||
|
||||
esc = mu_str_escape_c_literal (str, TRUE);
|
||||
|
||||
g_string_append_printf (gstr, "\t:%s %s\n", elm, esc);
|
||||
g_free (esc);
|
||||
}
|
||||
|
||||
|
||||
struct _ContactData {
|
||||
gboolean from, to, cc, bcc, reply_to;
|
||||
GString *gstr;
|
||||
MuMsgContactType prev_ctype;
|
||||
};
|
||||
typedef struct _ContactData ContactData;
|
||||
|
||||
static gchar*
|
||||
get_name_addr_pair (MuMsgContact *c)
|
||||
{
|
||||
gchar *name, *addr, *pair;
|
||||
|
||||
name = (char*)mu_msg_contact_name(c);
|
||||
addr = (char*)mu_msg_contact_address(c);
|
||||
|
||||
name = name ? mu_str_escape_c_literal (name, TRUE) : NULL;
|
||||
addr = addr ? mu_str_escape_c_literal (addr, TRUE) : NULL;
|
||||
|
||||
pair = g_strdup_printf ("(%s . %s)",
|
||||
name ? name : "nil",
|
||||
addr ? addr : "nil");
|
||||
g_free (name);
|
||||
g_free (addr);
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
static void
|
||||
add_prefix_maybe (GString *gstr, gboolean *field, const char *prefix)
|
||||
{
|
||||
/* if there's nothing in the field yet, add the prefix */
|
||||
if (!*field)
|
||||
g_string_append (gstr, prefix);
|
||||
|
||||
*field = TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
each_contact (MuMsgContact *c, ContactData *cdata)
|
||||
{
|
||||
char *pair;
|
||||
MuMsgContactType ctype;
|
||||
|
||||
ctype = mu_msg_contact_type (c);
|
||||
|
||||
/* if the current type is not the previous type, close the
|
||||
* previous first */
|
||||
if (cdata->prev_ctype != ctype && cdata->prev_ctype != (unsigned)-1)
|
||||
g_string_append (cdata->gstr, ")\n");
|
||||
|
||||
switch (ctype) {
|
||||
|
||||
case MU_MSG_CONTACT_TYPE_FROM:
|
||||
add_prefix_maybe (cdata->gstr, &cdata->from, "\t:from (");
|
||||
break;
|
||||
|
||||
case MU_MSG_CONTACT_TYPE_TO:
|
||||
add_prefix_maybe (cdata->gstr, &cdata->to, "\t:to (");
|
||||
break;
|
||||
|
||||
case MU_MSG_CONTACT_TYPE_CC:
|
||||
add_prefix_maybe (cdata->gstr, &cdata->cc, "\t:cc (");
|
||||
break;
|
||||
|
||||
case MU_MSG_CONTACT_TYPE_BCC:
|
||||
add_prefix_maybe (cdata->gstr, &cdata->bcc, "\t:bcc (");
|
||||
break;
|
||||
|
||||
case MU_MSG_CONTACT_TYPE_REPLY_TO:
|
||||
add_prefix_maybe (cdata->gstr, &cdata->reply_to, "\t:reply-to (");
|
||||
break;
|
||||
|
||||
default: g_return_val_if_reached (FALSE);
|
||||
}
|
||||
|
||||
cdata->prev_ctype = ctype;
|
||||
|
||||
pair = get_name_addr_pair (c);
|
||||
g_string_append (cdata->gstr, pair);
|
||||
g_free (pair);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
append_sexp_contacts (GString *gstr, MuMsg *msg)
|
||||
{
|
||||
ContactData cdata;
|
||||
|
||||
cdata.from = cdata.to = cdata.cc = cdata.bcc
|
||||
= cdata.reply_to = FALSE;
|
||||
cdata.gstr = gstr;
|
||||
cdata.prev_ctype = (unsigned)-1;
|
||||
|
||||
mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact,
|
||||
&cdata);
|
||||
|
||||
if (cdata.from || cdata.to || cdata.cc || cdata.bcc || cdata.reply_to)
|
||||
gstr = g_string_append (gstr, ")\n");
|
||||
}
|
||||
|
||||
struct _FlagData {
|
||||
char *flagstr;
|
||||
MuFlags msgflags;
|
||||
};
|
||||
typedef struct _FlagData FlagData;
|
||||
|
||||
static void
|
||||
each_flag (MuFlags flag, FlagData *fdata)
|
||||
{
|
||||
if (!(flag & fdata->msgflags))
|
||||
return;
|
||||
|
||||
if (!fdata->flagstr)
|
||||
fdata->flagstr = g_strdup (mu_flag_name(flag));
|
||||
else {
|
||||
gchar *tmp;
|
||||
tmp = g_strconcat (fdata->flagstr, " ",
|
||||
mu_flag_name(flag), NULL);
|
||||
g_free (fdata->flagstr);
|
||||
fdata->flagstr = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
append_sexp_flags (GString *gstr, MuMsg *msg)
|
||||
{
|
||||
FlagData fdata;
|
||||
|
||||
fdata.msgflags = mu_msg_get_flags (msg);
|
||||
fdata.flagstr = NULL;
|
||||
|
||||
mu_flags_foreach ((MuFlagsForeachFunc)each_flag, &fdata);
|
||||
if (fdata.flagstr)
|
||||
g_string_append_printf (gstr, "\t:flags (%s)\n",
|
||||
fdata.flagstr);
|
||||
g_free (fdata.flagstr);
|
||||
}
|
||||
|
||||
static char*
|
||||
get_temp_file (MuMsg *msg, unsigned index)
|
||||
{
|
||||
char *path;
|
||||
GError *err;
|
||||
|
||||
err = NULL;
|
||||
path = mu_msg_part_filepath_cache (msg, index);
|
||||
if (!mu_msg_part_save (msg, path, index,
|
||||
FALSE/*overwrite*/, TRUE/*use cache*/, &err)) {
|
||||
g_warning ("failed to save mime part: %s",
|
||||
err->message ? err->message : "something went wrong");
|
||||
g_clear_error (&err);
|
||||
g_free (path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
struct _PartInfo {
|
||||
char *parts;
|
||||
gboolean want_images;
|
||||
};
|
||||
typedef struct _PartInfo PartInfo;
|
||||
|
||||
|
||||
/* like the elvis operator,
|
||||
* http://colinharrington.net/blog/2008/10/groovy-elvis-operator/
|
||||
*/
|
||||
static const char*
|
||||
elvis (const char *s1, const char *s2)
|
||||
{
|
||||
return s1 ? s1 : s2;
|
||||
}
|
||||
|
||||
static void
|
||||
each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo)
|
||||
{
|
||||
const char *fname;
|
||||
char *name, *tmp;
|
||||
char *tmpfile;
|
||||
|
||||
if (!(fname = mu_msg_part_file_name (part)))
|
||||
fname = mu_msg_part_description (part);
|
||||
if (fname)
|
||||
name = mu_str_escape_c_literal (fname, TRUE);
|
||||
else
|
||||
name = g_strdup_printf ("\"%s-%s-%d\"",
|
||||
elvis (part->type, "application"),
|
||||
elvis (part->subtype, "octet-stream"),
|
||||
part->index);
|
||||
|
||||
tmpfile = NULL;
|
||||
if (pinfo->want_images && g_ascii_strcasecmp (part->type, "image") == 0) {
|
||||
char *tmp;
|
||||
if ((tmp = get_temp_file (msg, part->index))) {
|
||||
tmpfile = mu_str_escape_c_literal (tmp, TRUE);
|
||||
g_free (tmp);
|
||||
}
|
||||
}
|
||||
|
||||
tmp = g_strdup_printf
|
||||
("%s(:index %d :name %s :mime-type \"%s/%s\"%s%s "
|
||||
":attachment %s :size %i)",
|
||||
elvis (pinfo->parts, ""), part->index, name,
|
||||
elvis (part->type, "application"),
|
||||
elvis (part->subtype, "octet-stream"),
|
||||
tmpfile ? " :temp" : "", tmpfile ? tmpfile : "",
|
||||
mu_msg_part_looks_like_attachment (part, TRUE) ? "t" : "nil",
|
||||
(int)part->size);
|
||||
|
||||
g_free (pinfo->parts);
|
||||
pinfo->parts = tmp;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
append_sexp_parts (GString *gstr, MuMsg *msg, gboolean want_images)
|
||||
{
|
||||
PartInfo pinfo;
|
||||
|
||||
pinfo.parts = NULL;
|
||||
pinfo.want_images = want_images;
|
||||
|
||||
mu_msg_part_foreach (msg, FALSE,
|
||||
(MuMsgPartForeachFunc)each_part, &pinfo);
|
||||
|
||||
if (pinfo.parts) {
|
||||
g_string_append_printf (gstr, "\t:parts (%s)\n", pinfo.parts);
|
||||
g_free (pinfo.parts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
append_sexp_message_file_attr (GString *gstr, MuMsg *msg)
|
||||
{
|
||||
append_sexp_attr_list (gstr, "references", mu_msg_get_references (msg));
|
||||
append_sexp_attr (gstr, "in-reply-to",
|
||||
mu_msg_get_header (msg, "In-Reply-To"));
|
||||
append_sexp_attr (gstr, "body-txt",
|
||||
mu_msg_get_body_text(msg));
|
||||
append_sexp_attr (gstr, "body-html",
|
||||
mu_msg_get_body_html(msg));
|
||||
}
|
||||
|
||||
static void
|
||||
append_sexp_thread_info (GString *gstr, const MuMsgIterThreadInfo *ti)
|
||||
{
|
||||
g_string_append_printf
|
||||
(gstr, "\t:thread (:path \"%s\":level %u%s%s%s%s)\n",
|
||||
ti->threadpath,
|
||||
ti->level,
|
||||
ti->prop & MU_MSG_ITER_THREAD_PROP_FIRST_CHILD ?
|
||||
" :first-child t" : "",
|
||||
ti->prop & MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT ?
|
||||
" :empty-parent t" : "",
|
||||
ti->prop & MU_MSG_ITER_THREAD_PROP_DUP ?
|
||||
" :duplicate t" : "",
|
||||
ti->prop & MU_MSG_ITER_THREAD_PROP_HAS_CHILD ?
|
||||
" :has-child t" : "");
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_msg_to_sexp (MuMsg *msg, unsigned docid, const MuMsgIterThreadInfo *ti,
|
||||
gboolean header_only, gboolean extract_images)
|
||||
{
|
||||
GString *gstr;
|
||||
time_t t;
|
||||
|
||||
g_return_val_if_fail (msg, NULL);
|
||||
g_return_val_if_fail (!(header_only && extract_images), NULL);
|
||||
|
||||
gstr = g_string_sized_new (header_only ? 1024 : 8192);
|
||||
g_string_append (gstr, "(\n");
|
||||
|
||||
if (docid != 0)
|
||||
g_string_append_printf (gstr, "\t:docid %u\n", docid);
|
||||
|
||||
append_sexp_contacts (gstr, msg);
|
||||
|
||||
if (ti)
|
||||
append_sexp_thread_info (gstr, ti);
|
||||
|
||||
append_sexp_attr (gstr, "subject", mu_msg_get_subject (msg));
|
||||
|
||||
t = mu_msg_get_date (msg);
|
||||
/* weird time format for emacs 29-bit ints...*/
|
||||
g_string_append_printf (gstr,"\t:date (%u %u 0)\n", (unsigned)(t >> 16),
|
||||
(unsigned)(t & 0xffff));
|
||||
g_string_append_printf (gstr, "\t:size %u\n",
|
||||
(unsigned) mu_msg_get_size (msg));
|
||||
append_sexp_attr (gstr, "message-id", mu_msg_get_msgid (msg));
|
||||
append_sexp_attr (gstr, "path", mu_msg_get_path (msg));
|
||||
append_sexp_attr (gstr, "maildir", mu_msg_get_maildir (msg));
|
||||
|
||||
g_string_append_printf (gstr, "\t:priority %s\n",
|
||||
mu_msg_prio_name(mu_msg_get_prio(msg)));
|
||||
|
||||
append_sexp_flags (gstr, msg);
|
||||
|
||||
/* headers are retrieved from the database, views from the message file
|
||||
* file attr things can only be gotten from the file (ie., mu
|
||||
* view), not from the database (mu find). */
|
||||
if (!header_only) {
|
||||
append_sexp_message_file_attr (gstr, msg);
|
||||
append_sexp_parts (gstr, msg, extract_images);
|
||||
}
|
||||
|
||||
g_string_append (gstr, ")\n");
|
||||
return g_string_free (gstr, FALSE);
|
||||
}
|
||||
845
lib/mu-msg.c
Normal file
845
lib/mu-msg.c
Normal file
@ -0,0 +1,845 @@
|
||||
/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
**
|
||||
** Copyright (C) 2008-2012 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <gmime/gmime.h>
|
||||
|
||||
#include "mu-msg-priv.h" /* include before mu-msg.h */
|
||||
#include "mu-msg.h"
|
||||
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
|
||||
#include "mu-maildir.h"
|
||||
|
||||
/* note, we do the gmime initialization here rather than in
|
||||
* mu-runtime, because this way we don't need mu-runtime for simple
|
||||
* cases -- such as our unit tests. Also note that we need gmime init
|
||||
* even for the doc backend, as we use the address parsing functions
|
||||
* also there. */
|
||||
static gboolean _gmime_initialized = FALSE;
|
||||
|
||||
static void
|
||||
gmime_init (void)
|
||||
{
|
||||
g_return_if_fail (!_gmime_initialized);
|
||||
|
||||
#ifdef GMIME_ENABLE_RFC2047_WORKAROUNDS
|
||||
g_mime_init(GMIME_ENABLE_RFC2047_WORKAROUNDS);
|
||||
#else
|
||||
g_mime_init(0);
|
||||
#endif /* GMIME_ENABLE_RFC2047_WORKAROUNDS */
|
||||
|
||||
_gmime_initialized = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gmime_uninit (void)
|
||||
{
|
||||
g_return_if_fail (_gmime_initialized);
|
||||
|
||||
g_mime_shutdown();
|
||||
_gmime_initialized = FALSE;
|
||||
}
|
||||
|
||||
|
||||
static MuMsg*
|
||||
msg_new (void)
|
||||
{
|
||||
MuMsg *self;
|
||||
|
||||
self = g_slice_new0 (MuMsg);
|
||||
|
||||
self->_refcount = 1;
|
||||
self->_cache = mu_msg_cache_new ();
|
||||
|
||||
self->_file = NULL;
|
||||
self->_doc = NULL;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
MuMsg*
|
||||
mu_msg_new_from_file (const char *path, const char *mdir, GError **err)
|
||||
{
|
||||
MuMsg *self;
|
||||
MuMsgFile *msgfile;
|
||||
|
||||
g_return_val_if_fail (path, NULL);
|
||||
|
||||
if (G_UNLIKELY(!_gmime_initialized)) {
|
||||
gmime_init ();
|
||||
atexit (gmime_uninit);
|
||||
}
|
||||
|
||||
msgfile = mu_msg_file_new (path, mdir, err);
|
||||
if (!msgfile)
|
||||
return NULL;
|
||||
|
||||
self = msg_new ();
|
||||
self->_file = msgfile;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_close_file_backend (MuMsg *msg)
|
||||
{
|
||||
g_return_if_fail (msg);
|
||||
|
||||
mu_msg_file_destroy (msg->_file);
|
||||
msg->_file = NULL;
|
||||
}
|
||||
|
||||
|
||||
MuMsg*
|
||||
mu_msg_new_from_doc (XapianDocument *doc, GError **err)
|
||||
{
|
||||
MuMsg *self;
|
||||
MuMsgDoc *msgdoc;
|
||||
|
||||
g_return_val_if_fail (doc, NULL);
|
||||
|
||||
if (G_UNLIKELY(!_gmime_initialized)) {
|
||||
gmime_init ();
|
||||
atexit (gmime_uninit);
|
||||
}
|
||||
|
||||
msgdoc = mu_msg_doc_new (doc, err);
|
||||
if (!msgdoc)
|
||||
return NULL;
|
||||
|
||||
self = msg_new ();
|
||||
self->_doc = msgdoc;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
mu_msg_destroy (MuMsg *self)
|
||||
{
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
mu_msg_file_destroy (self->_file);
|
||||
mu_msg_doc_destroy (self->_doc);
|
||||
|
||||
mu_msg_cache_destroy (self->_cache);
|
||||
|
||||
g_slice_free (MuMsg, self);
|
||||
}
|
||||
|
||||
|
||||
MuMsg*
|
||||
mu_msg_ref (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
|
||||
++self->_refcount;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
mu_msg_unref (MuMsg *self)
|
||||
{
|
||||
g_return_if_fail (self);
|
||||
g_return_if_fail (self->_refcount >= 1);
|
||||
|
||||
if (--self->_refcount == 0)
|
||||
mu_msg_destroy (self);
|
||||
}
|
||||
|
||||
|
||||
/* use this instead of mu_msg_get_path so we don't get into infinite
|
||||
* regress...*/
|
||||
static const char*
|
||||
get_path (MuMsg *self)
|
||||
{
|
||||
const char *path;
|
||||
char *val;
|
||||
gboolean do_free;
|
||||
|
||||
/* try to get the path from the cache */
|
||||
path = mu_msg_cache_str (self->_cache, MU_MSG_FIELD_ID_PATH);
|
||||
if (path)
|
||||
return path;
|
||||
|
||||
/* nothing found yet? try the doc in case we are using that
|
||||
* backend */
|
||||
val = NULL;
|
||||
if (self->_doc)
|
||||
val = mu_msg_doc_get_str_field (self->_doc,
|
||||
MU_MSG_FIELD_ID_PATH,
|
||||
&do_free);
|
||||
|
||||
/* not in the cache yet? try to get it from the file backend,
|
||||
* in case we are using that */
|
||||
if (!val && self->_file)
|
||||
val = mu_msg_file_get_str_field (self->_file,
|
||||
MU_MSG_FIELD_ID_PATH,
|
||||
&do_free);
|
||||
|
||||
/* this cannot happen unless there are bugs in mu */
|
||||
if (!val) {
|
||||
g_warning ("%s: cannot find path", __FUNCTION__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we found something */
|
||||
return mu_msg_cache_set_str (self->_cache,
|
||||
MU_MSG_FIELD_ID_PATH, val,
|
||||
do_free);
|
||||
}
|
||||
|
||||
|
||||
/* for some data, we need to read the message file from disk */
|
||||
static MuMsgFile*
|
||||
get_msg_file (MuMsg *self)
|
||||
{
|
||||
MuMsgFile *mfile;
|
||||
const char *path;
|
||||
GError *err;
|
||||
|
||||
if (!(path = get_path (self)))
|
||||
return NULL;
|
||||
|
||||
err = NULL;
|
||||
mfile = mu_msg_file_new (path, NULL, &err);
|
||||
if (!mfile) {
|
||||
g_warning ("%s: failed to create MuMsgFile: %s",
|
||||
__FUNCTION__, err->message ? err->message : "?");
|
||||
g_error_free (err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mfile;
|
||||
}
|
||||
|
||||
|
||||
static const GSList*
|
||||
get_str_list_field (MuMsg *self, MuMsgFieldId mfid)
|
||||
{
|
||||
gboolean do_free;
|
||||
GSList *val;
|
||||
|
||||
/* first we try the cache */
|
||||
if (mu_msg_cache_cached (self->_cache, mfid))
|
||||
return mu_msg_cache_str_list (self->_cache, mfid);
|
||||
|
||||
/* if it's not in the cache but it is a value retrievable from
|
||||
* the doc backend, use that */
|
||||
val = NULL;
|
||||
if (self->_doc && mu_msg_field_xapian_value (mfid))
|
||||
val = mu_msg_doc_get_str_list_field (self->_doc,
|
||||
mfid, &do_free);
|
||||
else {
|
||||
/* if we don't have a file object yet, we need to
|
||||
* create it from the file on disk */
|
||||
if (!self->_file)
|
||||
self->_file = get_msg_file (self);
|
||||
if (!self->_file && !(self->_file = get_msg_file (self)))
|
||||
return NULL;
|
||||
val = mu_msg_file_get_str_list_field (self->_file, mfid,
|
||||
&do_free);
|
||||
}
|
||||
|
||||
/* if we get a string that needs freeing, we tell the cache to
|
||||
* mark the string as such, so it will be freed when the cache
|
||||
* is freed (or when the value is overwritten) */
|
||||
return mu_msg_cache_set_str_list (self->_cache, mfid, val,
|
||||
do_free);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static const char*
|
||||
get_str_field (MuMsg *self, MuMsgFieldId mfid)
|
||||
{
|
||||
gboolean do_free;
|
||||
char *val;
|
||||
|
||||
/* first we try the cache */
|
||||
if (mu_msg_cache_cached (self->_cache, mfid))
|
||||
return mu_msg_cache_str (self->_cache, mfid);
|
||||
|
||||
/* if it's not in the cache but it is a value retrievable from
|
||||
* the doc backend, use that */
|
||||
val = NULL;
|
||||
if (self->_doc && mu_msg_field_xapian_value (mfid))
|
||||
val = mu_msg_doc_get_str_field (self->_doc, mfid, &do_free);
|
||||
else if (mu_msg_field_gmime (mfid)) {
|
||||
/* if we don't have a file object yet, we need to
|
||||
* create it from the file on disk */
|
||||
if (!self->_file)
|
||||
self->_file = get_msg_file (self);
|
||||
if (!self->_file && !(self->_file = get_msg_file (self)))
|
||||
return NULL;
|
||||
val = mu_msg_file_get_str_field (self->_file, mfid, &do_free);
|
||||
} else {
|
||||
g_warning ("%s: cannot retrieve field", __FUNCTION__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* if we get a string that needs freeing, we tell the cache to
|
||||
* mark the string as such, so it will be freed when the cache
|
||||
* is freed (or when the value is overwritten) */
|
||||
return mu_msg_cache_set_str (self->_cache, mfid, val, do_free);
|
||||
}
|
||||
|
||||
|
||||
static gint64
|
||||
get_num_field (MuMsg *self, MuMsgFieldId mfid)
|
||||
{
|
||||
guint64 val;
|
||||
|
||||
/* first try the cache */
|
||||
if (mu_msg_cache_cached (self->_cache, mfid))
|
||||
return mu_msg_cache_num (self->_cache, mfid);
|
||||
|
||||
/* if it's not in the cache but it is a value retrievable from
|
||||
* the doc backend, use that */
|
||||
val = -1;
|
||||
if (self->_doc && mu_msg_field_xapian_value (mfid))
|
||||
val = mu_msg_doc_get_num_field (self->_doc, mfid);
|
||||
else {
|
||||
/* if we don't have a file object yet, we need to
|
||||
* create it from the file on disk */
|
||||
if (!self->_file)
|
||||
self->_file = get_msg_file (self);
|
||||
if (!self->_file && !(self->_file = get_msg_file (self)))
|
||||
return -1;
|
||||
val = mu_msg_file_get_num_field (self->_file, mfid);
|
||||
}
|
||||
|
||||
return mu_msg_cache_set_num (self->_cache, mfid, val);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_header (MuMsg *self, const char *header)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (header, NULL);
|
||||
|
||||
/* if we don't have a file object yet, we need to
|
||||
* create it from the file on disk */
|
||||
if (!self->_file)
|
||||
self->_file = get_msg_file (self);
|
||||
if (!self->_file && !(self->_file = get_msg_file (self)))
|
||||
return NULL;
|
||||
|
||||
return mu_msg_file_get_header (self->_file, header);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_path (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_PATH);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_subject (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_SUBJECT);
|
||||
}
|
||||
|
||||
const char*
|
||||
mu_msg_get_msgid (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_MSGID);
|
||||
}
|
||||
|
||||
const char*
|
||||
mu_msg_get_maildir (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_MAILDIR);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_from (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_FROM);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_to (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_TO);
|
||||
}
|
||||
|
||||
const char*
|
||||
mu_msg_get_cc (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_CC);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_bcc (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_BCC);
|
||||
}
|
||||
|
||||
|
||||
time_t
|
||||
mu_msg_get_date (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, (time_t)-1);
|
||||
return (time_t)get_num_field (self, MU_MSG_FIELD_ID_DATE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
MuFlags
|
||||
mu_msg_get_flags (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, MU_FLAG_NONE);
|
||||
return (MuFlags)get_num_field (self, MU_MSG_FIELD_ID_FLAGS);
|
||||
}
|
||||
|
||||
size_t
|
||||
mu_msg_get_size (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, (size_t)-1);
|
||||
return (size_t)get_num_field (self, MU_MSG_FIELD_ID_SIZE);
|
||||
}
|
||||
|
||||
|
||||
MuMsgPrio
|
||||
mu_msg_get_prio (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, MU_MSG_PRIO_NORMAL);
|
||||
return (MuMsgPrio)get_num_field (self, MU_MSG_FIELD_ID_PRIO);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_body_html (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_BODY_HTML);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_body_text (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, MU_MSG_FIELD_ID_BODY_TEXT);
|
||||
}
|
||||
|
||||
|
||||
const GSList*
|
||||
mu_msg_get_references (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_list_field (self, MU_MSG_FIELD_ID_REFS);
|
||||
}
|
||||
|
||||
|
||||
const GSList*
|
||||
mu_msg_get_tags (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_list_field (self, MU_MSG_FIELD_ID_TAGS);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_msg_get_field_string (MuMsg *self, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_field (self, mfid);
|
||||
}
|
||||
|
||||
|
||||
const GSList*
|
||||
mu_msg_get_field_string_list (MuMsg *self, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
return get_str_list_field (self, mfid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
gint64
|
||||
mu_msg_get_field_numeric (MuMsg *self, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (self, -1);
|
||||
return get_num_field (self, mfid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
MuMsgContact *
|
||||
mu_msg_contact_new (const char *name, const char *address,
|
||||
MuMsgContactType type)
|
||||
{
|
||||
MuMsgContact *self;
|
||||
|
||||
g_return_val_if_fail (name, NULL);
|
||||
g_return_val_if_fail (address, NULL);
|
||||
g_return_val_if_fail (!mu_msg_contact_type_is_valid(type),
|
||||
NULL);
|
||||
|
||||
self = g_slice_new (MuMsgContact);
|
||||
|
||||
self->name = g_strdup (name);
|
||||
self->address = g_strdup (address);
|
||||
self->type = type;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_contact_destroy (MuMsgContact *self)
|
||||
{
|
||||
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
g_free ((void*)self->name);
|
||||
g_free ((void*)self->address);
|
||||
g_slice_free (MuMsgContact, self);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
fill_contact (MuMsgContact *self, InternetAddress *addr,
|
||||
MuMsgContactType ctype)
|
||||
{
|
||||
if (!addr)
|
||||
return FALSE;
|
||||
|
||||
self->name = internet_address_get_name (addr);
|
||||
self->type = ctype;
|
||||
|
||||
/* we only support internet mailbox addresses; if we don't
|
||||
* check, g_mime hits an assert
|
||||
*/
|
||||
if (INTERNET_ADDRESS_IS_MAILBOX(addr))
|
||||
self->address = internet_address_mailbox_get_addr
|
||||
(INTERNET_ADDRESS_MAILBOX(addr));
|
||||
else
|
||||
self->address = NULL;
|
||||
|
||||
/* note, the address could NULL e.g. when the recipient is something like
|
||||
* 'Undisclosed recipients'
|
||||
*/
|
||||
return self->address != NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
address_list_foreach (InternetAddressList *addrlist, MuMsgContactType ctype,
|
||||
MuMsgContactForeachFunc func, gpointer user_data)
|
||||
{
|
||||
int i, len;
|
||||
|
||||
if (!addrlist)
|
||||
return;
|
||||
|
||||
len = internet_address_list_length(addrlist);
|
||||
|
||||
for (i = 0; i != len; ++i) {
|
||||
MuMsgContact contact;
|
||||
if (!fill_contact(&contact,
|
||||
internet_address_list_get_address (addrlist, i),
|
||||
ctype))
|
||||
continue;
|
||||
|
||||
if (!(func)(&contact, user_data))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
addresses_foreach (const char* addrs, MuMsgContactType ctype,
|
||||
MuMsgContactForeachFunc func, gpointer user_data)
|
||||
{
|
||||
InternetAddressList *addrlist;
|
||||
|
||||
if (!addrs)
|
||||
return;
|
||||
|
||||
addrlist = internet_address_list_parse_string (addrs);
|
||||
if (addrlist) {
|
||||
address_list_foreach (addrlist, ctype, func, user_data);
|
||||
g_object_unref (addrlist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
msg_contact_foreach_file (MuMsg *msg, MuMsgContactForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
GMimeRecipientType _gmime_type;
|
||||
MuMsgContactType _type;
|
||||
} ctypes[] = {
|
||||
{GMIME_RECIPIENT_TYPE_TO, MU_MSG_CONTACT_TYPE_TO},
|
||||
{GMIME_RECIPIENT_TYPE_CC, MU_MSG_CONTACT_TYPE_CC},
|
||||
{GMIME_RECIPIENT_TYPE_BCC, MU_MSG_CONTACT_TYPE_BCC},
|
||||
};
|
||||
|
||||
/* sender */
|
||||
addresses_foreach (g_mime_message_get_sender (msg->_file->_mime_msg),
|
||||
MU_MSG_CONTACT_TYPE_FROM, func, user_data);
|
||||
|
||||
/* reply_to */
|
||||
addresses_foreach (g_mime_message_get_reply_to (msg->_file->_mime_msg),
|
||||
MU_MSG_CONTACT_TYPE_REPLY_TO, func, user_data);
|
||||
|
||||
/* get to, cc, bcc */
|
||||
for (i = 0; i != G_N_ELEMENTS(ctypes); ++i) {
|
||||
InternetAddressList *addrlist;
|
||||
addrlist = g_mime_message_get_recipients (msg->_file->_mime_msg,
|
||||
ctypes[i]._gmime_type);
|
||||
address_list_foreach (addrlist, ctypes[i]._type, func, user_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
msg_contact_foreach_doc (MuMsg *msg, MuMsgContactForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
addresses_foreach (mu_msg_get_from (msg),
|
||||
MU_MSG_CONTACT_TYPE_FROM, func, user_data);
|
||||
addresses_foreach (mu_msg_get_to (msg),
|
||||
MU_MSG_CONTACT_TYPE_TO, func, user_data);
|
||||
addresses_foreach (mu_msg_get_cc (msg),
|
||||
MU_MSG_CONTACT_TYPE_CC, func, user_data);
|
||||
addresses_foreach (mu_msg_get_bcc (msg),
|
||||
MU_MSG_CONTACT_TYPE_BCC, func, user_data);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_if_fail (msg);
|
||||
g_return_if_fail (func);
|
||||
|
||||
if (msg->_file)
|
||||
msg_contact_foreach_file (msg, func, user_data);
|
||||
else if (msg->_doc)
|
||||
msg_contact_foreach_doc (msg, func, user_data);
|
||||
else
|
||||
g_return_if_reached ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int
|
||||
cmp_str (const char* s1, const char *s2)
|
||||
{
|
||||
if (s1 == s2)
|
||||
return 0;
|
||||
else if (!s1)
|
||||
return -1;
|
||||
else if (!s2)
|
||||
return 1;
|
||||
|
||||
return g_utf8_collate (s1, s2);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
cmp_subject (const char* s1, const char *s2)
|
||||
{
|
||||
if (s1 == s2)
|
||||
return 0;
|
||||
else if (!s1)
|
||||
return -1;
|
||||
else if (!s2)
|
||||
return 1;
|
||||
|
||||
return g_utf8_collate (
|
||||
mu_str_subject_normalize (s1),
|
||||
mu_str_subject_normalize (s2));
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
mu_msg_cmp (MuMsg *m1, MuMsg *m2, MuMsgFieldId mfid)
|
||||
{
|
||||
g_return_val_if_fail (m1, 0);
|
||||
g_return_val_if_fail (m2, 0);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid(mfid), 0);
|
||||
|
||||
if (mfid == MU_MSG_FIELD_ID_SUBJECT)
|
||||
return cmp_subject (get_str_field (m1, mfid),
|
||||
get_str_field (m2, mfid));
|
||||
|
||||
if (mu_msg_field_is_string (mfid))
|
||||
return cmp_str (get_str_field (m1, mfid),
|
||||
get_str_field (m2, mfid));
|
||||
|
||||
/* TODO: note, we cast (potentially > MAXINT to int) */
|
||||
if (mu_msg_field_is_numeric (mfid))
|
||||
return get_num_field(m1, mfid) - get_num_field(m2, mfid);
|
||||
|
||||
return 0; /* TODO: handle lists */
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_msg_is_readable (MuMsg *self)
|
||||
{
|
||||
g_return_val_if_fail (self, FALSE);
|
||||
|
||||
return (access (get_str_field (self, MU_MSG_FIELD_ID_PATH), R_OK)
|
||||
== 0) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* we need do to determine the
|
||||
* /home/foo/Maildir/bar
|
||||
* from the /bar
|
||||
* that we got
|
||||
*/
|
||||
char*
|
||||
get_target_mdir (MuMsg *msg, const char *target_maildir, GError **err)
|
||||
{
|
||||
char *rootmaildir, *rv;
|
||||
const char *maildir;
|
||||
gboolean not_top_level;
|
||||
|
||||
/* maildir is the maildir stored in the message, e.g. '/foo' */
|
||||
maildir = mu_msg_get_maildir(msg);
|
||||
if (!maildir) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_GMIME,
|
||||
"message without maildir");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* the 'rootmaildir' is the filesystem path from root to
|
||||
* maildir, ie. /home/user/Maildir/foo */
|
||||
rootmaildir = mu_maildir_get_maildir_from_path (mu_msg_get_path(msg));
|
||||
if (!rootmaildir)
|
||||
return NULL;
|
||||
|
||||
/* we do a sanity check: verify that that maildir is a suffix of
|
||||
* rootmaildir;*/
|
||||
not_top_level = TRUE;
|
||||
if (!g_str_has_suffix (rootmaildir, maildir) &&
|
||||
/* special case for the top-level '/' maildir, and
|
||||
* remember not_top_level */
|
||||
(not_top_level = (g_strcmp0 (maildir, "/") != 0))) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE,
|
||||
"path is '%s', but maildir is '%s' ('%s')",
|
||||
rootmaildir, mu_msg_get_maildir(msg),
|
||||
mu_msg_get_path (msg));
|
||||
g_free (rootmaildir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* if we're not at the top-level, remove the final '/' from
|
||||
* the rootmaildir */
|
||||
if (not_top_level)
|
||||
rootmaildir[strlen(rootmaildir) -
|
||||
strlen (mu_msg_get_maildir(msg))] = '\0';
|
||||
|
||||
rv = g_strconcat (rootmaildir, target_maildir, NULL);
|
||||
g_free (rootmaildir);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* move a msg to another maildir, trying to maintain 'integrity',
|
||||
* ie. msg in 'new/' will go to new/, one in cur/ goes to cur/. be
|
||||
* super-paranoid here...
|
||||
*/
|
||||
gboolean
|
||||
mu_msg_move_to_maildir (MuMsg *self, const char *maildir,
|
||||
MuFlags flags, gboolean ignore_dups, GError **err)
|
||||
{
|
||||
char *newfullpath;
|
||||
char *targetmdir;
|
||||
|
||||
g_return_val_if_fail (self, FALSE);
|
||||
g_return_val_if_fail (maildir, FALSE); /* i.e. "/inbox" */
|
||||
|
||||
targetmdir = get_target_mdir (self, maildir, err);
|
||||
if (!targetmdir)
|
||||
return FALSE;
|
||||
|
||||
newfullpath = mu_maildir_move_message (mu_msg_get_path (self),
|
||||
targetmdir, flags,
|
||||
ignore_dups, err);
|
||||
g_free (targetmdir);
|
||||
|
||||
/* update the message path and the flags; they may have
|
||||
* changed */
|
||||
if (newfullpath) {
|
||||
mu_msg_cache_set_str (self->_cache, MU_MSG_FIELD_ID_PATH, newfullpath,
|
||||
TRUE); /* the cache will free the string */
|
||||
mu_msg_cache_set_str (self->_cache, MU_MSG_FIELD_ID_MAILDIR,
|
||||
g_strdup(maildir), TRUE);
|
||||
/* the cache will free the string */
|
||||
|
||||
/* the contentflags haven't changed, so make sure they persist */
|
||||
flags |= mu_msg_get_flags (self) &
|
||||
(MU_FLAG_HAS_ATTACH|MU_FLAG_ENCRYPTED|MU_FLAG_SIGNED);
|
||||
/* update the pseudo-flag as well */
|
||||
if (!(flags & MU_FLAG_NEW) && (flags & MU_FLAG_SEEN))
|
||||
flags &= ~MU_FLAG_UNREAD;
|
||||
else
|
||||
flags |= MU_FLAG_UNREAD;
|
||||
|
||||
mu_msg_cache_set_num (self->_cache, MU_MSG_FIELD_ID_FLAGS, flags);
|
||||
}
|
||||
|
||||
return newfullpath ? TRUE : FALSE;
|
||||
}
|
||||
528
lib/mu-msg.h
Normal file
528
lib/mu-msg.h
Normal file
@ -0,0 +1,528 @@
|
||||
/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
**
|
||||
** Copyright (C) 2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_MSG_H__
|
||||
#define __MU_MSG_H__
|
||||
|
||||
#include <mu-flags.h>
|
||||
#include <mu-msg-fields.h>
|
||||
#include <mu-msg-prio.h>
|
||||
#include <mu-util.h> /* for MuError and XapianDocument */
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuMsg;
|
||||
typedef struct _MuMsg MuMsg;
|
||||
|
||||
/**
|
||||
* create a new MuMsg* instance which parses a message and provides
|
||||
* read access to its properties; call mu_msg_unref when done with it.
|
||||
*
|
||||
* @param path full path to an email message file
|
||||
* @param mdir the maildir for this message; ie, if the path is
|
||||
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar; you can
|
||||
* pass NULL for this parameter, in which case some maildir-specific
|
||||
* information is not available.
|
||||
* @param err receive error information (MU_ERROR_FILE or
|
||||
* MU_ERROR_GMIME), or NULL. There will only be err info if the
|
||||
* function returns NULL
|
||||
*
|
||||
* @return a new MuMsg instance or NULL in case of error; call
|
||||
* mu_msg_unref when done with this message
|
||||
*/
|
||||
MuMsg *mu_msg_new_from_file (const char* filepath, const char *maildir,
|
||||
GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* create a new MuMsg* instance based on a Xapian::Document
|
||||
*
|
||||
* @param store a MuStore ptr
|
||||
* @param doc a ptr to a Xapian::Document (but cast to XapianDocument,
|
||||
* because this is C not C++). MuMsg takes _ownership_ of this pointer;
|
||||
* don't touch it afterwards
|
||||
* @param err receive error information, or NULL. There
|
||||
* will only be err info if the function returns NULL
|
||||
*
|
||||
* @return a new MuMsg instance or NULL in case of error; call
|
||||
* mu_msg_unref when done with this message
|
||||
*/
|
||||
MuMsg *mu_msg_new_from_doc (XapianDocument* doc, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* close the file-backend, if any; this function is for the use case
|
||||
* where you have a large amount of messages where you need some
|
||||
* file-backed field (body or attachments). If you don't close the
|
||||
* file-backend after retrieving the desired field, you'd quickly run
|
||||
* out of file descriptors. If this message does not have a
|
||||
* file-backend, do nothing.
|
||||
*
|
||||
* @param msg a message object
|
||||
*/
|
||||
void mu_msg_close_file_backend (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* increase the reference count for this message
|
||||
*
|
||||
* @param msg a message
|
||||
*
|
||||
* @return the message with its reference count increased, or NULL in
|
||||
* case of error.
|
||||
*/
|
||||
MuMsg *mu_msg_ref (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* decrease the reference count for this message. if the reference
|
||||
* count reaches 0, the message will be destroyed.
|
||||
*
|
||||
* @param msg a message
|
||||
*/
|
||||
void mu_msg_unref (MuMsg *msg);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* cache the values from the backend (file or db), so we don't the
|
||||
* backend anymore
|
||||
*
|
||||
* @param self a message
|
||||
*/
|
||||
void mu_msg_cache_values (MuMsg *self);
|
||||
|
||||
|
||||
/**
|
||||
* get the plain text body of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the plain text body or NULL in case of error or if there is no
|
||||
* such body. the returned string should *not* be modified or freed.
|
||||
* The returned data is in UTF8 or NULL.
|
||||
*/
|
||||
const char* mu_msg_get_body_text (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get the html body of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the html body or NULL in case of error or if there is no
|
||||
* such body. the returned string should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_body_html (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* get the sender (From:) of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the sender of this Message or NULL in case of error or if there
|
||||
* is no sender. the returned string should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_from (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get the recipients (To:) of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the sender of this Message or NULL in case of error or if there
|
||||
* are no recipients. the returned string should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_to (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get the carbon-copy recipients (Cc:) of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the Cc: recipients of this Message or NULL in case of error or if
|
||||
* there are no such recipients. the returned string should *not* be modified
|
||||
* or freed.
|
||||
*/
|
||||
const char* mu_msg_get_cc (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get the blind carbon-copy recipients (Bcc:) of this message; this
|
||||
* field usually only appears in outgoing messages
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the Bcc: recipients of this Message or NULL in case of
|
||||
* error or if there are no such recipients. the returned string
|
||||
* should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_bcc (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* get the file system path of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the path of this Message or NULL in case of error.
|
||||
* the returned string should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_path (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get the maildir this message lives in; ie, if the path is
|
||||
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the maildir requested or NULL in case of error. The returned
|
||||
* string should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_maildir (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get the subject of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the subject of this Message or NULL in case of error or if there
|
||||
* is no subject. the returned string should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_subject (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* get the Message-Id of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the Message-Id of this Message (without the enclosing <>)
|
||||
* or NULL in case of error or if there is none. the returned string
|
||||
* should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_msgid (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get any arbitrary header from this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
* @header the header requested
|
||||
*
|
||||
* @return the header requested or NULL in case of error or if there
|
||||
* is no such header. the returned string should *not* be modified or freed.
|
||||
*/
|
||||
const char* mu_msg_get_header (MuMsg *msg,
|
||||
const char* header);
|
||||
|
||||
/**
|
||||
* get the message date/time (the Date: field) as time_t, using UTC
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return message date/time or 0 in case of error or if there
|
||||
* is no such header.
|
||||
*/
|
||||
time_t mu_msg_get_date (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* get the flags for this message
|
||||
*
|
||||
* @param msg valid MuMsg* instance
|
||||
*
|
||||
* @return the fileflags as logically OR'd #Mu MsgFlags or 0 if
|
||||
* there are none.
|
||||
*/
|
||||
MuFlags mu_msg_get_flags (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get the file size in bytes of this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the filesize
|
||||
*/
|
||||
size_t mu_msg_get_size (MuMsg *msg);
|
||||
|
||||
|
||||
/**
|
||||
* get some field value as string
|
||||
*
|
||||
* @param msg a valid MuMsg instance
|
||||
* @param field the field to retrieve; it must be a string-typed field
|
||||
*
|
||||
* @return a string that should not be freed
|
||||
*/
|
||||
const char* mu_msg_get_field_string (MuMsg *msg, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
/**
|
||||
* get some field value as string-list
|
||||
*
|
||||
* @param msg a valid MuMsg instance
|
||||
* @param field the field to retrieve; it must be a string-list-typed field
|
||||
*
|
||||
* @return a list that should not be freed
|
||||
*/
|
||||
const GSList* mu_msg_get_field_string_list (MuMsg *self, MuMsgFieldId mfid);
|
||||
|
||||
/**
|
||||
* get some field value as string
|
||||
*
|
||||
* @param msg a valid MuMsg instance
|
||||
* @param field the field to retrieve; it must be a numeric field
|
||||
*
|
||||
* @return a string that should not be freed
|
||||
*/
|
||||
gint64 mu_msg_get_field_numeric (MuMsg *msg, MuMsgFieldId mfid);
|
||||
|
||||
/**
|
||||
* get the message priority for this message (MU_MSG_PRIO_LOW,
|
||||
* MU_MSG_PRIO_NORMAL or MU_MSG_PRIO_HIGH) the X-Priority,
|
||||
* X-MSMailPriority, Importance and Precedence header are checked, in
|
||||
* that order. if no known or explicit priority is set,
|
||||
* MU_MSG_PRIO_NORMAL is assumed
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the message priority (!= 0) or 0 in case of error
|
||||
*/
|
||||
MuMsgPrio mu_msg_get_prio (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* get the timestamp (mtime) for the file containing this message
|
||||
*
|
||||
* @param msg a valid MuMsg* instance
|
||||
*
|
||||
* @return the timestamp or 0 in case of error
|
||||
*/
|
||||
time_t mu_msg_get_timestamp (MuMsg *msg);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get a specific header from the message. This value will _not_ be
|
||||
* cached
|
||||
*
|
||||
* @param self a MuMsg instance
|
||||
* @param header a specific header (like 'X-Mailer' or 'Organization')
|
||||
*
|
||||
* @return a header string which is valid as long as this MuMsg is
|
||||
*/
|
||||
const char* mu_msg_get_header (MuMsg *self, const char *header);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get the list of references, with the direct parent as the final
|
||||
* one; this final one is typically the 'In-reply-to' field. Note, any
|
||||
* reference (message-id) will appear at most once, duplicates are
|
||||
* filtered out.
|
||||
*
|
||||
* @param msg a valid MuMsg
|
||||
*
|
||||
* @return a list with the references for this msg. Don't modify/free
|
||||
*/
|
||||
const GSList* mu_msg_get_references (MuMsg *msg);
|
||||
|
||||
/**
|
||||
* get the list of tags (ie., X-Label)
|
||||
*
|
||||
* @param msg a valid MuMsg
|
||||
*
|
||||
* @return a list with the tags for this msg. Don't modify/free
|
||||
*/
|
||||
const GSList* mu_msg_get_tags (MuMsg *self);
|
||||
|
||||
|
||||
/**
|
||||
* compare two messages for sorting
|
||||
*
|
||||
* @param m1 a message
|
||||
* @param m2 another message
|
||||
* @param mfid the message to use for the comparison
|
||||
*
|
||||
* @return negative if m1 is smaller, positive if m1 is smaller, 0 if
|
||||
* they are equal
|
||||
*/
|
||||
int mu_msg_cmp (MuMsg *m1, MuMsg *m2, MuMsgFieldId mfid);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* check whether there there's a readable file behind this message
|
||||
*
|
||||
* @param self a MuMsg*
|
||||
*
|
||||
* @return TRUE if the message file is readable, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_is_readable (MuMsg *self);
|
||||
|
||||
|
||||
struct _MuMsgIterThreadInfo;
|
||||
|
||||
|
||||
/**
|
||||
* convert the msg to a Lisp symbolic expression (for further processing in
|
||||
* e.g. emacs)
|
||||
*
|
||||
* @param msg a valid message
|
||||
* @param docid the docid for this message, or 0
|
||||
* @param ti thread info for the current message, or NULL
|
||||
* @param headers if TRUE, only include message fields which can be
|
||||
* obtained from the database (this is much faster if the MuMsg is
|
||||
* database-backed, so no file needs to be opened)
|
||||
* @param extract_images if TRUE, extract image attachments as temporary
|
||||
* files and include links to those in the sexp
|
||||
*
|
||||
* @return a string with the sexp (free with g_free) or NULL in case of error
|
||||
*/
|
||||
char* mu_msg_to_sexp (MuMsg *msg, unsigned docid,
|
||||
const struct _MuMsgIterThreadInfo *ti,
|
||||
gboolean headers_only, gboolean extract_images);
|
||||
|
||||
/**
|
||||
* move a message to another maildir; note that this does _not_ update
|
||||
* the database
|
||||
*
|
||||
* @param msg a message with an existing file system path in an actual
|
||||
* maildir
|
||||
* @param maildir the subdir where the message should go, relative to
|
||||
* rootmaildir. e.g. "/archive"
|
||||
* @param flags to set for the target (influences the filename, path)
|
||||
* @param silently ignore the src=target case (return TRUE)
|
||||
* @param err (may be NULL) may contain error information; note if the
|
||||
* function return FALSE, err is not set for all error condition
|
||||
* (ie. not for parameter error
|
||||
*
|
||||
* @return TRUE if it worked, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_msg_move_to_maildir (MuMsg *msg, const char *maildir,
|
||||
MuFlags flags, gboolean ignore_dups,
|
||||
GError **err);
|
||||
|
||||
|
||||
enum _MuMsgContactType { /* Reply-To:? */
|
||||
MU_MSG_CONTACT_TYPE_TO = 0,
|
||||
MU_MSG_CONTACT_TYPE_FROM,
|
||||
MU_MSG_CONTACT_TYPE_CC,
|
||||
MU_MSG_CONTACT_TYPE_BCC,
|
||||
MU_MSG_CONTACT_TYPE_REPLY_TO,
|
||||
|
||||
MU_MSG_CONTACT_TYPE_NUM
|
||||
};
|
||||
typedef guint MuMsgContactType;
|
||||
|
||||
/* not a 'real' contact type */
|
||||
#define MU_MSG_CONTACT_TYPE_ALL (MU_MSG_CONTACT_TYPE_NUM + 1)
|
||||
|
||||
#define mu_msg_contact_type_is_valid(MCT)\
|
||||
((MCT) < MU_MSG_CONTACT_TYPE_NUM)
|
||||
|
||||
struct _MuMsgContact {
|
||||
const char *name; /* Foo Bar */
|
||||
const char *address; /* foo@bar.cuux */
|
||||
MuMsgContactType type; /* MU_MSG_CONTACT_TYPE_{ TO,
|
||||
* CC, BCC, FROM, REPLY_TO} */
|
||||
};
|
||||
typedef struct _MuMsgContact MuMsgContact;
|
||||
|
||||
/**
|
||||
* create a new MuMsgContact object; note, in many case, this is not
|
||||
* needed, any a stack-allocated struct can be uses.
|
||||
*
|
||||
* @param name the name of the contact
|
||||
* @param address the e-mail address of the contact
|
||||
* @param type the type of contact: cc, bcc, from, to
|
||||
*
|
||||
* @return a newly allocated MuMsgConcact or NULL in case of
|
||||
* error. use mu_msg_contact_destroy to destroy it when it's no longer
|
||||
* needed.
|
||||
*/
|
||||
MuMsgContact *mu_msg_contact_new (const char *name, const char *address,
|
||||
MuMsgContactType type)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* destroy a MuMsgConcact object
|
||||
*
|
||||
* @param contact a contact object, or NULL
|
||||
*/
|
||||
void mu_msg_contact_destroy (MuMsgContact *contact);
|
||||
|
||||
/**
|
||||
* macro to get the name of a contact
|
||||
*
|
||||
* @param ct a MuMsgContact
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
#define mu_msg_contact_name(ct) ((ct)->name)
|
||||
|
||||
/**
|
||||
* macro to get the address of a contact
|
||||
*
|
||||
* @param ct a MuMsgContact
|
||||
*
|
||||
* @return the address
|
||||
*/
|
||||
#define mu_msg_contact_address(ct) ((ct)->address)
|
||||
|
||||
/**
|
||||
* macro to get the contact type
|
||||
*
|
||||
* @param ct a MuMsgContact
|
||||
*
|
||||
* @return the contact type
|
||||
*/
|
||||
#define mu_msg_contact_type(ct) ((ct)->type)
|
||||
|
||||
|
||||
/**
|
||||
* callback function
|
||||
*
|
||||
* @param contact
|
||||
* @param user_data a user provided data pointer
|
||||
*
|
||||
* @return TRUE if we should continue the foreach, FALSE otherwise
|
||||
*/
|
||||
typedef gboolean (*MuMsgContactForeachFunc) (MuMsgContact* contact,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* call a function for each of the contacts in a message; the order is:
|
||||
* from to cc bcc (of each there are zero or more)
|
||||
*
|
||||
* @param msg a valid MuMsgGMime* instance
|
||||
* @param func a callback function to call for each contact; when
|
||||
* the callback does not return TRUE, it won't be called again
|
||||
* @param user_data a user-provide pointer that will be passed to the callback
|
||||
*
|
||||
*/
|
||||
void mu_msg_contact_foreach (MuMsg *msg, MuMsgContactForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_MSG_H__*/
|
||||
360
lib/mu-query.cc
Normal file
360
lib/mu-query.cc
Normal file
@ -0,0 +1,360 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <xapian.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include "mu-query.h"
|
||||
#include "mu-msg-fields.h"
|
||||
|
||||
#include "mu-msg-iter.h"
|
||||
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-date.h"
|
||||
|
||||
/*
|
||||
* custom parser for date ranges
|
||||
*/
|
||||
class MuDateRangeProcessor : public Xapian::StringValueRangeProcessor {
|
||||
public:
|
||||
MuDateRangeProcessor():
|
||||
Xapian::StringValueRangeProcessor(
|
||||
(Xapian::valueno)MU_MSG_FIELD_ID_DATE) {}
|
||||
|
||||
Xapian::valueno operator()(std::string &begin, std::string &end) {
|
||||
|
||||
if (!clear_prefix (begin))
|
||||
return Xapian::BAD_VALUENO;
|
||||
|
||||
begin = to_sortable (begin, true);
|
||||
end = to_sortable (end, false);
|
||||
|
||||
if (begin > end)
|
||||
throw Xapian::QueryParserError
|
||||
("end time is before begin");
|
||||
|
||||
return (Xapian::valueno)MU_MSG_FIELD_ID_DATE;
|
||||
}
|
||||
private:
|
||||
std::string to_sortable (std::string& s, bool is_begin) {
|
||||
|
||||
const char* str;
|
||||
time_t t;
|
||||
|
||||
str = mu_date_interpret_s (s.c_str(), is_begin ? TRUE: FALSE);
|
||||
str = mu_date_complete_s (str, is_begin ? TRUE: FALSE);
|
||||
t = mu_date_str_to_time_t (str, TRUE /*local*/);
|
||||
str = mu_date_time_t_to_str_s (t, FALSE /*UTC*/);
|
||||
|
||||
return s = std::string(str);
|
||||
}
|
||||
|
||||
|
||||
bool clear_prefix (std::string& begin) {
|
||||
|
||||
const std::string colon (":");
|
||||
const std::string name (mu_msg_field_name
|
||||
(MU_MSG_FIELD_ID_DATE) + colon);
|
||||
const std::string shortcut (
|
||||
std::string(1, mu_msg_field_shortcut
|
||||
(MU_MSG_FIELD_ID_DATE)) + colon);
|
||||
|
||||
if (begin.find (name) == 0) {
|
||||
begin.erase (0, name.length());
|
||||
return true;
|
||||
} else if (begin.find (shortcut) == 0) {
|
||||
begin.erase (0, shortcut.length());
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
class MuSizeRangeProcessor : public Xapian::NumberValueRangeProcessor {
|
||||
public:
|
||||
MuSizeRangeProcessor():
|
||||
Xapian::NumberValueRangeProcessor(MU_MSG_FIELD_ID_SIZE) {
|
||||
}
|
||||
|
||||
Xapian::valueno operator()(std::string &begin, std::string &end) {
|
||||
|
||||
if (!clear_prefix (begin))
|
||||
return Xapian::BAD_VALUENO;
|
||||
|
||||
if (!substitute_size (begin) || !substitute_size (end))
|
||||
return Xapian::BAD_VALUENO;
|
||||
|
||||
/* swap if b > e */
|
||||
if (begin > end)
|
||||
std::swap (begin, end);
|
||||
|
||||
begin = Xapian::sortable_serialise (atol(begin.c_str()));
|
||||
end = Xapian::sortable_serialise (atol(end.c_str()));
|
||||
|
||||
return (Xapian::valueno)MU_MSG_FIELD_ID_SIZE;
|
||||
}
|
||||
private:
|
||||
bool clear_prefix (std::string& begin) {
|
||||
|
||||
const std::string colon (":");
|
||||
const std::string name (mu_msg_field_name
|
||||
(MU_MSG_FIELD_ID_SIZE) + colon);
|
||||
const std::string shortcut (
|
||||
std::string(1, mu_msg_field_shortcut
|
||||
(MU_MSG_FIELD_ID_SIZE)) + colon);
|
||||
|
||||
if (begin.find (name) == 0) {
|
||||
begin.erase (0, name.length());
|
||||
return true;
|
||||
} else if (begin.find (shortcut) == 0) {
|
||||
begin.erase (0, shortcut.length());
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool substitute_size (std::string& size) {
|
||||
gchar str[16];
|
||||
gint64 num = mu_str_size_parse_bkm(size.c_str());
|
||||
if (num < 0)
|
||||
throw Xapian::QueryParserError ("invalid size");
|
||||
snprintf (str, sizeof(str), "%" G_GUINT64_FORMAT, num);
|
||||
size = str;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void add_prefix (MuMsgFieldId field, Xapian::QueryParser* qparser);
|
||||
|
||||
struct _MuQuery {
|
||||
public:
|
||||
_MuQuery (MuStore *store): _store(mu_store_ref(store)) {
|
||||
|
||||
_qparser.set_database (db());
|
||||
_qparser.set_default_op (Xapian::Query::OP_AND);
|
||||
|
||||
_qparser.add_valuerangeprocessor (&_date_range_processor);
|
||||
_qparser.add_valuerangeprocessor (&_size_range_processor);
|
||||
|
||||
mu_msg_field_foreach ((MuMsgFieldForeachFunc)add_prefix,
|
||||
&_qparser);
|
||||
}
|
||||
|
||||
~_MuQuery () { mu_store_unref (_store); }
|
||||
|
||||
Xapian::Database& db() const {
|
||||
Xapian::Database* db;
|
||||
db = reinterpret_cast<Xapian::Database*>
|
||||
(mu_store_get_read_only_database (_store));
|
||||
if (!db)
|
||||
throw std::runtime_error ("no database");
|
||||
return *db;
|
||||
}
|
||||
Xapian::QueryParser& query_parser () { return _qparser; }
|
||||
|
||||
private:
|
||||
Xapian::QueryParser _qparser;
|
||||
MuDateRangeProcessor _date_range_processor;
|
||||
MuSizeRangeProcessor _size_range_processor;
|
||||
|
||||
MuStore *_store;
|
||||
};
|
||||
|
||||
static const Xapian::Query
|
||||
get_query (MuQuery *mqx, const char* searchexpr, GError **err)
|
||||
{
|
||||
Xapian::Query query;
|
||||
char *preprocessed;
|
||||
|
||||
preprocessed = mu_query_preprocess (searchexpr, err);
|
||||
if (!preprocessed)
|
||||
throw std::runtime_error
|
||||
("parse error while preprocessing query");
|
||||
|
||||
try {
|
||||
query = mqx->query_parser().parse_query
|
||||
(preprocessed,
|
||||
Xapian::QueryParser::FLAG_BOOLEAN |
|
||||
Xapian::QueryParser::FLAG_PURE_NOT |
|
||||
Xapian::QueryParser::FLAG_WILDCARD |
|
||||
Xapian::QueryParser::FLAG_AUTO_SYNONYMS |
|
||||
Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE
|
||||
);
|
||||
g_free (preprocessed);
|
||||
return query;
|
||||
|
||||
} catch (...) {
|
||||
mu_util_g_set_error (err,MU_ERROR_XAPIAN_QUERY,
|
||||
"parse error in query");
|
||||
g_free (preprocessed);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
add_prefix (MuMsgFieldId mfid, Xapian::QueryParser* qparser)
|
||||
{
|
||||
if (!mu_msg_field_xapian_index(mfid) &&
|
||||
!mu_msg_field_xapian_term(mfid) &&
|
||||
!mu_msg_field_xapian_contact(mfid))
|
||||
return;
|
||||
|
||||
try {
|
||||
const std::string pfx
|
||||
(1, mu_msg_field_xapian_prefix (mfid));
|
||||
const std::string shortcut
|
||||
(1, mu_msg_field_shortcut (mfid));
|
||||
|
||||
if (mu_msg_field_uses_boolean_prefix (mfid)) {
|
||||
qparser->add_boolean_prefix
|
||||
(mu_msg_field_name(mfid), pfx);
|
||||
qparser->add_boolean_prefix (shortcut, pfx);
|
||||
} else {
|
||||
qparser->add_prefix
|
||||
(mu_msg_field_name(mfid), pfx);
|
||||
qparser->add_prefix (shortcut, pfx);
|
||||
}
|
||||
|
||||
if (!mu_msg_field_needs_prefix(mfid))
|
||||
qparser->add_prefix ("", pfx);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
MuQuery*
|
||||
mu_query_new (MuStore *store, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, NULL);
|
||||
|
||||
if (mu_store_count (store, err) == 0) {
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_IS_EMPTY,
|
||||
"database is empty");
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return new MuQuery (store);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN (err,
|
||||
MU_ERROR_XAPIAN, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_query_destroy (MuQuery *self)
|
||||
{
|
||||
try { delete self; } MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
|
||||
/* preprocess a query to make them a bit more promiscuous */
|
||||
char*
|
||||
mu_query_preprocess (const char *query, GError **err)
|
||||
{
|
||||
GSList *parts, *cur;
|
||||
gchar *myquery;
|
||||
|
||||
g_return_val_if_fail (query, NULL);
|
||||
|
||||
/* convert the query to a list of query terms, and escape them
|
||||
* separately */
|
||||
parts = mu_str_esc_to_list (query, err);
|
||||
if (!parts)
|
||||
return NULL;
|
||||
|
||||
for (cur = parts; cur; cur = g_slist_next(cur)) {
|
||||
/* remove accents and turn to lower-case */
|
||||
cur->data = mu_str_normalize_in_place ((gchar*)cur->data, TRUE);
|
||||
/* escape '@', single '_' and ':' if it's not following a
|
||||
* xapian-pfx with '_' */
|
||||
cur->data = mu_str_xapian_escape_in_place
|
||||
((gchar*)cur->data, TRUE /*escape spaces too*/);
|
||||
}
|
||||
|
||||
myquery = mu_str_from_list (parts, ' ');
|
||||
mu_str_free_list (parts);
|
||||
|
||||
return myquery;
|
||||
}
|
||||
|
||||
|
||||
MuMsgIter*
|
||||
mu_query_run (MuQuery *self, const char* searchexpr, gboolean threads,
|
||||
MuMsgFieldId sortfieldid, gboolean revert, int maxnum,
|
||||
GError **err)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (searchexpr, NULL);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid (sortfieldid) ||
|
||||
sortfieldid == MU_MSG_FIELD_ID_NONE,
|
||||
NULL);
|
||||
try {
|
||||
Xapian::Enquire enq (self->db());
|
||||
|
||||
/* note, when our result will be *threaded*, we sort
|
||||
* there, and don't let Xapian do any sorting */
|
||||
if (!threads && sortfieldid != MU_MSG_FIELD_ID_NONE)
|
||||
enq.set_sort_by_value ((Xapian::valueno)sortfieldid,
|
||||
revert ? true : false);
|
||||
if (!mu_str_is_empty(searchexpr) &&
|
||||
g_strcmp0 (searchexpr, "\"\"") != 0) /* NULL or "" or """" */
|
||||
enq.set_query(get_query (self, searchexpr, err));
|
||||
else
|
||||
enq.set_query(Xapian::Query::MatchAll);
|
||||
|
||||
enq.set_cutoff(0,0);
|
||||
|
||||
return mu_msg_iter_new (
|
||||
reinterpret_cast<XapianEnquire*>(&enq),
|
||||
maxnum <= 0 ? self->db().get_doccount() : maxnum,
|
||||
threads,
|
||||
threads ? sortfieldid : MU_MSG_FIELD_ID_NONE,
|
||||
revert);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN (err, MU_ERROR_XAPIAN, 0);
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_query_as_string (MuQuery *self, const char *searchexpr, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (searchexpr, NULL);
|
||||
|
||||
try {
|
||||
Xapian::Query query (get_query(self, searchexpr, err));
|
||||
return g_strdup(query.get_description().c_str());
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(NULL);
|
||||
}
|
||||
121
lib/mu-query.h
Normal file
121
lib/mu-query.h
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_QUERY_H__
|
||||
#define __MU_QUERY_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <mu-store.h>
|
||||
#include <mu-msg-iter.h>
|
||||
#include <mu-util.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuQuery;
|
||||
typedef struct _MuQuery MuQuery;
|
||||
|
||||
/**
|
||||
* create a new MuQuery instance.
|
||||
*
|
||||
* @param store a MuStore object
|
||||
* @param err receives error information (if there is any); if
|
||||
* function returns non-NULL, err will _not_be set. err can be NULL
|
||||
* possble errors (err->code) are MU_ERROR_XAPIAN_DIR and
|
||||
* MU_ERROR_XAPIAN_NOT_UPTODATE
|
||||
*
|
||||
* @return a new MuQuery instance, or NULL in case of error.
|
||||
* when the instance is no longer needed, use mu_query_destroy
|
||||
* to free it
|
||||
*/
|
||||
MuQuery *mu_query_new (MuStore *store, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* destroy the MuQuery instance
|
||||
*
|
||||
* @param self a MuQuery instance, or NULL
|
||||
*/
|
||||
void mu_query_destroy (MuQuery *self);
|
||||
|
||||
|
||||
/**
|
||||
* get a version string for the database
|
||||
*
|
||||
* @param store a valid MuQuery
|
||||
*
|
||||
* @return the version string (free with g_free), or NULL in case of error
|
||||
*/
|
||||
char* mu_query_version (MuQuery *store)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* run a Xapian query; for the syntax, please refer to the mu-find
|
||||
* manpage, or http://xapian.org/docs/queryparser.html
|
||||
*
|
||||
* @param self a valid MuQuery instance
|
||||
* @param expr the search expression; use "" to match all messages
|
||||
* @param threads calculate message-threads
|
||||
* @param sortfield the field id to sort by or MU_MSG_FIELD_ID_NONE if
|
||||
* sorting is not desired
|
||||
* @param reverse if TRUE, sort in descending (Z-A) order, otherwise,
|
||||
* sort in descending (A-Z) order
|
||||
* @param maxnum maximum number of search results to return, or <= 0 for
|
||||
* unlimited
|
||||
* @param err receives error information (if there is any); if
|
||||
* function returns non-NULL, err will _not_be set. err can be NULL
|
||||
* possible error (err->code) is MU_ERROR_QUERY,
|
||||
*
|
||||
* @return a MuMsgIter instance you can iterate over, or NULL in
|
||||
* case of error
|
||||
*/
|
||||
MuMsgIter* mu_query_run (MuQuery *self, const char* expr, gboolean threads,
|
||||
MuMsgFieldId sortfieldid, gboolean ascending, int maxnum,
|
||||
GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get a string representation of the Xapian search query
|
||||
*
|
||||
* @param self a MuQuery instance
|
||||
* @param searchexpr a xapian search expression
|
||||
* @param err receives error information (if there is any); if
|
||||
* function returns non-NULL, err will _not_be set. err can be NULL
|
||||
*
|
||||
* @return the string representation of the xapian query, or NULL in case of
|
||||
* error; free the returned value with g_free
|
||||
*/
|
||||
char* mu_query_as_string (MuQuery *self, const char* searchexpr, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* pre-process the query; this function is useful mainly for debugging mu
|
||||
*
|
||||
* @param query a query string
|
||||
*
|
||||
* @return a pre-processed query, free it with g_free
|
||||
*/
|
||||
char* mu_query_preprocess (const char *query, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_QUERY_H__*/
|
||||
209
lib/mu-runtime.c
Normal file
209
lib/mu-runtime.c
Normal file
@ -0,0 +1,209 @@
|
||||
/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
**
|
||||
** Copyright (C) 2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#include "mu-runtime.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <locale.h> /* for setlocale() */
|
||||
#include <stdio.h> /* for fileno() */
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "mu-msg.h"
|
||||
#include "mu-log.h"
|
||||
#include "mu-util.h"
|
||||
|
||||
#define MU_XAPIAN_DIRNAME "xapian"
|
||||
#define MU_BOOKMARKS_FILENAME "bookmarks"
|
||||
#define MU_CACHE_DIRNAME "cache"
|
||||
#define MU_CONTACTS_FILENAME "contacts"
|
||||
#define MU_LOG_DIRNAME "log"
|
||||
|
||||
|
||||
struct _MuRuntimeData {
|
||||
gchar *_str[MU_RUNTIME_PATH_NUM];
|
||||
gchar *_name; /* e.g., 'mu', 'mug' */
|
||||
};
|
||||
typedef struct _MuRuntimeData MuRuntimeData;
|
||||
|
||||
/* static, global data for this singleton */
|
||||
static gboolean _initialized = FALSE;
|
||||
static MuRuntimeData *_data = NULL;
|
||||
|
||||
static void runtime_free (void);
|
||||
static gboolean init_paths (const char* muhome, MuRuntimeData *data);
|
||||
static const char* runtime_path (MuRuntimePath path);
|
||||
|
||||
|
||||
static gboolean
|
||||
init_log (const char *muhome, const char *name,
|
||||
gboolean log_stderr, gboolean quiet, gboolean debug)
|
||||
{
|
||||
gboolean rv;
|
||||
char *logpath;
|
||||
|
||||
if (log_stderr)
|
||||
return mu_log_init_with_fd (fileno(stderr), FALSE,
|
||||
quiet, debug);
|
||||
|
||||
logpath = g_strdup_printf ("%s%c%s%c%s.log",
|
||||
muhome, G_DIR_SEPARATOR,
|
||||
MU_LOG_DIRNAME, G_DIR_SEPARATOR,
|
||||
name);
|
||||
rv = mu_log_init (logpath, TRUE, quiet, debug);
|
||||
g_free (logpath);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
gboolean
|
||||
mu_runtime_init (const char* muhome_arg, const char *name)
|
||||
{
|
||||
gchar *muhome;
|
||||
|
||||
g_return_val_if_fail (!_initialized, FALSE);
|
||||
g_return_val_if_fail (name, FALSE);
|
||||
|
||||
setlocale (LC_ALL, "");
|
||||
g_type_init ();
|
||||
|
||||
if (muhome_arg)
|
||||
muhome = g_strdup (muhome_arg);
|
||||
else
|
||||
muhome = mu_util_guess_mu_homedir ();
|
||||
|
||||
if (!mu_util_create_dir_maybe (muhome, 0700, TRUE)) {
|
||||
g_printerr ("mu: invalid mu homedir specified;"
|
||||
" use --muhome=<dir>\n");
|
||||
runtime_free ();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
_data = g_new0 (MuRuntimeData, 1);
|
||||
_data->_str[MU_RUNTIME_PATH_MUHOME] = muhome;
|
||||
init_paths (muhome, _data);
|
||||
_data->_name = g_strdup (name);
|
||||
|
||||
if (!init_log (muhome, name, FALSE, TRUE, FALSE)) {
|
||||
runtime_free ();
|
||||
g_free (muhome);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return _initialized = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
runtime_free (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i != MU_RUNTIME_PATH_NUM; ++i)
|
||||
g_free (_data->_str[i]);
|
||||
|
||||
g_free (_data->_name);
|
||||
|
||||
/* mu_config_uninit (_data->_config); */
|
||||
|
||||
mu_log_uninit();
|
||||
|
||||
g_free (_data);
|
||||
}
|
||||
|
||||
void
|
||||
mu_runtime_uninit (void)
|
||||
{
|
||||
g_return_if_fail (_initialized);
|
||||
|
||||
runtime_free ();
|
||||
|
||||
_initialized = FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
create_dirs_maybe (MuRuntimeData *data)
|
||||
{
|
||||
if (!mu_util_create_dir_maybe
|
||||
(data->_str[MU_RUNTIME_PATH_CACHE], 0700, TRUE)) {
|
||||
g_warning ("failed to create cache dir");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!mu_util_create_dir_maybe
|
||||
(data->_str[MU_RUNTIME_PATH_LOG], 0700, TRUE)) {
|
||||
g_warning ("failed to create log dir");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
init_paths (const char* muhome, MuRuntimeData *data)
|
||||
{
|
||||
data->_str [MU_RUNTIME_PATH_XAPIANDB] =
|
||||
g_strdup_printf ("%s%c%s", muhome, G_DIR_SEPARATOR,
|
||||
MU_XAPIAN_DIRNAME);
|
||||
|
||||
data->_str [MU_RUNTIME_PATH_BOOKMARKS] =
|
||||
g_strdup_printf ("%s%c%s", muhome, G_DIR_SEPARATOR,
|
||||
MU_BOOKMARKS_FILENAME);
|
||||
|
||||
data->_str [MU_RUNTIME_PATH_CACHE] =
|
||||
g_strdup_printf ("%s%c%s", muhome, G_DIR_SEPARATOR,
|
||||
MU_CACHE_DIRNAME);
|
||||
|
||||
data->_str [MU_RUNTIME_PATH_CONTACTS] =
|
||||
g_strdup_printf ("%s%c%s", data->_str[MU_RUNTIME_PATH_CACHE],
|
||||
G_DIR_SEPARATOR, MU_CONTACTS_FILENAME);
|
||||
|
||||
data->_str [MU_RUNTIME_PATH_LOG] =
|
||||
g_strdup_printf ("%s%c%s", muhome,
|
||||
G_DIR_SEPARATOR, MU_LOG_DIRNAME);
|
||||
|
||||
if (!create_dirs_maybe (data))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* so we can called when _initialized is FALSE still */
|
||||
static const char*
|
||||
runtime_path (MuRuntimePath path)
|
||||
{
|
||||
return _data->_str[path];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char*
|
||||
mu_runtime_path (MuRuntimePath path)
|
||||
{
|
||||
g_return_val_if_fail (_initialized, NULL);
|
||||
g_return_val_if_fail (path < MU_RUNTIME_PATH_NUM, NULL);
|
||||
|
||||
return runtime_path (path);
|
||||
}
|
||||
86
lib/mu-runtime.h
Normal file
86
lib/mu-runtime.h
Normal file
@ -0,0 +1,86 @@
|
||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
**
|
||||
** Copyright (C) 2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
#ifndef __MU_RUNTIME_H__
|
||||
#define __MU_RUNTIME_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* initialize the mu runtime system; initializes logging and other
|
||||
* systems. To uninitialize, use mu_runtime_uninit
|
||||
*
|
||||
* @param muhome path where to find the mu home directory (typicaly, ~/.mu)
|
||||
* @param name of the main program, ie. 'mu', 'mug' or
|
||||
* 'procmule'. this influences the name of the e.g. the logfile
|
||||
*
|
||||
* @return TRUE if succeeded, FALSE in case of error
|
||||
*/
|
||||
gboolean mu_runtime_init (const char *muhome, const char *name);
|
||||
|
||||
|
||||
/**
|
||||
* initialize the mu runtime system with comand line argument; this
|
||||
* will parse the command line assuming the parameters of the 'mu'
|
||||
* program. Initializes logging and other systems. To uninitialize,
|
||||
* use mu_runtime_uninit
|
||||
*
|
||||
* @param ptr to the param count (typically, argc)
|
||||
* @param ptr to the params (typically, argv)
|
||||
* @param name of the main program, ie. 'mu', 'mug' or
|
||||
* 'procmule'. this influences the name of the e.g. the logfile
|
||||
*
|
||||
* @return TRUE if succeeded, FALSE in case of error
|
||||
*/
|
||||
gboolean mu_runtime_init_from_cmdline (int *pargc, char ***pargv,
|
||||
const char *name);
|
||||
|
||||
|
||||
/**
|
||||
* free all resources
|
||||
*
|
||||
*/
|
||||
void mu_runtime_uninit (void);
|
||||
|
||||
|
||||
enum _MuRuntimePath {
|
||||
MU_RUNTIME_PATH_MUHOME, /* mu home path */
|
||||
MU_RUNTIME_PATH_XAPIANDB, /* mu xapian db path */
|
||||
MU_RUNTIME_PATH_BOOKMARKS, /* mu bookmarks file path */
|
||||
MU_RUNTIME_PATH_CACHE, /* mu cache path */
|
||||
MU_RUNTIME_PATH_LOG, /* mu path for log files */
|
||||
MU_RUNTIME_PATH_CONTACTS, /* mu path to the contacts cache */
|
||||
|
||||
MU_RUNTIME_PATH_NUM
|
||||
};
|
||||
typedef enum _MuRuntimePath MuRuntimePath;
|
||||
|
||||
/**
|
||||
* get a file system path to some 'special' file or directory
|
||||
*
|
||||
* @return ma string which should be not be modified/freed, or NULL in
|
||||
* case of error.
|
||||
*/
|
||||
const char* mu_runtime_path (MuRuntimePath path);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_RUNTIME_H__*/
|
||||
228
lib/mu-store-priv.hh
Normal file
228
lib/mu-store-priv.hh
Normal file
@ -0,0 +1,228 @@
|
||||
/* -*-mode: c++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8-*- */
|
||||
/*
|
||||
** Copyright (C) 2011 <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_STORE_PRIV_HH__
|
||||
#define __MU_STORE_PRIV_HH__
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <xapian.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "mu-store.h"
|
||||
#include "mu-contacts.h"
|
||||
|
||||
|
||||
class MuStoreError {
|
||||
public:
|
||||
MuStoreError (MuError err, const std::string& what) :
|
||||
_err (err), _what(what) {}
|
||||
MuError mu_error () const { return _err; }
|
||||
const std::string& what() const { return _what; }
|
||||
private:
|
||||
MuError _err;
|
||||
const std::string _what;
|
||||
};
|
||||
|
||||
|
||||
struct _MuStore {
|
||||
public:
|
||||
/* create a read-write MuStore */
|
||||
_MuStore (const char *path, const char *contacts_path, bool rebuild) {
|
||||
|
||||
init (path, contacts_path, rebuild, false);
|
||||
|
||||
if (rebuild)
|
||||
_db = new Xapian::WritableDatabase
|
||||
(path, Xapian::DB_CREATE_OR_OVERWRITE);
|
||||
else
|
||||
_db = new Xapian::WritableDatabase
|
||||
(path, Xapian::DB_CREATE_OR_OPEN);
|
||||
|
||||
check_set_version ();
|
||||
|
||||
if (contacts_path) {
|
||||
_contacts = mu_contacts_new (contacts_path);
|
||||
if (!_contacts) /* don't bail-out for this */
|
||||
throw MuStoreError (MU_ERROR_FILE,
|
||||
("failed to init contacts cache"));
|
||||
}
|
||||
|
||||
MU_WRITE_LOG ("%s: opened %s (batch size: %u) for read-write",
|
||||
__FUNCTION__, this->path(), (unsigned)batch_size());
|
||||
}
|
||||
|
||||
/* create a read-only MuStore */
|
||||
_MuStore (const char *path) {
|
||||
|
||||
init (path, NULL, false, false);
|
||||
|
||||
_db = new Xapian::Database (path);
|
||||
if (mu_store_needs_upgrade(this))
|
||||
throw MuStoreError (MU_ERROR_XAPIAN_NOT_UP_TO_DATE,
|
||||
("store needs an upgrade"));
|
||||
|
||||
MU_WRITE_LOG ("%s: opened %s read-only", __FUNCTION__, this->path());
|
||||
}
|
||||
|
||||
void init (const char *path, const char *contacts_path,
|
||||
bool rebuild, bool read_only) {
|
||||
|
||||
_batch_size = DEFAULT_BATCH_SIZE;
|
||||
_contacts = 0;
|
||||
_in_transaction = false;
|
||||
_path = path;
|
||||
_processed = 0;
|
||||
_read_only = read_only;
|
||||
_ref_count = 1;
|
||||
_version = NULL;
|
||||
}
|
||||
|
||||
void check_set_version () {
|
||||
/* check version...*/
|
||||
gchar *version;
|
||||
version = mu_store_get_metadata (this, MU_STORE_VERSION_KEY, NULL);
|
||||
if (!version)
|
||||
mu_store_set_metadata (this, MU_STORE_VERSION_KEY,
|
||||
MU_STORE_SCHEMA_VERSION, NULL);
|
||||
else if (g_strcmp0 (version, MU_STORE_SCHEMA_VERSION) != 0) {
|
||||
g_free (version);
|
||||
throw MuStoreError (MU_ERROR_XAPIAN_NOT_UP_TO_DATE,
|
||||
("store needs an upgrade"));
|
||||
} else
|
||||
g_free (version);
|
||||
}
|
||||
|
||||
~_MuStore () {
|
||||
try {
|
||||
if (_ref_count != 0)
|
||||
g_warning ("ref count != 0");
|
||||
|
||||
g_free (_version);
|
||||
|
||||
mu_contacts_destroy (_contacts);
|
||||
if (!_read_only)
|
||||
mu_store_flush (this);
|
||||
|
||||
MU_WRITE_LOG ("closing xapian database with %d document(s)",
|
||||
(int)db_read_only()->get_doccount());
|
||||
delete _db;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
/* close the old database, and write an empty one on top of it */
|
||||
void clear () {
|
||||
if (is_read_only())
|
||||
throw std::runtime_error ("database is read-only");
|
||||
|
||||
// clear the database
|
||||
db_writable()->close ();
|
||||
delete _db;
|
||||
_db = new Xapian::WritableDatabase
|
||||
(path(), Xapian::DB_CREATE_OR_OVERWRITE);
|
||||
|
||||
// clear the contacts cache
|
||||
if (_contacts)
|
||||
mu_contacts_clear (_contacts);
|
||||
}
|
||||
|
||||
/* get a unique id for this message; note, this function returns a
|
||||
* static buffer -- not reentrant */
|
||||
const char *get_uid_term (const char *path);
|
||||
|
||||
MuContacts* contacts() { return _contacts; }
|
||||
|
||||
const char* version () {
|
||||
g_free (_version);
|
||||
return _version = mu_store_get_metadata (this, MU_STORE_VERSION_KEY,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void set_version (const char *version) {
|
||||
mu_store_set_metadata (this, MU_STORE_VERSION_KEY, version, NULL);
|
||||
}
|
||||
|
||||
static unsigned max_term_length() { return MAX_TERM_LENGTH; }
|
||||
|
||||
void begin_transaction ();
|
||||
void commit_transaction ();
|
||||
void rollback_transaction ();
|
||||
|
||||
Xapian::WritableDatabase* db_writable() {
|
||||
if (G_UNLIKELY(is_read_only()))
|
||||
throw std::runtime_error ("database is read-only");
|
||||
return (Xapian::WritableDatabase*)_db;
|
||||
}
|
||||
|
||||
Xapian::Database* db_read_only() const { return _db; }
|
||||
|
||||
const char* path () const { return _path.c_str(); }
|
||||
bool is_read_only () const { return _read_only; }
|
||||
|
||||
size_t batch_size () const { return _batch_size;}
|
||||
size_t set_batch_size (size_t n) {
|
||||
return _batch_size = ( n == 0) ? DEFAULT_BATCH_SIZE : n;
|
||||
}
|
||||
|
||||
bool in_transaction () const { return _in_transaction; }
|
||||
bool in_transaction (bool in_tx) { return _in_transaction = in_tx; }
|
||||
|
||||
int processed () const { return _processed; }
|
||||
int set_processed (int n) { return _processed = n;}
|
||||
int inc_processed () { return ++_processed; }
|
||||
|
||||
/* MuStore is ref-counted */
|
||||
guint ref () { return ++_ref_count; }
|
||||
guint unref () {
|
||||
if (_ref_count < 1)
|
||||
g_critical ("ref count error");
|
||||
return --_ref_count;
|
||||
}
|
||||
|
||||
/* by default, use transactions of 30000 messages */
|
||||
static const unsigned DEFAULT_BATCH_SIZE = 30000;
|
||||
/* http://article.gmane.org/gmane.comp.search.xapian.general/3656 */
|
||||
static const unsigned MAX_TERM_LENGTH = 240;
|
||||
|
||||
private:
|
||||
/* transaction handling */
|
||||
bool _in_transaction;
|
||||
int _processed;
|
||||
size_t _batch_size; /* batch size of a xapian transaction */
|
||||
|
||||
/* contacts object to cache all the contact information */
|
||||
MuContacts *_contacts;
|
||||
|
||||
std::string _path;
|
||||
gchar *_version;
|
||||
|
||||
Xapian::Database *_db;
|
||||
bool _read_only;
|
||||
|
||||
guint _ref_count;
|
||||
};
|
||||
|
||||
|
||||
#endif /*__MU_STORE_PRIV_HH__*/
|
||||
268
lib/mu-store-read.cc
Normal file
268
lib/mu-store-read.cc
Normal file
@ -0,0 +1,268 @@
|
||||
/* -*-mode: c++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8-*- */
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <xapian.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "mu-store.h"
|
||||
#include "mu-store-priv.hh" /* _MuStore */
|
||||
|
||||
#include "mu-msg.h"
|
||||
#include "mu-msg-part.h"
|
||||
#include "mu-store.h"
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-date.h"
|
||||
#include "mu-flags.h"
|
||||
#include "mu-contacts.h"
|
||||
|
||||
|
||||
// note: not re-entrant
|
||||
const char*
|
||||
_MuStore::get_uid_term (const char* path)
|
||||
{
|
||||
// combination of DJB, BKDR hash functions to get a 64 bit
|
||||
// value
|
||||
|
||||
unsigned djbhash, bkdrhash, bkdrseed;
|
||||
unsigned u;
|
||||
static char hex[18];
|
||||
static const char uid_prefix =
|
||||
mu_msg_field_xapian_prefix(MU_MSG_FIELD_ID_UID);
|
||||
|
||||
djbhash = 5381;
|
||||
bkdrhash = 0;
|
||||
bkdrseed = 1313;
|
||||
|
||||
for(u = 0; path[u]; ++u) {
|
||||
djbhash = ((djbhash << 5) + djbhash) + path[u];
|
||||
bkdrhash = bkdrhash * bkdrseed + path[u];
|
||||
}
|
||||
|
||||
snprintf (hex, sizeof(hex), "%c%08x%08x",
|
||||
uid_prefix, djbhash, bkdrhash);
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
|
||||
MuStore*
|
||||
mu_store_new_read_only (const char* xpath, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (xpath, NULL);
|
||||
|
||||
try {
|
||||
return new _MuStore (xpath);
|
||||
|
||||
} catch (const MuStoreError& merr) {
|
||||
mu_util_g_set_error (err, merr.mu_error(), "%s",
|
||||
merr.what().c_str());
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR(err, MU_ERROR_XAPIAN);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_is_read_only (MuStore *store)
|
||||
{
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
|
||||
try {
|
||||
return store->is_read_only() ? TRUE : FALSE;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(FALSE);
|
||||
|
||||
}
|
||||
|
||||
|
||||
unsigned
|
||||
mu_store_count (MuStore *store, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, (unsigned)-1);
|
||||
|
||||
try {
|
||||
return store->db_read_only()->get_doccount();
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN,
|
||||
(unsigned)-1);
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
mu_store_version (MuStore *store)
|
||||
{
|
||||
g_return_val_if_fail (store, NULL);
|
||||
return store->version ();
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_needs_upgrade (MuStore *store)
|
||||
{
|
||||
g_return_val_if_fail (store, TRUE);
|
||||
|
||||
return (g_strcmp0 (mu_store_version (store),
|
||||
MU_STORE_SCHEMA_VERSION) == 0) ? FALSE : TRUE;
|
||||
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_store_get_metadata (MuStore *store, const char *key, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, NULL);
|
||||
g_return_val_if_fail (key, NULL);
|
||||
|
||||
try {
|
||||
const std::string val (store->db_read_only()->get_metadata (key));
|
||||
return val.empty() ? NULL : g_strdup (val.c_str());
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN, NULL);
|
||||
}
|
||||
|
||||
|
||||
XapianDatabase*
|
||||
mu_store_get_read_only_database (MuStore *store)
|
||||
{
|
||||
g_return_val_if_fail (store, NULL);
|
||||
return (XapianWritableDatabase*)store->db_read_only();
|
||||
}
|
||||
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_contains_message (MuStore *store, const char* path, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
try {
|
||||
const std::string term (store->get_uid_term(path));
|
||||
return store->db_read_only()->term_exists (term) ? TRUE: FALSE;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN, FALSE);
|
||||
|
||||
}
|
||||
|
||||
|
||||
unsigned
|
||||
mu_store_get_docid_for_path (MuStore *store, const char* path, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
try {
|
||||
const std::string term (store->get_uid_term(path));
|
||||
Xapian::Query query (term);
|
||||
Xapian::Enquire enq (*store->db_read_only());
|
||||
|
||||
enq.set_query (query);
|
||||
|
||||
Xapian::MSet mset (enq.get_mset (0,1));
|
||||
if (mset.empty())
|
||||
throw MuStoreError (MU_ERROR_NO_MATCHES,
|
||||
"message not found");
|
||||
|
||||
return *mset.begin();
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN,
|
||||
MU_STORE_INVALID_DOCID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
time_t
|
||||
mu_store_get_timestamp (MuStore *store, const char* msgpath, GError **err)
|
||||
{
|
||||
char *stampstr;
|
||||
time_t rv;
|
||||
|
||||
g_return_val_if_fail (store, 0);
|
||||
g_return_val_if_fail (msgpath, 0);
|
||||
|
||||
stampstr = mu_store_get_metadata (store, msgpath, err);
|
||||
if (!stampstr)
|
||||
return (time_t)0;
|
||||
|
||||
rv = (time_t) g_ascii_strtoull (stampstr, NULL, 10);
|
||||
g_free (stampstr);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
|
||||
MuError
|
||||
mu_store_foreach (MuStore *self,
|
||||
MuStoreForeachFunc func, void *user_data, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (self, MU_ERROR);
|
||||
g_return_val_if_fail (func, MU_ERROR);
|
||||
|
||||
try {
|
||||
Xapian::Enquire enq (*self->db_read_only());
|
||||
|
||||
enq.set_query (Xapian::Query::MatchAll);
|
||||
enq.set_cutoff (0,0);
|
||||
|
||||
Xapian::MSet matches
|
||||
(enq.get_mset (0, self->db_read_only()->get_doccount()));
|
||||
if (matches.empty())
|
||||
return MU_OK; /* database is empty */
|
||||
|
||||
for (Xapian::MSet::iterator iter = matches.begin();
|
||||
iter != matches.end(); ++iter) {
|
||||
Xapian::Document doc (iter.get_document());
|
||||
const std::string path(doc.get_value(MU_MSG_FIELD_ID_PATH));
|
||||
MuError res = func (path.c_str(), user_data);
|
||||
if (res != MU_OK)
|
||||
return res;
|
||||
}
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN,
|
||||
MU_ERROR_XAPIAN);
|
||||
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
MuMsg*
|
||||
mu_store_get_msg (MuStore *self, unsigned docid, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (self, NULL);
|
||||
g_return_val_if_fail (docid != 0, NULL);
|
||||
|
||||
try {
|
||||
Xapian::Document *doc =
|
||||
new Xapian::Document
|
||||
(self->db_read_only()->get_document (docid));
|
||||
return mu_msg_new_from_doc ((XapianDocument*)doc, err);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN (err, MU_ERROR_XAPIAN, 0);
|
||||
}
|
||||
794
lib/mu-store-write.cc
Normal file
794
lib/mu-store-write.cc
Normal file
@ -0,0 +1,794 @@
|
||||
/* -*-mode: c++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8-*- */
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <xapian.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "mu-store.h"
|
||||
#include "mu-store-priv.hh" /* _MuStore */
|
||||
|
||||
#include "mu-msg.h"
|
||||
#include "mu-msg-part.h"
|
||||
#include "mu-store.h"
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-date.h"
|
||||
#include "mu-flags.h"
|
||||
#include "mu-contacts.h"
|
||||
|
||||
void
|
||||
_MuStore::begin_transaction ()
|
||||
{
|
||||
try {
|
||||
db_writable()->begin_transaction();
|
||||
in_transaction (true);
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
_MuStore::commit_transaction () {
|
||||
try {
|
||||
in_transaction (false);
|
||||
db_writable()->commit_transaction();
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
void
|
||||
_MuStore::rollback_transaction () {
|
||||
try {
|
||||
in_transaction (false);
|
||||
db_writable()->cancel_transaction();
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
|
||||
/* we cache these prefix strings, so we don't have to allocate them all
|
||||
* the time; this should save 10-20 string allocs per message */
|
||||
G_GNUC_CONST static const std::string&
|
||||
prefix (MuMsgFieldId mfid)
|
||||
{
|
||||
static std::string fields[MU_MSG_FIELD_ID_NUM];
|
||||
static bool initialized = false;
|
||||
|
||||
if (G_UNLIKELY(!initialized)) {
|
||||
for (int i = 0; i != MU_MSG_FIELD_ID_NUM; ++i)
|
||||
fields[i] = std::string (1, mu_msg_field_xapian_prefix
|
||||
((MuMsgFieldId)i));
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return fields[mfid];
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_synonym_for_flag (MuFlags flag, Xapian::WritableDatabase *db)
|
||||
{
|
||||
static const std::string pfx(prefix(MU_MSG_FIELD_ID_FLAGS));
|
||||
|
||||
db->clear_synonyms (pfx + mu_flag_name (flag));
|
||||
db->add_synonym (pfx + mu_flag_name (flag), pfx +
|
||||
(std::string(1, (char)(tolower(mu_flag_char(flag))))));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_synonym_for_prio (MuMsgPrio prio, Xapian::WritableDatabase *db)
|
||||
{
|
||||
static const std::string pfx (prefix(MU_MSG_FIELD_ID_PRIO));
|
||||
|
||||
std::string s1 (pfx + mu_msg_prio_name (prio));
|
||||
std::string s2 (pfx + (std::string(1, mu_msg_prio_char (prio))));
|
||||
|
||||
db->clear_synonyms (s1);
|
||||
db->clear_synonyms (s2);
|
||||
|
||||
db->add_synonym (s1, s2);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_synonyms (MuStore *store)
|
||||
{
|
||||
mu_flags_foreach ((MuFlagsForeachFunc)add_synonym_for_flag,
|
||||
store->db_writable());
|
||||
mu_msg_prio_foreach ((MuMsgPrioForeachFunc)add_synonym_for_prio,
|
||||
store->db_writable());
|
||||
}
|
||||
|
||||
|
||||
MuStore*
|
||||
mu_store_new_writable (const char* xpath, const char *contacts_cache,
|
||||
gboolean rebuild, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (xpath, NULL);
|
||||
|
||||
try {
|
||||
try {
|
||||
MuStore *store;
|
||||
store = new _MuStore (xpath, contacts_cache,
|
||||
rebuild ? true : false);
|
||||
add_synonyms (store);
|
||||
return store;
|
||||
|
||||
} MU_STORE_CATCH_BLOCK_RETURN(err,NULL);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN (err, MU_ERROR_XAPIAN, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
mu_store_set_batch_size (MuStore *store, guint batchsize)
|
||||
{
|
||||
g_return_if_fail (store);
|
||||
store->set_batch_size (batchsize);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_set_metadata (MuStore *store, const char *key, const char *val,
|
||||
GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
g_return_val_if_fail (key, FALSE);
|
||||
g_return_val_if_fail (val, FALSE);
|
||||
|
||||
try {
|
||||
try {
|
||||
store->db_writable()->set_metadata (key, val);
|
||||
return TRUE;
|
||||
|
||||
} MU_STORE_CATCH_BLOCK_RETURN(err, FALSE);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(err, MU_ERROR_XAPIAN, FALSE);
|
||||
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_clear (MuStore *store, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
|
||||
try {
|
||||
try {
|
||||
store->clear();
|
||||
return TRUE;
|
||||
|
||||
} MU_STORE_CATCH_BLOCK_RETURN(err, FALSE);
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mu_store_flush (MuStore *store)
|
||||
{
|
||||
g_return_if_fail (store);
|
||||
|
||||
try {
|
||||
if (store->in_transaction())
|
||||
store->commit_transaction ();
|
||||
store->db_writable()->flush (); /* => commit, post X 1.1.x */
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_terms_values_date (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid)
|
||||
{
|
||||
time_t t;
|
||||
const char *datestr;
|
||||
|
||||
t = (time_t)mu_msg_get_field_numeric (msg, mfid);
|
||||
if (t != 0) {
|
||||
datestr = mu_date_time_t_to_str_s (t, FALSE /*UTC*/);
|
||||
doc.add_value ((Xapian::valueno)mfid, datestr);
|
||||
}
|
||||
}
|
||||
|
||||
/* pre-calculate; optimization */
|
||||
G_GNUC_CONST static const std::string&
|
||||
flag_val (char flagchar)
|
||||
{
|
||||
static const std::string
|
||||
pfx (prefix(MU_MSG_FIELD_ID_FLAGS)),
|
||||
draftstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_DRAFT))),
|
||||
flaggedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_FLAGGED))),
|
||||
passedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_PASSED))),
|
||||
repliedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_REPLIED))),
|
||||
seenstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_SEEN))),
|
||||
trashedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_TRASHED))),
|
||||
newstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_NEW))),
|
||||
|
||||
signedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_SIGNED))),
|
||||
encryptedstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_ENCRYPTED))),
|
||||
has_attachstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_HAS_ATTACH))),
|
||||
unreadstr (pfx + (char)tolower(mu_flag_char(MU_FLAG_UNREAD)));
|
||||
|
||||
switch (flagchar) {
|
||||
|
||||
case 'D': return draftstr;
|
||||
case 'F': return flaggedstr;
|
||||
case 'P': return passedstr;
|
||||
case 'R': return repliedstr;
|
||||
case 'S': return seenstr;
|
||||
case 'T': return trashedstr;
|
||||
|
||||
case 'N': return newstr;
|
||||
|
||||
case 'z': return signedstr;
|
||||
case 'x': return encryptedstr;
|
||||
case 'a': return has_attachstr;
|
||||
|
||||
case 'u': return unreadstr;
|
||||
|
||||
default:
|
||||
g_return_val_if_reached (flaggedstr);
|
||||
return flaggedstr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* pre-calculate; optimization */
|
||||
G_GNUC_CONST static const std::string&
|
||||
prio_val (MuMsgPrio prio)
|
||||
{
|
||||
static const std::string pfx (prefix(MU_MSG_FIELD_ID_PRIO));
|
||||
|
||||
static const std::string
|
||||
lowstr (pfx + std::string(1, mu_msg_prio_char(MU_MSG_PRIO_LOW))),
|
||||
normalstr (pfx + std::string(1, mu_msg_prio_char(MU_MSG_PRIO_NORMAL))),
|
||||
highstr (pfx + std::string(1, mu_msg_prio_char(MU_MSG_PRIO_HIGH)));
|
||||
|
||||
switch (prio) {
|
||||
case MU_MSG_PRIO_LOW: return lowstr;
|
||||
case MU_MSG_PRIO_NORMAL: return normalstr;
|
||||
case MU_MSG_PRIO_HIGH: return highstr;
|
||||
default:
|
||||
g_return_val_if_reached (normalstr);
|
||||
return normalstr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
add_terms_values_number (Xapian::Document& doc, MuMsg *msg, MuMsgFieldId mfid)
|
||||
{
|
||||
gint64 num = mu_msg_get_field_numeric (msg, mfid);
|
||||
|
||||
const std::string numstr (Xapian::sortable_serialise((double)num));
|
||||
doc.add_value ((Xapian::valueno)mfid, numstr);
|
||||
|
||||
if (mfid == MU_MSG_FIELD_ID_FLAGS) {
|
||||
const char *cur = mu_flags_to_str_s
|
||||
((MuFlags)num,(MuFlagType)MU_FLAG_TYPE_ANY);
|
||||
g_return_if_fail (cur);
|
||||
while (*cur) {
|
||||
doc.add_term (flag_val(*cur));
|
||||
++cur;
|
||||
}
|
||||
|
||||
} else if (mfid == MU_MSG_FIELD_ID_PRIO)
|
||||
doc.add_term (prio_val((MuMsgPrio)num));
|
||||
}
|
||||
|
||||
|
||||
/* for string and string-list */
|
||||
static void
|
||||
add_terms_values_str (Xapian::Document& doc, char *val,
|
||||
MuMsgFieldId mfid)
|
||||
{
|
||||
/* the value is what we display in search results; the
|
||||
* unchanged original */
|
||||
if (mu_msg_field_xapian_value(mfid))
|
||||
doc.add_value ((Xapian::valueno)mfid, val);
|
||||
|
||||
/* now, let's create some search terms... */
|
||||
if (mu_msg_field_normalize (mfid))
|
||||
mu_str_normalize_in_place (val, TRUE);
|
||||
|
||||
if (mu_msg_field_xapian_index (mfid)) {
|
||||
Xapian::TermGenerator termgen;
|
||||
termgen.set_document (doc);
|
||||
termgen.index_text_without_positions (val, 1, prefix(mfid));
|
||||
}
|
||||
if (mu_msg_field_xapian_escape (mfid))
|
||||
mu_str_xapian_escape_in_place (val,
|
||||
TRUE /*esc_space*/);
|
||||
if (mu_msg_field_xapian_term(mfid))
|
||||
doc.add_term (prefix(mfid) +
|
||||
std::string(val, 0, _MuStore::MAX_TERM_LENGTH));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_terms_values_string (Xapian::Document& doc, MuMsg *msg,
|
||||
MuMsgFieldId mfid)
|
||||
{
|
||||
const char *orig;
|
||||
char *val;
|
||||
size_t len;
|
||||
|
||||
if (!(orig = mu_msg_get_field_string (msg, mfid)))
|
||||
return; /* nothing to do */
|
||||
|
||||
/* try stack-allocation, it's much faster*/
|
||||
len = strlen (orig);
|
||||
val = (char*)(G_LIKELY(len < 1024)?g_alloca(len+1):g_malloc(len+1));
|
||||
strcpy (val, orig);
|
||||
|
||||
add_terms_values_str (doc, val, mfid);
|
||||
|
||||
if (!(G_LIKELY(len < 1024)))
|
||||
g_free (val);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
add_terms_values_string_list (Xapian::Document& doc, MuMsg *msg,
|
||||
MuMsgFieldId mfid)
|
||||
{
|
||||
const GSList *lst;
|
||||
|
||||
lst = mu_msg_get_field_string_list (msg, mfid);
|
||||
|
||||
if (lst && mu_msg_field_xapian_value (mfid)) {
|
||||
gchar *str;
|
||||
str = mu_str_from_list (lst, ',');
|
||||
if (str)
|
||||
doc.add_value ((Xapian::valueno)mfid, str);
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
if (lst && mu_msg_field_xapian_term (mfid)) {
|
||||
while (lst) {
|
||||
size_t len;
|
||||
char *val;
|
||||
/* try stack-allocation, it's much faster*/
|
||||
len = strlen ((char*)lst->data);
|
||||
if (G_LIKELY(len < 1024))
|
||||
val = (char*)g_alloca(len+1);
|
||||
else
|
||||
val = (char*)g_malloc(len+1);
|
||||
strcpy (val, (char*)lst->data);
|
||||
|
||||
add_terms_values_str (doc, val, mfid);
|
||||
|
||||
if (!(G_LIKELY(len < 1024)))
|
||||
g_free (val);
|
||||
|
||||
lst = g_slist_next ((GSList*)lst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PartData {
|
||||
PartData (Xapian::Document& doc, MuMsgFieldId mfid):
|
||||
_doc (doc), _mfid(mfid) {}
|
||||
Xapian::Document _doc;
|
||||
MuMsgFieldId _mfid;
|
||||
};
|
||||
|
||||
static gboolean
|
||||
is_textual (MuMsgPart *part)
|
||||
{
|
||||
/* all text parts except text/html */
|
||||
if ((strcasecmp (part->type, "text") == 0) &&
|
||||
(strcasecmp (part->subtype, "html") != 0))
|
||||
return TRUE;
|
||||
|
||||
/* message/rfc822 */
|
||||
if ((strcasecmp (part->type, "message") == 0) &&
|
||||
(strcasecmp (part->subtype, "rfc822") == 0))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
index_text_part (MuMsgPart *part, PartData *pdata)
|
||||
{
|
||||
gboolean err;
|
||||
char *txt, *norm;
|
||||
Xapian::TermGenerator termgen;
|
||||
|
||||
if (!is_textual (part))
|
||||
return FALSE;
|
||||
|
||||
txt = mu_msg_part_get_text (part, &err);
|
||||
if (!txt || err)
|
||||
return FALSE;
|
||||
|
||||
termgen.set_document(pdata->_doc);
|
||||
|
||||
norm = mu_str_normalize (txt, TRUE);
|
||||
|
||||
termgen.index_text_without_positions
|
||||
(norm, 1, prefix(MU_MSG_FIELD_ID_EMBEDDED_TEXT));
|
||||
|
||||
g_free (norm);
|
||||
g_free (txt);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
each_part (MuMsg *msg, MuMsgPart *part, PartData *pdata)
|
||||
{
|
||||
static const std::string
|
||||
file (prefix(MU_MSG_FIELD_ID_FILE)),
|
||||
mime (prefix(MU_MSG_FIELD_ID_MIME));
|
||||
|
||||
/* save the mime type of any part */
|
||||
if (part->type) {
|
||||
/* note, we use '_' instead of '/' to separate
|
||||
* type/subtype -- Xapian doesn't treat '/' as
|
||||
* desired, so we use '_' and pre-process queries; see
|
||||
* mu_query_preprocess */
|
||||
char ctype[MuStore::MAX_TERM_LENGTH + 1];
|
||||
snprintf (ctype, sizeof(ctype), "%s_%s",
|
||||
part->type, part->subtype);
|
||||
// g_print ("store: %s\n", ctype);
|
||||
|
||||
pdata->_doc.add_term
|
||||
(mime + std::string(ctype, 0, MuStore::MAX_TERM_LENGTH));
|
||||
}
|
||||
|
||||
/* save the name of anything that has a filename */
|
||||
if (part->file_name) {
|
||||
char val[MuStore::MAX_TERM_LENGTH + 1];
|
||||
strncpy (val, part->file_name, sizeof(val));
|
||||
|
||||
/* now, let's create a term... */
|
||||
mu_str_normalize_in_place (val, TRUE);
|
||||
mu_str_xapian_escape_in_place (val, TRUE /*esc space*/);
|
||||
|
||||
pdata->_doc.add_term
|
||||
(file + std::string(val, 0, MuStore::MAX_TERM_LENGTH));
|
||||
}
|
||||
|
||||
/* now, for non-body parts with some MIME-types, index the
|
||||
* content as well */
|
||||
if (!part->is_body)
|
||||
index_text_part (part, pdata);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_terms_values_attach (Xapian::Document& doc, MuMsg *msg,
|
||||
MuMsgFieldId mfid)
|
||||
{
|
||||
PartData pdata (doc, mfid);
|
||||
mu_msg_part_foreach (msg, TRUE,
|
||||
(MuMsgPartForeachFunc)each_part, &pdata);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_terms_values_body (Xapian::Document& doc, MuMsg *msg,
|
||||
MuMsgFieldId mfid)
|
||||
{
|
||||
const char *str;
|
||||
char *norm;
|
||||
|
||||
if (mu_msg_get_flags(msg) & MU_FLAG_ENCRYPTED)
|
||||
return; /* ignore encrypted bodies */
|
||||
|
||||
str = mu_msg_get_body_text (msg);
|
||||
if (!str) /* FIXME: html->txt fallback needed */
|
||||
str = mu_msg_get_body_html (msg);
|
||||
|
||||
if (!str)
|
||||
return; /* no body... */
|
||||
|
||||
Xapian::TermGenerator termgen;
|
||||
termgen.set_document(doc);
|
||||
|
||||
norm = mu_str_normalize (str, TRUE);
|
||||
termgen.index_text_without_positions (norm, 1, prefix(mfid));
|
||||
|
||||
g_free (norm);
|
||||
}
|
||||
|
||||
struct _MsgDoc {
|
||||
Xapian::Document *_doc;
|
||||
MuMsg *_msg;
|
||||
MuStore *_store;
|
||||
};
|
||||
typedef struct _MsgDoc MsgDoc;
|
||||
|
||||
|
||||
static void
|
||||
add_terms_values_default (MuMsgFieldId mfid, MsgDoc* msgdoc)
|
||||
{
|
||||
if (mu_msg_field_is_numeric (mfid))
|
||||
add_terms_values_number
|
||||
(*msgdoc->_doc, msgdoc->_msg, mfid);
|
||||
else if (mu_msg_field_is_string (mfid))
|
||||
add_terms_values_string
|
||||
(*msgdoc->_doc, msgdoc->_msg, mfid);
|
||||
else if (mu_msg_field_is_string_list(mfid))
|
||||
add_terms_values_string_list
|
||||
(*msgdoc->_doc, msgdoc->_msg, mfid);
|
||||
else
|
||||
g_return_if_reached ();
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
add_terms_values (MuMsgFieldId mfid, MsgDoc* msgdoc)
|
||||
{
|
||||
/* note: contact-stuff (To/Cc/From) will handled in
|
||||
* each_contact_info, not here */
|
||||
if (!mu_msg_field_xapian_index(mfid) &&
|
||||
!mu_msg_field_xapian_term(mfid) &&
|
||||
!mu_msg_field_xapian_value(mfid))
|
||||
return;
|
||||
|
||||
switch (mfid) {
|
||||
case MU_MSG_FIELD_ID_DATE:
|
||||
add_terms_values_date (*msgdoc->_doc, msgdoc->_msg, mfid);
|
||||
break;
|
||||
case MU_MSG_FIELD_ID_BODY_TEXT:
|
||||
add_terms_values_body (*msgdoc->_doc, msgdoc->_msg, mfid);
|
||||
break;
|
||||
|
||||
/* note: add_terms_values_attach handles _FILE, _MIME and
|
||||
* _ATTACH_TEXT msgfields */
|
||||
case MU_MSG_FIELD_ID_FILE:
|
||||
add_terms_values_attach (*msgdoc->_doc, msgdoc->_msg, mfid);
|
||||
break;
|
||||
case MU_MSG_FIELD_ID_MIME:
|
||||
case MU_MSG_FIELD_ID_EMBEDDED_TEXT:
|
||||
break;
|
||||
///////////////////////////////////////////
|
||||
|
||||
case MU_MSG_FIELD_ID_UID:
|
||||
break; /* already taken care of elsewhere */
|
||||
default:
|
||||
return add_terms_values_default (mfid, msgdoc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const std::string&
|
||||
xapian_pfx (MuMsgContact *contact)
|
||||
{
|
||||
static const std::string empty;
|
||||
|
||||
/* use ptr to string to prevent copy... */
|
||||
switch (contact->type) {
|
||||
case MU_MSG_CONTACT_TYPE_TO:
|
||||
return prefix(MU_MSG_FIELD_ID_TO);
|
||||
case MU_MSG_CONTACT_TYPE_FROM:
|
||||
return prefix(MU_MSG_FIELD_ID_FROM);
|
||||
case MU_MSG_CONTACT_TYPE_CC:
|
||||
return prefix(MU_MSG_FIELD_ID_CC);
|
||||
case MU_MSG_CONTACT_TYPE_BCC:
|
||||
return prefix(MU_MSG_FIELD_ID_BCC);
|
||||
default:
|
||||
g_warning ("unsupported contact type %u",
|
||||
(unsigned)contact->type);
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
each_contact_info (MuMsgContact *contact, MsgDoc *msgdoc)
|
||||
{
|
||||
/* for now, don't store reply-to addresses */
|
||||
if (mu_msg_contact_type (contact) == MU_MSG_CONTACT_TYPE_REPLY_TO)
|
||||
return;
|
||||
|
||||
const std::string pfx (xapian_pfx(contact));
|
||||
if (pfx.empty())
|
||||
return; /* unsupported contact type */
|
||||
|
||||
if (!mu_str_is_empty(contact->name)) {
|
||||
Xapian::TermGenerator termgen;
|
||||
termgen.set_document (*msgdoc->_doc);
|
||||
char *norm = mu_str_normalize (contact->name, TRUE);
|
||||
termgen.index_text_without_positions (norm, 1, pfx);
|
||||
g_free (norm);
|
||||
}
|
||||
|
||||
/* don't normalize e-mail address, but do lowercase it */
|
||||
if (!mu_str_is_empty(contact->address)) {
|
||||
|
||||
char *escaped;
|
||||
escaped = mu_str_xapian_escape (contact->address,
|
||||
FALSE /*dont esc space*/);
|
||||
msgdoc->_doc->add_term
|
||||
(std::string (pfx + escaped, 0, MuStore::MAX_TERM_LENGTH));
|
||||
g_free (escaped);
|
||||
|
||||
/* store it also in our contacts cache */
|
||||
if (msgdoc->_store->contacts())
|
||||
mu_contacts_add (msgdoc->_store->contacts(),
|
||||
contact->address, contact->name,
|
||||
mu_msg_get_date(msgdoc->_msg));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Xapian::Document
|
||||
new_doc_from_message (MuStore *store, MuMsg *msg)
|
||||
{
|
||||
Xapian::Document doc;
|
||||
MsgDoc docinfo = {&doc, msg, store};
|
||||
|
||||
mu_msg_field_foreach ((MuMsgFieldForeachFunc)add_terms_values, &docinfo);
|
||||
/* also store the contact-info as separate terms */
|
||||
mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact_info,
|
||||
&docinfo);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
unsigned
|
||||
mu_store_add_msg (MuStore *store, MuMsg *msg, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, MU_STORE_INVALID_DOCID);
|
||||
g_return_val_if_fail (msg, MU_STORE_INVALID_DOCID);
|
||||
|
||||
try {
|
||||
Xapian::docid id;
|
||||
Xapian::Document doc (new_doc_from_message(store, msg));
|
||||
const std::string term (store->get_uid_term
|
||||
(mu_msg_get_path(msg)));
|
||||
|
||||
if (!store->in_transaction())
|
||||
store->begin_transaction();
|
||||
|
||||
doc.add_term (term);
|
||||
|
||||
MU_WRITE_LOG ("adding: %s", term.c_str());
|
||||
|
||||
/* note, this will replace any other messages for this path */
|
||||
id = store->db_writable()->replace_document (term, doc);
|
||||
|
||||
if (store->inc_processed() % store->batch_size() == 0)
|
||||
store->commit_transaction();
|
||||
|
||||
return id;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR (err, MU_ERROR_XAPIAN_STORE_FAILED);
|
||||
|
||||
if (store->in_transaction())
|
||||
store->rollback_transaction();
|
||||
|
||||
return MU_STORE_INVALID_DOCID;
|
||||
}
|
||||
|
||||
|
||||
unsigned
|
||||
mu_store_update_msg (MuStore *store, unsigned docid, MuMsg *msg, GError **err)
|
||||
{
|
||||
g_return_val_if_fail (store, MU_STORE_INVALID_DOCID);
|
||||
g_return_val_if_fail (msg, MU_STORE_INVALID_DOCID);
|
||||
g_return_val_if_fail (docid != 0, MU_STORE_INVALID_DOCID);
|
||||
|
||||
try {
|
||||
Xapian::Document doc (new_doc_from_message(store, msg));
|
||||
|
||||
if (!store->in_transaction())
|
||||
store->begin_transaction();
|
||||
|
||||
const std::string term
|
||||
(store->get_uid_term(mu_msg_get_path(msg)));
|
||||
doc.add_term (term);
|
||||
|
||||
store->db_writable()->replace_document (docid, doc);
|
||||
|
||||
if (store->inc_processed() % store->batch_size() == 0)
|
||||
store->commit_transaction();
|
||||
|
||||
return docid;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_G_ERROR (err, MU_ERROR_XAPIAN_STORE_FAILED);
|
||||
|
||||
if (store->in_transaction())
|
||||
store->rollback_transaction();
|
||||
|
||||
return MU_STORE_INVALID_DOCID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned
|
||||
mu_store_add_path (MuStore *store, const char *path, const char *maildir,
|
||||
GError **err)
|
||||
{
|
||||
MuMsg *msg;
|
||||
unsigned docid;
|
||||
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
msg = mu_msg_new_from_file (path, maildir, err);
|
||||
if (!msg)
|
||||
return MU_STORE_INVALID_DOCID;
|
||||
|
||||
docid = mu_store_add_msg (store, msg, err);
|
||||
mu_msg_unref (msg);
|
||||
|
||||
return docid;
|
||||
}
|
||||
|
||||
|
||||
XapianWritableDatabase*
|
||||
mu_store_get_writable_database (MuStore *store)
|
||||
{
|
||||
g_return_val_if_fail (store, NULL);
|
||||
|
||||
return (XapianWritableDatabase*)store->db_writable();
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_remove_path (MuStore *store, const char *msgpath)
|
||||
{
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
g_return_val_if_fail (msgpath, FALSE);
|
||||
|
||||
try {
|
||||
const std::string term
|
||||
(store->get_uid_term(msgpath));
|
||||
|
||||
store->db_writable()->delete_document (term);
|
||||
store->inc_processed();
|
||||
|
||||
return TRUE;
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK_RETURN (FALSE);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_set_timestamp (MuStore *store, const char* msgpath,
|
||||
time_t stamp, GError **err)
|
||||
{
|
||||
char buf[21];
|
||||
|
||||
g_return_val_if_fail (store, FALSE);
|
||||
g_return_val_if_fail (msgpath, FALSE);
|
||||
|
||||
sprintf (buf, "%" G_GUINT64_FORMAT, (guint64)stamp);
|
||||
return mu_store_set_metadata (store, msgpath, buf, err);
|
||||
}
|
||||
119
lib/mu-store.cc
Normal file
119
lib/mu-store.cc
Normal file
@ -0,0 +1,119 @@
|
||||
/* -*-mode: c++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8-*- */
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <xapian.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "mu-store.h"
|
||||
#include "mu-store-priv.hh" /* _MuStore */
|
||||
|
||||
|
||||
#include "mu-msg.h"
|
||||
#include "mu-msg-part.h"
|
||||
#include "mu-store.h"
|
||||
#include "mu-util.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-date.h"
|
||||
#include "mu-flags.h"
|
||||
#include "mu-contacts.h"
|
||||
|
||||
|
||||
|
||||
MuStore*
|
||||
mu_store_ref (MuStore *store)
|
||||
{
|
||||
g_return_val_if_fail (store, NULL);
|
||||
store->ref();
|
||||
return store;
|
||||
}
|
||||
|
||||
MuStore*
|
||||
mu_store_unref (MuStore *store)
|
||||
{
|
||||
g_return_val_if_fail (store, NULL);
|
||||
|
||||
|
||||
if (store->unref() == 0) {
|
||||
try { delete store; } MU_XAPIAN_CATCH_BLOCK;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static char*
|
||||
xapian_get_metadata (const gchar *xpath, const gchar *key)
|
||||
{
|
||||
g_return_val_if_fail (xpath, NULL);
|
||||
g_return_val_if_fail (key, NULL);
|
||||
|
||||
if (!access(xpath, F_OK) == 0) {
|
||||
g_warning ("cannot access %s: %s", xpath, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
Xapian::Database db (xpath);
|
||||
const std::string val(db.get_metadata (key));
|
||||
return val.empty() ? NULL : g_strdup (val.c_str());
|
||||
|
||||
} MU_XAPIAN_CATCH_BLOCK;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char*
|
||||
mu_store_database_version (const gchar *xpath)
|
||||
{
|
||||
g_return_val_if_fail (xpath, NULL);
|
||||
|
||||
return xapian_get_metadata (xpath, MU_STORE_VERSION_KEY);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
gboolean
|
||||
mu_store_database_is_locked (const gchar *xpath)
|
||||
{
|
||||
g_return_val_if_fail (xpath, FALSE);
|
||||
|
||||
try {
|
||||
Xapian::WritableDatabase db (xpath, Xapian::DB_OPEN);
|
||||
} catch (const Xapian::DatabaseLockError& xer) {
|
||||
return TRUE;
|
||||
} catch (const Xapian::Error &xer) {
|
||||
g_warning ("%s: error: %s", __FUNCTION__,
|
||||
xer.get_msg().c_str());
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
393
lib/mu-store.h
Normal file
393
lib/mu-store.h
Normal file
@ -0,0 +1,393 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_STORE_H__
|
||||
#define __MU_STORE_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <inttypes.h>
|
||||
#include <mu-msg.h>
|
||||
#include <mu-util.h> /* for MuError, MuError */
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _MuStore;
|
||||
typedef struct _MuStore MuStore;
|
||||
|
||||
|
||||
/**
|
||||
* create a new writable Xapian store, a place to store documents
|
||||
*
|
||||
* @param path the path to the database
|
||||
* @param ccachepath path where to cache the contacts information, or NULL
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return a new MuStore object with ref count == 1, or NULL in case
|
||||
* of error; free with mu_store_unref
|
||||
*/
|
||||
MuStore* mu_store_new_writable (const char *xpath, const char *ccachepath,
|
||||
gboolean rebuild, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* create a new read-only Xapian store, for querying documents
|
||||
*
|
||||
* @param path the path to the database
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return a new MuStore object with ref count == 1, or NULL in case
|
||||
* of error; free with mu_store_unref
|
||||
*/
|
||||
MuStore* mu_store_new_read_only (const char* xpath, GError **err)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* increase the reference count for this store with 1
|
||||
*
|
||||
* @param store a valid store object
|
||||
*
|
||||
* @return the same store with increased ref count, or NULL in case of
|
||||
* error
|
||||
*/
|
||||
MuStore* mu_store_ref (MuStore *store);
|
||||
|
||||
/**
|
||||
* decrease the reference count for this store with 1
|
||||
*
|
||||
* @param store a valid store object
|
||||
*
|
||||
* @return NULL
|
||||
*/
|
||||
MuStore* mu_store_unref (MuStore *store);
|
||||
|
||||
|
||||
/**
|
||||
* we need this when using Xapian::WritableDatabase* from C
|
||||
*/
|
||||
typedef gpointer XapianWritableDatabase;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get the underlying writable database object for this store; not
|
||||
* that this pointer becomes in valid after mu_store_destroy
|
||||
*
|
||||
* @param store a valid store
|
||||
*
|
||||
* @return a Xapian::WritableDatabase (you'll need to cast in C++), or
|
||||
* NULL in case of error.
|
||||
*/
|
||||
XapianWritableDatabase* mu_store_get_writable_database (MuStore *store);
|
||||
|
||||
|
||||
/**
|
||||
* we need this when using Xapian::WritableDatabase* from C
|
||||
*/
|
||||
typedef gpointer XapianDatabase;
|
||||
|
||||
|
||||
/**
|
||||
* get the underlying read-only database object for this store; not that this
|
||||
* pointer becomes in valid after mu_store_destroy
|
||||
*
|
||||
* @param store a valid store
|
||||
*
|
||||
* @return a Xapian::Database (you'll need to cast in C++), or
|
||||
* NULL in case of error.
|
||||
*/
|
||||
XapianDatabase* mu_store_get_read_only_database (MuStore *store);
|
||||
|
||||
|
||||
/**
|
||||
* set the Xapian batch size for this store. Normally, there's no need
|
||||
* to use this function as the default is good enough; however, if you
|
||||
* use mu in a very memory-constrained environment, you can set the
|
||||
* batchsize to e.g. 1000 at the cost of significant slow-down.
|
||||
*
|
||||
* @param store a valid store object
|
||||
* @param batchsize the new batch size; or 0 to reset to
|
||||
* the default batch size
|
||||
*/
|
||||
void mu_store_set_batch_size (MuStore *store, guint batchsize);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get the numbers of documents in the database
|
||||
*
|
||||
* @param index a valid MuStore instance
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return the number of documents in the database; (unsigned)-1 in
|
||||
* case of error
|
||||
*/
|
||||
unsigned mu_store_count (MuStore *store, GError **err);
|
||||
|
||||
/**
|
||||
* get a version string for the database; it's a const string, which
|
||||
* is valid as long MuStore exists and mu_store_version is not called
|
||||
* again.
|
||||
*
|
||||
* @param store a valid MuStore
|
||||
*
|
||||
* @return the version string or NULL in case of error
|
||||
*/
|
||||
const char* mu_store_version (MuStore *store);
|
||||
|
||||
|
||||
/**
|
||||
* try to flush/commit all outstanding work
|
||||
*
|
||||
* @param store a valid xapian store
|
||||
*/
|
||||
void mu_store_flush (MuStore *store);
|
||||
|
||||
#define MU_STORE_INVALID_DOCID 0
|
||||
|
||||
/**
|
||||
* store an email message in the XapianStore
|
||||
*
|
||||
* @param store a valid store
|
||||
* @param msg a valid message
|
||||
* @param err receives error information, if any, or NULL
|
||||
*
|
||||
* @return the docid of the stored message, or 0
|
||||
* (MU_STORE_INVALID_DOCID) in case of error
|
||||
*/
|
||||
unsigned mu_store_add_msg (MuStore *store, MuMsg *msg, GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* update an email message in the XapianStore
|
||||
*
|
||||
* @param store a valid store
|
||||
* @param the docid for the message
|
||||
* @param msg a valid message
|
||||
* @param err receives error information, if any, or NULL
|
||||
*
|
||||
* @return the docid of the stored message, or 0
|
||||
* (MU_STORE_INVALID_DOCID) in case of error
|
||||
*/
|
||||
unsigned mu_store_update_msg (MuStore *store, unsigned docid, MuMsg *msg,
|
||||
GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* store an email message in the XapianStore; similar to
|
||||
* mu_store_store, but instead takes a path as parameter instead of a
|
||||
* MuMsg*
|
||||
*
|
||||
* @param store a valid store
|
||||
* @param path full filesystem path to a valid message
|
||||
* @param maildir set the maildir (e.g. "/drafts") for this message, or NULL
|
||||
* note that you cannot mu_msg_move_msg_to_maildir unless maildir is set.
|
||||
* @param err receives error information, if any, or NULL
|
||||
*
|
||||
* @return the docid of the stored message, or 0
|
||||
* (MU_STORE_INVALID_DOCID) in case of error
|
||||
*/
|
||||
unsigned mu_store_add_path (MuStore *store, const char *path,
|
||||
const char* maildir, GError **err);
|
||||
|
||||
/**
|
||||
* remove a message from the database based on its path
|
||||
*
|
||||
* @param store a valid store
|
||||
* @param msgpath path of the message (note, this is only used to
|
||||
* *identify* the message; a common use of this function is to remove
|
||||
* a message from the database, for which there is no message anymore
|
||||
* in the filesystem.
|
||||
*
|
||||
* @return TRUE if it succeeded, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_store_remove_path (MuStore *store, const char* msgpath);
|
||||
|
||||
|
||||
/**
|
||||
* does a certain message exist in the database already?
|
||||
*
|
||||
* @param store a store
|
||||
* @param path the message path
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return TRUE if the message exists, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_store_contains_message (MuStore *store, const char* path,
|
||||
GError **err);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get the docid for message at path
|
||||
*
|
||||
* @param store a store
|
||||
* @param path the message path
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return the docid if the message was found, MU_STORE_INVALID_DOCID (0) otherwise
|
||||
* */
|
||||
unsigned mu_store_get_docid_for_path (MuStore *store, const char* path, GError **err);
|
||||
|
||||
/**
|
||||
* store a timestamp for a directory
|
||||
*
|
||||
* @param store a valid store
|
||||
* @param msgpath path to a maildir
|
||||
* @param stamp a timestamp
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return TRUE if setting the timestamp succeeded, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_store_set_timestamp (MuStore *store, const char* msgpath,
|
||||
time_t stamp, GError **err);
|
||||
|
||||
/**
|
||||
* get the timestamp for a directory
|
||||
*
|
||||
* @param store a valid store
|
||||
* @param msgpath path to a maildir
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return the timestamp, or 0 in case of error
|
||||
*/
|
||||
time_t mu_store_get_timestamp (MuStore *store, const char* msgpath,
|
||||
GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* check whether this store is read-only
|
||||
*
|
||||
* @param store a store
|
||||
*
|
||||
* @return TRUE if the store is read-only, FALSE otherwise (and in
|
||||
* case of error)
|
||||
*/
|
||||
gboolean mu_store_is_read_only (MuStore *store);
|
||||
|
||||
|
||||
/**
|
||||
* call a function for each document in the database
|
||||
*
|
||||
* @param self a valid store
|
||||
* @param func a callback function to to call for each document
|
||||
* @param user_data a user pointer passed to the callback function
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return MU_OK if all went well, MU_STOP if the foreach was interrupted,
|
||||
* MU_ERROR in case of error
|
||||
*/
|
||||
typedef MuError (*MuStoreForeachFunc) (const char* path,
|
||||
void *user_data);
|
||||
MuError mu_store_foreach (MuStore *self, MuStoreForeachFunc func,
|
||||
void *user_data, GError **err);
|
||||
|
||||
/**
|
||||
* set metadata for this MuStore
|
||||
*
|
||||
* @param store a store
|
||||
* @param key metadata key
|
||||
* @param val metadata value
|
||||
* @param err to receive error info or NULL. err->code is the MuError value
|
||||
*
|
||||
* @return TRUE if succeeded, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_store_set_metadata (MuStore *store, const char *key, const char *val,
|
||||
GError **err);
|
||||
|
||||
/**
|
||||
* get metadata for this MuStore
|
||||
*
|
||||
* @param store a store
|
||||
* @param key the metadata key
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return the value of the metadata (gfree when done with it), or
|
||||
* NULL in case of error
|
||||
*/
|
||||
char* mu_store_get_metadata (MuStore *store, const char *key, GError **err)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
" * get the version of the xapian database (ie., the version of the
|
||||
* 'schema' we are using). If this version != MU_STORE_SCHEMA_VERSION,
|
||||
* it's means we need to a full reindex.
|
||||
*
|
||||
* @param xpath path to the xapian database
|
||||
*
|
||||
* @return the version of the database as a newly allocated string
|
||||
* (free with g_free); if there is no version yet, it will return NULL
|
||||
*/
|
||||
gchar* mu_store_database_version (const gchar *xpath) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* check whether the database needs to be upgraded, e.g., when it was
|
||||
* created with a different version of mu
|
||||
*
|
||||
* @param store a MuStore instance
|
||||
*
|
||||
* @return TRUE if the database needs upgrading, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_store_needs_upgrade (MuStore *store);
|
||||
|
||||
/**
|
||||
* clear the database, ie., remove all of the contents. This is a
|
||||
* destructive operation, but the database can be restored be doing a
|
||||
* full scan of the maildirs. Also, clear the contacts cache file
|
||||
*
|
||||
* @param store a MuStore object
|
||||
* @param err to receive error info or NULL. err->code is MuError value
|
||||
*
|
||||
* @return TRUE if the clearing succeeded, FALSE otherwise.
|
||||
*/
|
||||
gboolean mu_store_clear (MuStore *store, GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* check if the database is locked for writing
|
||||
*
|
||||
* @param xpath path to a xapian database
|
||||
*
|
||||
* @return TRUE if it is locked, FALSE otherwise (or in case of error)
|
||||
*/
|
||||
gboolean mu_store_database_is_locked (const gchar *xpath);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* get a specific message, based on its Xapian docid
|
||||
*
|
||||
* @param self a valid MuQuery instance
|
||||
* @param docid the Xapian docid for the wanted message
|
||||
* @param err receives error information, or NULL
|
||||
*
|
||||
* @return a MuMsg instance (use mu_msg_unref when done with it), or
|
||||
* NULL in case of error
|
||||
*/
|
||||
MuMsg* mu_store_get_msg (MuStore *self, unsigned docid, GError **err);
|
||||
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_STORE_H__*/
|
||||
396
lib/mu-str-normalize.c
Normal file
396
lib/mu-str-normalize.c
Normal file
@ -0,0 +1,396 @@
|
||||
/*
|
||||
** Copyright (C) 2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "mu-str.h"
|
||||
|
||||
|
||||
char*
|
||||
mu_str_normalize (const char *str, gboolean downcase)
|
||||
{
|
||||
g_return_val_if_fail (str, NULL);
|
||||
|
||||
return mu_str_normalize_in_place (g_strdup(str), downcase);
|
||||
}
|
||||
|
||||
|
||||
/* this implementation should work for _all_ locales. */
|
||||
static char*
|
||||
mu_str_normalize_in_place_generic (char *str, gboolean downcase)
|
||||
{
|
||||
/* FIXME: add accent-folding etc. */
|
||||
|
||||
if (downcase) {
|
||||
|
||||
char *norm;
|
||||
size_t len;
|
||||
|
||||
len = strlen (str);
|
||||
norm = g_utf8_strdown (str, len);
|
||||
|
||||
if (strlen (norm) > len)
|
||||
g_warning ("normalized text doesn't fit :/");
|
||||
|
||||
memcpy (str, norm, len);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* this implementation works for accented chars in Unicode Blocks
|
||||
* 'Latin-1 Supplement' and 'Latin Extended-A'. An alternative (slower
|
||||
* but much simpler) implementation would be to use g_utf8_normalize
|
||||
* to decompose characters in the accent part and the character part,
|
||||
* and then get rid of the former. That would be slower than what we
|
||||
* do here, but also more *complete*. It's unclear whether it would
|
||||
* be slower *in practice* => needs checking
|
||||
*/
|
||||
|
||||
/* we can normalize in-place, as the normalized string will never be
|
||||
* longer than the original. even for replacements that are 2 chars
|
||||
* wide (e.g. German ß => ss), the replacement is 2 bytes, like the
|
||||
* original 0xc3 0x9f
|
||||
*/
|
||||
char*
|
||||
mu_str_normalize_in_place (char *str, gboolean downcase)
|
||||
{
|
||||
const guchar *cur;
|
||||
int i;
|
||||
|
||||
g_return_val_if_fail (str, NULL);
|
||||
|
||||
if (*str == '\0')
|
||||
return str;
|
||||
|
||||
for (i = 0, cur = (const guchar*)str; *cur; ++cur) {
|
||||
|
||||
/* special case for plain-old ascii */
|
||||
if ((*cur < 0x80)) {
|
||||
str[i++] = downcase ? tolower (*cur) : *cur;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*cur == 0xc3) { /* latin-1 supplement */
|
||||
++cur;
|
||||
switch (*cur) {
|
||||
|
||||
case 0x80:
|
||||
case 0x81:
|
||||
case 0x82:
|
||||
case 0x83:
|
||||
case 0x84:
|
||||
case 0x85: str[i++] = downcase ? 'a' : 'A' ; break;
|
||||
|
||||
case 0x86:
|
||||
str[i++] = downcase ? 'a' : 'A' ;
|
||||
str[i++] = 'e';
|
||||
break;
|
||||
|
||||
case 0x87: str[i++] = downcase ? 'c' : 'C'; break;
|
||||
|
||||
case 0x88:
|
||||
case 0x89:
|
||||
case 0x8a:
|
||||
case 0x8b:
|
||||
str[i++] = downcase ? 'e' : 'E';
|
||||
break;
|
||||
|
||||
case 0x8c:
|
||||
case 0x8d:
|
||||
case 0x8e:
|
||||
case 0x8f: str[i++] = downcase ? 'i': 'I'; break;
|
||||
|
||||
case 0x90: str[i++] = downcase ? 'd' : 'D'; break;
|
||||
case 0x91: str[i++] = downcase ? 'n' : 'N'; break;
|
||||
|
||||
case 0x92:
|
||||
case 0x93:
|
||||
case 0x94:
|
||||
case 0x95:
|
||||
case 0x96: str[i++] = downcase ? 'o' : 'O'; break;
|
||||
|
||||
case 0x99:
|
||||
case 0x9a:
|
||||
case 0x9b:
|
||||
case 0x9c: str[i++] = downcase ? 'u' : 'U'; break;
|
||||
|
||||
case 0x9d: str[i++] = downcase ? 'y' : 'Y'; break;
|
||||
|
||||
case 0x9e:
|
||||
str[i++] = downcase ? 't' : 'T';
|
||||
str[i++] = 'h';
|
||||
break;
|
||||
|
||||
case 0x9f: str[i++] = 's'; str[i++] = 's'; break;
|
||||
|
||||
case 0xa0:
|
||||
case 0xa1:
|
||||
case 0xa2:
|
||||
case 0xa3:
|
||||
case 0xa4:
|
||||
case 0xa5: str[i++] = 'a'; break;
|
||||
|
||||
case 0xa6: str[i++] = 'a'; str[i++] = 'e'; break;
|
||||
case 0xa7: str[i++] = 'c'; break;
|
||||
|
||||
case 0xa8:
|
||||
case 0xa9:
|
||||
case 0xaa:
|
||||
case 0xab: str[i++] = 'e'; break;
|
||||
|
||||
case 0xac:
|
||||
case 0xad:
|
||||
case 0xae:
|
||||
case 0xaf: str[i++] = 'i'; break;
|
||||
|
||||
case 0xb0: str[i++] = 'd'; break;
|
||||
case 0xb1: str[i++] = 'n'; break;
|
||||
|
||||
case 0xb2:
|
||||
case 0xb3:
|
||||
case 0xb4:
|
||||
case 0xb5:
|
||||
case 0xb6: str[i++] = 'o'; break;
|
||||
|
||||
case 0xb9:
|
||||
case 0xba:
|
||||
case 0xbb:
|
||||
case 0xbc: str[i++] = 'u'; break;
|
||||
|
||||
case 0xbd: str[i++] = 'y'; break;
|
||||
case 0xbe: str[i++] = 't'; str[i++] = 'h'; break;
|
||||
case 0xbf: str[i++] = 'y'; break;
|
||||
|
||||
default:
|
||||
str[i++] = *cur;
|
||||
}
|
||||
|
||||
} else if (*cur == 0xc4) { /* Latin Extended-A (0x04) */
|
||||
++cur;
|
||||
switch (*cur) {
|
||||
case 0x80:
|
||||
case 0x82:
|
||||
case 0x84: str[i++] = downcase ? 'a' : 'A'; break;
|
||||
|
||||
case 0x86:
|
||||
case 0x88:
|
||||
case 0x8a:
|
||||
case 0x8c: str[i++] = downcase ? 'c' : 'C'; break;
|
||||
|
||||
case 0x8e:
|
||||
case 0x90: str[i++] = downcase ? 'd' : 'D'; break;
|
||||
|
||||
case 0x92:
|
||||
case 0x94:
|
||||
case 0x96:
|
||||
case 0x98:
|
||||
case 0x9a: str[i++] = downcase ? 'e' : 'E'; break;
|
||||
|
||||
case 0x9c:
|
||||
case 0x9e:
|
||||
case 0xa0:
|
||||
case 0xa2: str[i++] = downcase ? 'g' : 'G'; break;
|
||||
|
||||
case 0xa4:
|
||||
case 0xa6: str[i++] = downcase ? 'h' : 'H'; break;
|
||||
|
||||
case 0xa8:
|
||||
case 0xaa:
|
||||
case 0xac:
|
||||
case 0xae:
|
||||
case 0xb0: str[i++] = downcase ? 'i' : 'I'; break;
|
||||
|
||||
case 0xb2:
|
||||
str[i++] = downcase ? 'i' : 'I';
|
||||
str[i++] = downcase ? 'j' : 'J';
|
||||
break;
|
||||
|
||||
|
||||
case 0xb4: str[i++] = downcase ? 'j' : 'J'; break;
|
||||
|
||||
case 0xb6: str[i++] = downcase ? 'k' : 'K'; break;
|
||||
|
||||
case 0xb9:
|
||||
case 0xbb:
|
||||
case 0xbd:
|
||||
case 0xbf: str[i++] = downcase ? 'l': 'L'; break;
|
||||
|
||||
case 0x81:
|
||||
case 0x83:
|
||||
case 0x85: str[i++] = 'a'; break;
|
||||
|
||||
case 0x87:
|
||||
case 0x89:
|
||||
case 0x8b:
|
||||
case 0x8d: str[i++] = 'c'; break;
|
||||
|
||||
case 0x8f:
|
||||
case 0x91: str[i++] = 'd'; break;
|
||||
|
||||
case 0x93:
|
||||
case 0x95:
|
||||
case 0x97:
|
||||
case 0x99:
|
||||
case 0x9b: str[i++] = 'e'; break;
|
||||
|
||||
case 0x9d:
|
||||
case 0x9f:
|
||||
case 0xa1:
|
||||
case 0xa: str[i++] = 'g'; break;
|
||||
|
||||
case 0xa5:
|
||||
case 0xa7: str[i++] = 'h'; break;
|
||||
|
||||
case 0xa9:
|
||||
case 0xab:
|
||||
case 0xad:
|
||||
case 0xaf:
|
||||
case 0xb1: str[i++] = 'i'; break;
|
||||
|
||||
case 0xb3: str[i++] = 'i'; str[i++] = 'j'; break;
|
||||
|
||||
case 0xb5: str[i++] = 'j'; break;
|
||||
|
||||
case 0xb7:
|
||||
case 0xb8: str[i++] = 'k'; break;
|
||||
|
||||
case 0xba:
|
||||
case 0xbc:
|
||||
case 0xbe: str[i++] = 'l'; break;
|
||||
|
||||
default: str[i++] = *cur; break;
|
||||
|
||||
}
|
||||
|
||||
} else if (*cur == 0xc5) { /* Latin Extended-A (0xc5) */
|
||||
++cur;
|
||||
switch (*cur) {
|
||||
case 0x81: str[i++] = downcase ? 'l': 'L'; break;
|
||||
|
||||
case 0x83:
|
||||
case 0x85:
|
||||
case 0x87: str[i++] = downcase ? 'n': 'N'; break;
|
||||
|
||||
case 0x8c:
|
||||
case 0x8e:
|
||||
case 0x90: str[i++] = downcase ? 'o': 'O'; break;
|
||||
|
||||
case 0x92:
|
||||
str[i++] = downcase ? 'o': 'O';
|
||||
str[i++] = 'e';
|
||||
break;
|
||||
|
||||
case 0x94:
|
||||
case 0x96:
|
||||
case 0x98: str[i++] = downcase ? 'r': 'R'; break;
|
||||
|
||||
case 0x9a:
|
||||
case 0x9c:
|
||||
case 0x9e:
|
||||
case 0xa0: str[i++] = downcase ? 's': 'S'; break;
|
||||
|
||||
case 0xa2:
|
||||
case 0xa4:
|
||||
case 0xa6: str[i++] = downcase ? 't': 'T'; break;
|
||||
|
||||
case 0xa8:
|
||||
case 0xaa:
|
||||
case 0xac:
|
||||
case 0xae:
|
||||
case 0xb0:
|
||||
case 0xb2: str[i++] = downcase ? 'u': 'U'; break;
|
||||
case 0xb4: str[i++] = downcase ? 'w': 'W'; break;
|
||||
|
||||
case 0xb6:
|
||||
case 0xb8: str[i++] = downcase ? 'y': 'Y'; break;
|
||||
|
||||
case 0xb9:
|
||||
case 0xbb:
|
||||
case 0xbd: str[i++] = downcase ? 'z': 'Z'; break;
|
||||
|
||||
case 0x80:
|
||||
case 0x82: str[i++] = 'l'; break;
|
||||
|
||||
case 0x84:
|
||||
case 0x86:
|
||||
case 0x88:
|
||||
case 0x89:
|
||||
case 0x8a:
|
||||
case 0x8b: str[i++] = 'n'; break;
|
||||
|
||||
case 0x8d:
|
||||
case 0x8f:
|
||||
case 0x91: str[i++] = 'o'; break;
|
||||
|
||||
case 0x93: str[i++] = 'o'; str[i++] = 'e'; break;
|
||||
|
||||
case 0x95:
|
||||
case 0x97:
|
||||
case 0x99: str[i++] = 'r'; break;
|
||||
|
||||
case 0x9b:
|
||||
case 0x9d:
|
||||
case 0x9f:
|
||||
case 0xa1: str[i++] = 's'; break;
|
||||
|
||||
case 0xa3:
|
||||
case 0xa5:
|
||||
case 0xa7: str[i++] = 't'; break;
|
||||
|
||||
case 0xa9:
|
||||
case 0xab:
|
||||
case 0xad:
|
||||
case 0xaf:
|
||||
case 0xb1:
|
||||
case 0xb3: str[i++] = 'u'; break;
|
||||
|
||||
case 0xb5: str[i++] = 'w'; break;
|
||||
|
||||
case 0xb7: str[i++] = 'y'; break;
|
||||
|
||||
case 0xba:
|
||||
case 0xbc:
|
||||
case 0xbe: str[i++] = 'z'; break;
|
||||
|
||||
case 0xbf: str[i++] = 's'; break;
|
||||
|
||||
default: str[i++] = *cur; break;
|
||||
}
|
||||
} else {
|
||||
/* our fast-path for latin-utf8 does not work -- bummer!
|
||||
* use something more generic (but a bit slower)
|
||||
*/
|
||||
return mu_str_normalize_in_place_generic (str, downcase);
|
||||
}
|
||||
}
|
||||
|
||||
str[i] = '\0';
|
||||
|
||||
return str;
|
||||
}
|
||||
681
lib/mu-str.c
Normal file
681
lib/mu-str.c
Normal file
@ -0,0 +1,681 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* hopefully, this should get us a sane PATH_MAX */
|
||||
#include <limits.h>
|
||||
/* not all systems provide PATH_MAX in limits.h */
|
||||
#ifndef PATH_MAX
|
||||
#include <sys/param.h>
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX MAXPATHLEN
|
||||
#endif /*!PATH_MAX*/
|
||||
#endif /*PATH_MAX*/
|
||||
|
||||
#include "mu-str.h"
|
||||
#include "mu-msg-fields.h"
|
||||
|
||||
|
||||
const char*
|
||||
mu_str_size_s (size_t s)
|
||||
{
|
||||
static char buf[32];
|
||||
|
||||
#ifdef HAVE_GLIB216
|
||||
char *tmp;
|
||||
|
||||
tmp = g_format_size_for_display ((goffset)s);
|
||||
strncpy (buf, tmp, sizeof(buf));
|
||||
buf[sizeof(buf) -1] = '\0'; /* just in case */
|
||||
g_free (tmp);
|
||||
|
||||
#else
|
||||
if (s >= 1000 * 1000)
|
||||
g_snprintf(buf, sizeof(buf), "%.1f MB",
|
||||
(double)s/(1000*1000));
|
||||
else
|
||||
g_snprintf(buf, sizeof(buf), "%.1f kB", (double)s/(1000));
|
||||
#endif /*HAVE_GLIB216*/
|
||||
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
char*
|
||||
mu_str_size (size_t s)
|
||||
{
|
||||
return g_strdup (mu_str_size_s(s));
|
||||
}
|
||||
|
||||
const char*
|
||||
mu_str_flags_s (MuFlags flags)
|
||||
{
|
||||
return mu_flags_to_str_s (flags, MU_FLAG_TYPE_ANY);
|
||||
}
|
||||
|
||||
char*
|
||||
mu_str_flags (MuFlags flags)
|
||||
{
|
||||
return g_strdup (mu_str_flags_s(flags));
|
||||
}
|
||||
|
||||
char*
|
||||
mu_str_summarize (const char* str, size_t max_lines)
|
||||
{
|
||||
char *summary;
|
||||
size_t nl_seen;
|
||||
unsigned i,j;
|
||||
gboolean last_was_blank;
|
||||
|
||||
g_return_val_if_fail (str, NULL);
|
||||
g_return_val_if_fail (max_lines > 0, NULL);
|
||||
|
||||
/* len for summary <= original len */
|
||||
summary = g_new (gchar, strlen(str) + 1);
|
||||
|
||||
/* copy the string up to max_lines lines, replace CR/LF/tab with
|
||||
* single space */
|
||||
for (i = j = 0, nl_seen = 0, last_was_blank = TRUE;
|
||||
nl_seen < max_lines && str[i] != '\0'; ++i) {
|
||||
|
||||
if (str[i] == '\n' || str[i] == '\r' ||
|
||||
str[i] == '\t' || str[i] == ' ' ) {
|
||||
|
||||
if (str[i] == '\n')
|
||||
++nl_seen;
|
||||
|
||||
/* no double-blanks or blank at end of str */
|
||||
if (!last_was_blank && str[i+1] != '\0')
|
||||
summary[j++] = ' ';
|
||||
|
||||
last_was_blank = TRUE;
|
||||
} else {
|
||||
|
||||
summary[j++] = str[i];
|
||||
last_was_blank = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
summary[j] = '\0';
|
||||
return summary;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cleanup_contact (char *contact)
|
||||
{
|
||||
char *c, *c2;
|
||||
|
||||
/* replace "'<> with space */
|
||||
for (c2 = contact; *c2; ++c2)
|
||||
if (*c2 == '"' || *c2 == '\'' || *c2 == '<' || *c2 == '>')
|
||||
*c2 = ' ';
|
||||
|
||||
/* remove everything between '()' if it's after the 5th pos;
|
||||
* good to cleanup corporate contact address spam... */
|
||||
c = g_strstr_len (contact, -1, "(");
|
||||
if (c && c - contact > 5)
|
||||
*c = '\0';
|
||||
|
||||
g_strstrip (contact);
|
||||
}
|
||||
|
||||
|
||||
/* this is still somewhat simplistic... */
|
||||
const char*
|
||||
mu_str_display_contact_s (const char *str)
|
||||
{
|
||||
static gchar contact[255];
|
||||
gchar *c, *c2;
|
||||
|
||||
str = str ? str : "";
|
||||
g_strlcpy (contact, str, sizeof(contact));
|
||||
|
||||
/* we check for '<', so we can strip out the address stuff in
|
||||
* e.g. 'Hello World <hello@world.xx>, but only if there is
|
||||
* something alphanumeric before the <
|
||||
*/
|
||||
c = g_strstr_len (contact, -1, "<");
|
||||
if (c != NULL) {
|
||||
for (c2 = contact; c2 < c && !(isalnum(*c2)); ++c2);
|
||||
if (c2 != c) /* apparently, there was something,
|
||||
* so we can remove the <... part*/
|
||||
*c = '\0';
|
||||
}
|
||||
|
||||
cleanup_contact (contact);
|
||||
|
||||
return contact;
|
||||
}
|
||||
|
||||
char*
|
||||
mu_str_display_contact (const char *str)
|
||||
{
|
||||
g_return_val_if_fail (str, NULL);
|
||||
|
||||
return g_strdup (mu_str_display_contact_s (str));
|
||||
}
|
||||
|
||||
|
||||
gint64
|
||||
mu_str_size_parse_bkm (const char* str)
|
||||
{
|
||||
gint64 num;
|
||||
|
||||
g_return_val_if_fail (str, -1);
|
||||
|
||||
if (!isdigit(str[0]))
|
||||
return -1;
|
||||
|
||||
num = atoi(str);
|
||||
for (++str; isdigit(*str); ++str);
|
||||
|
||||
switch (tolower(*str)) {
|
||||
case '\0':
|
||||
case 'b' : return num; /* bytes */
|
||||
case 'k': return num * 1000; /* kilobyte */
|
||||
case 'm': return num * 1000 * 1000; /* megabyte */
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
char*
|
||||
mu_str_from_list (const GSList *lst, char sepa)
|
||||
{
|
||||
const GSList *cur;
|
||||
char *str;
|
||||
|
||||
g_return_val_if_fail (sepa, NULL);
|
||||
|
||||
for (cur = lst, str = NULL; cur; cur = g_slist_next(cur)) {
|
||||
|
||||
char *tmp;
|
||||
/* two extra dummy '\0' so -Wstack-protector won't complain */
|
||||
char sep[4] = { '\0', '\0', '\0', '\0' };
|
||||
sep[0] = cur->next ? sepa : '\0';
|
||||
|
||||
tmp = g_strdup_printf ("%s%s%s",
|
||||
str ? str : "",
|
||||
(gchar*)cur->data,
|
||||
sep);
|
||||
g_free (str);
|
||||
str = tmp;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
GSList*
|
||||
mu_str_to_list (const char *str, char sepa, gboolean strip)
|
||||
{
|
||||
GSList *lst;
|
||||
gchar **strs, **cur;
|
||||
/* two extra dummy '\0' so -Wstack-protector won't complain */
|
||||
char sep[4] = { '\0', '\0', '\0', '\0' };
|
||||
|
||||
g_return_val_if_fail (sepa, NULL);
|
||||
|
||||
if (!str)
|
||||
return NULL;
|
||||
|
||||
sep[0] = sepa;
|
||||
strs = g_strsplit (str, sep, -1);
|
||||
|
||||
for (cur = strs, lst = NULL; cur && *cur; ++cur) {
|
||||
char *elm;
|
||||
elm = g_strdup(*cur);
|
||||
if (strip)
|
||||
elm = g_strstrip (elm);
|
||||
|
||||
lst = g_slist_prepend (lst, elm);
|
||||
}
|
||||
|
||||
lst = g_slist_reverse (lst);
|
||||
g_strfreev (strs);
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
|
||||
static gchar*
|
||||
eat_esc_string (char **strlst, GError **err)
|
||||
{
|
||||
char *str;
|
||||
gboolean quoted;
|
||||
GString *gstr;
|
||||
|
||||
str = g_strchug (*strlst);
|
||||
gstr = g_string_sized_new (strlen(str));
|
||||
|
||||
for (quoted = FALSE; *str; ++str) {
|
||||
|
||||
if (*str == '"') {
|
||||
quoted = !quoted;
|
||||
continue;
|
||||
} else if (*str == '\\') {
|
||||
if (str[1] != ' ' && str[1] != '"' && str[1] != '\\')
|
||||
goto err; /* invalid escaping */
|
||||
g_string_append_c (gstr, str[1]);
|
||||
++str;
|
||||
continue;
|
||||
} else if (*str == ' ' && !quoted) {
|
||||
++str;
|
||||
goto leave;
|
||||
} else
|
||||
g_string_append_c (gstr, *str);
|
||||
}
|
||||
leave:
|
||||
*strlst = str;
|
||||
return g_string_free (gstr, FALSE);
|
||||
err:
|
||||
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
|
||||
"error parsing string '%s'", g_strchug(*strlst));
|
||||
*strlst = NULL;
|
||||
return g_string_free (gstr, TRUE);
|
||||
}
|
||||
|
||||
|
||||
GSList*
|
||||
mu_str_esc_to_list (const char *strings, GError **err)
|
||||
{
|
||||
GSList *lst;
|
||||
char *mystrings, *freeme;
|
||||
const char* cur;
|
||||
|
||||
g_return_val_if_fail (strings, NULL);
|
||||
|
||||
for (cur = strings; *cur && (*cur == ' ' || *cur == '\t'); ++cur);
|
||||
freeme = mystrings = g_strdup (cur);
|
||||
|
||||
lst = NULL;
|
||||
do {
|
||||
gchar *str;
|
||||
str = eat_esc_string (&mystrings, err);
|
||||
if (str)
|
||||
lst = g_slist_prepend (lst, str);
|
||||
else {
|
||||
g_free (freeme);
|
||||
mu_str_free_list (lst);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} while (mystrings && *mystrings);
|
||||
|
||||
g_free (freeme);
|
||||
return g_slist_reverse (lst);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void
|
||||
mu_str_free_list (GSList *lst)
|
||||
{
|
||||
g_slist_foreach (lst, (GFunc)g_free, NULL);
|
||||
g_slist_free (lst);
|
||||
}
|
||||
|
||||
const gchar*
|
||||
mu_str_subject_normalize (const gchar* str)
|
||||
{
|
||||
gchar *last_colon;
|
||||
g_return_val_if_fail (str, NULL);
|
||||
|
||||
last_colon = g_strrstr (str, ":");
|
||||
if (!last_colon)
|
||||
return str;
|
||||
else {
|
||||
gchar *str;
|
||||
str = last_colon + 1;
|
||||
while (*str == ' ')
|
||||
++str;
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct _CheckPrefix {
|
||||
const char *str;
|
||||
gboolean match;
|
||||
gboolean range_field;
|
||||
};
|
||||
typedef struct _CheckPrefix CheckPrefix;
|
||||
|
||||
|
||||
|
||||
static void
|
||||
each_check_prefix (MuMsgFieldId mfid, CheckPrefix *cpfx)
|
||||
{
|
||||
const char *pfx;
|
||||
char pfx_short[3] = { 'X', ':', '\0'};
|
||||
char k;
|
||||
|
||||
if (!cpfx || cpfx->match)
|
||||
return;
|
||||
|
||||
k = pfx_short[0] = mu_msg_field_shortcut (mfid);
|
||||
if (k && g_str_has_prefix (cpfx->str, pfx_short)) {
|
||||
cpfx->match = TRUE;
|
||||
cpfx->range_field = mu_msg_field_is_range_field (mfid);
|
||||
}
|
||||
|
||||
pfx = mu_msg_field_name (mfid);
|
||||
if (pfx && g_str_has_prefix (cpfx->str, pfx) &&
|
||||
cpfx->str[strlen(pfx)] == ':') {
|
||||
cpfx->match = TRUE;
|
||||
cpfx->range_field = mu_msg_field_is_range_field (mfid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
check_for_field (const char *str, gboolean *is_field, gboolean *is_range_field)
|
||||
{
|
||||
CheckPrefix pfx;
|
||||
|
||||
pfx.str = str;
|
||||
|
||||
/* skip any non-alphanum starts in cpfx->str; this is to
|
||||
* handle the case where we have e.g. "(maildir:/abc)"
|
||||
*/
|
||||
while (pfx.str && !isalnum(*pfx.str))
|
||||
++pfx.str;
|
||||
|
||||
pfx.match = pfx.range_field = FALSE;
|
||||
|
||||
mu_msg_field_foreach ((MuMsgFieldForeachFunc)each_check_prefix,
|
||||
&pfx);
|
||||
|
||||
*is_field = pfx.match;
|
||||
*is_range_field = pfx.range_field;
|
||||
}
|
||||
|
||||
/*
|
||||
* Xapian treats various characters such as '@', '-', ':' and '.'
|
||||
* specially; function below is an ugly hack to make it DWIM in most
|
||||
* cases...
|
||||
*
|
||||
* function expects search terms (not complete queries)
|
||||
* */
|
||||
char*
|
||||
mu_str_xapian_escape_in_place (char *term, gboolean esc_space)
|
||||
{
|
||||
unsigned char *cur;
|
||||
const char escchar = '_';
|
||||
gboolean is_field, is_range_field;
|
||||
unsigned colon;
|
||||
|
||||
g_return_val_if_fail (term, NULL);
|
||||
|
||||
check_for_field (term, &is_field, &is_range_field);
|
||||
|
||||
for (colon = 0, cur = (unsigned char*)term; *cur; ++cur) {
|
||||
|
||||
switch (*cur) {
|
||||
|
||||
case '.': /* escape '..' if it's not a range field*/
|
||||
if (is_range_field && cur[1] == '.')
|
||||
cur += 1;
|
||||
else
|
||||
*cur = escchar;
|
||||
break;
|
||||
case ':':
|
||||
/* if there's a registered xapian prefix
|
||||
* before the *first* ':', don't touch
|
||||
* it. Otherwise replace ':' with '_'... ugh
|
||||
* yuck ugly...
|
||||
*/
|
||||
if (colon != 0 || !is_field)
|
||||
*cur = escchar;
|
||||
++colon;
|
||||
break;
|
||||
case '(':
|
||||
case ')':
|
||||
case '\'':
|
||||
case '*': /* wildcard */
|
||||
break;
|
||||
default:
|
||||
/* escape all other special stuff */
|
||||
if (*cur < 0x80 && !isalnum (*cur))
|
||||
*cur = escchar;
|
||||
}
|
||||
}
|
||||
|
||||
/* downcase try to remove accents etc. */
|
||||
return mu_str_normalize_in_place (term, TRUE);
|
||||
}
|
||||
|
||||
char*
|
||||
mu_str_xapian_escape (const char *query, gboolean esc_space)
|
||||
{
|
||||
g_return_val_if_fail (query, NULL);
|
||||
|
||||
return mu_str_xapian_escape_in_place (g_strdup(query), esc_space);
|
||||
}
|
||||
|
||||
|
||||
/* note: this function is *not* re-entrant, it returns a static buffer */
|
||||
const char*
|
||||
mu_str_fullpath_s (const char* path, const char* name)
|
||||
{
|
||||
static char buf[PATH_MAX + 1];
|
||||
|
||||
g_return_val_if_fail (path, NULL);
|
||||
|
||||
snprintf (buf, sizeof(buf), "%s%c%s", path, G_DIR_SEPARATOR,
|
||||
name ? name : "");
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_str_escape_c_literal (const gchar* str, gboolean in_quotes)
|
||||
{
|
||||
const char* cur;
|
||||
GString *tmp;
|
||||
|
||||
g_return_val_if_fail (str, NULL);
|
||||
|
||||
tmp = g_string_sized_new (2 * strlen(str));
|
||||
|
||||
if (in_quotes)
|
||||
g_string_append_c (tmp, '"');
|
||||
|
||||
for (cur = str; *cur; ++cur)
|
||||
switch (*cur) {
|
||||
case '\\': tmp = g_string_append (tmp, "\\\\"); break;
|
||||
case '"': tmp = g_string_append (tmp, "\\\""); break;
|
||||
default: tmp = g_string_append_c (tmp, *cur);
|
||||
}
|
||||
|
||||
if (in_quotes)
|
||||
g_string_append_c (tmp, '"');
|
||||
|
||||
return g_string_free (tmp, FALSE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* turn \0-terminated buf into ascii (which is a utf8 subset); convert
|
||||
* any non-ascii into '.'
|
||||
*/
|
||||
char*
|
||||
mu_str_asciify_in_place (char *buf)
|
||||
{
|
||||
char *c;
|
||||
|
||||
g_return_val_if_fail (buf, NULL);
|
||||
|
||||
for (c = buf; c && *c; ++c)
|
||||
if (!isascii(*c))
|
||||
c[0] = '.';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
char*
|
||||
mu_str_utf8ify (const char *buf)
|
||||
{
|
||||
char *utf8;
|
||||
|
||||
g_return_val_if_fail (buf, NULL);
|
||||
|
||||
utf8 = g_strdup (buf);
|
||||
|
||||
if (!g_utf8_validate (buf, -1, NULL))
|
||||
mu_str_asciify_in_place (utf8);
|
||||
|
||||
return utf8;
|
||||
}
|
||||
|
||||
|
||||
|
||||
gchar*
|
||||
mu_str_convert_to_utf8 (const char* buffer, const char *charset)
|
||||
{
|
||||
GError *err;
|
||||
gchar * utf8;
|
||||
|
||||
g_return_val_if_fail (buffer, NULL);
|
||||
g_return_val_if_fail (charset, NULL );
|
||||
|
||||
err = NULL;
|
||||
utf8 = g_convert_with_fallback (buffer, -1, "UTF-8",
|
||||
charset, NULL,
|
||||
NULL, NULL, &err);
|
||||
if (!utf8) {
|
||||
g_debug ("%s: conversion failed from %s: %s",
|
||||
__FUNCTION__, charset, err ? err->message : "");
|
||||
if (err)
|
||||
g_error_free (err);
|
||||
}
|
||||
|
||||
return utf8;
|
||||
}
|
||||
|
||||
|
||||
|
||||
gchar*
|
||||
mu_str_guess_last_name (const char *name)
|
||||
{
|
||||
const gchar *lastsp;
|
||||
|
||||
if (!name)
|
||||
return g_strdup ("");
|
||||
|
||||
lastsp = g_strrstr (name, " ");
|
||||
|
||||
return g_strdup (lastsp ? lastsp + 1 : "");
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_str_guess_first_name (const char *name)
|
||||
{
|
||||
const gchar *lastsp;
|
||||
|
||||
if (!name)
|
||||
return g_strdup ("");
|
||||
|
||||
lastsp = g_strrstr (name, " ");
|
||||
|
||||
if (lastsp)
|
||||
return g_strndup (name, lastsp - name);
|
||||
else
|
||||
return g_strdup (name);
|
||||
}
|
||||
|
||||
static gchar*
|
||||
cleanup_str (const char* str)
|
||||
{
|
||||
gchar *s;
|
||||
const gchar *cur;
|
||||
unsigned i;
|
||||
|
||||
if (mu_str_is_empty(str))
|
||||
return g_strdup ("");
|
||||
|
||||
s = g_new0 (char, strlen(str) + 1);
|
||||
|
||||
for (cur = str, i = 0; *cur; ++cur) {
|
||||
if (ispunct(*cur) || isspace(*cur))
|
||||
continue;
|
||||
else
|
||||
s[i++] = *cur;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_str_guess_nick (const char* name)
|
||||
{
|
||||
gchar *fname, *lname, *nick;
|
||||
gchar initial[7];
|
||||
|
||||
fname = mu_str_guess_first_name (name);
|
||||
lname = mu_str_guess_last_name (name);
|
||||
|
||||
/* if there's no last name, use first name as the nick */
|
||||
if (mu_str_is_empty(fname) || mu_str_is_empty(lname)) {
|
||||
g_free (lname);
|
||||
nick = fname;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
memset (initial, 0, sizeof(initial));
|
||||
/* couldn't we get an initial for the last name? */
|
||||
if (g_unichar_to_utf8 (g_utf8_get_char (lname), initial) == 0) {
|
||||
g_free (lname);
|
||||
nick = fname;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
nick = g_strdup_printf ("%s%s", fname, initial);
|
||||
g_free (fname);
|
||||
g_free (lname);
|
||||
|
||||
leave:
|
||||
{
|
||||
gchar *tmp;
|
||||
tmp = cleanup_str (nick);
|
||||
g_free (nick);
|
||||
nick = tmp;
|
||||
}
|
||||
|
||||
return nick;
|
||||
}
|
||||
352
lib/mu-str.h
Normal file
352
lib/mu-str.h
Normal file
@ -0,0 +1,352 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_STR_H__
|
||||
#define __MU_STR_H__
|
||||
|
||||
#include <time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "lib/mu-msg.h"
|
||||
#include "lib/mu-flags.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
/**
|
||||
* create a 'display contact' from an email header To/Cc/Bcc/From-type address
|
||||
* ie., turn
|
||||
* "Foo Bar" <foo@bar.com>
|
||||
* into
|
||||
* Foo Bar
|
||||
* Note that this is based on some simple heuristics. Max length is 255 bytes.
|
||||
*
|
||||
* mu_str_display_contact_s returns a statically allocated
|
||||
* buffer (ie, non-reentrant), while mu_str_display_contact
|
||||
* returns a newly allocated string that you must free with g_free
|
||||
* when done with it.
|
||||
*
|
||||
* @param str a 'contact str' (ie., what is in the To/Cc/Bcc/From fields), or NULL
|
||||
*
|
||||
* @return a newly allocated string with a display contact
|
||||
*/
|
||||
const char* mu_str_display_contact_s (const char *str);
|
||||
char *mu_str_display_contact (const char *str);
|
||||
|
||||
|
||||
/**
|
||||
* get a display size for a given size_t; uses M for sizes >
|
||||
* 1000*1000, k for smaller sizes. Note: this function use the
|
||||
* 10-based SI units, _not_ the powers-of-2 based ones.
|
||||
*
|
||||
* mu_str_size_s returns a ptr to a static buffer,
|
||||
* while mu_str_size returns dynamically allocated
|
||||
* memory that must be freed after use.
|
||||
*
|
||||
* @param t the size as an size_t
|
||||
*
|
||||
* @return a string representation of the size; see above
|
||||
* for what to do with it
|
||||
*/
|
||||
const char* mu_str_size_s (size_t s) G_GNUC_CONST;
|
||||
char* mu_str_size (size_t s) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* get a display string for a given set of flags, OR'ed in
|
||||
* @param flags; one character per flag:
|
||||
* D=draft,F=flagged,N=new,P=passed,R=replied,S=seen,T=trashed
|
||||
* a=has-attachment,s=signed, x=encrypted
|
||||
*
|
||||
* mu_str_file_flags_s returns a ptr to a static buffer,
|
||||
* while mu_str_file_flags returns dynamically allocated
|
||||
* memory that must be freed after use.
|
||||
*
|
||||
* @param flags file flags
|
||||
*
|
||||
* @return a string representation of the flags; see above
|
||||
* for what to do with it
|
||||
*/
|
||||
const char* mu_str_flags_s (MuFlags flags) G_GNUC_CONST;
|
||||
char* mu_str_flags (MuFlags flags)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* get a 'summary' of the string, ie. the first /n/ lines of the
|
||||
* strings, with all newlines removed, replaced by single spaces
|
||||
*
|
||||
* @param str the source string
|
||||
* @param max_lines the maximum number of lines to include in the summary
|
||||
*
|
||||
* @return a newly allocated string with the summary. use g_free to free it.
|
||||
*/
|
||||
char* mu_str_summarize (const char* str, size_t max_lines)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* normalize a string (ie., collapse accented characters etc.), and
|
||||
* optionally, downcase it. Works for accented chars in Unicode Blocks
|
||||
* 'Latin-1 Supplement' and 'Latin Extended-A'
|
||||
*
|
||||
* @param str a valid utf8 string or NULL
|
||||
* @param downcase if TRUE, convert the string to lowercase
|
||||
*
|
||||
* @return the normalize string, or NULL in case of error or str was NULL
|
||||
*/
|
||||
char* mu_str_normalize (const char *str, gboolean downcase)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* normalize a string (ie., collapse accented characters etc.), and
|
||||
* optionally, downcase it. this happen by changing the string; if
|
||||
* that is not desired, use mu_str_normalize. Works for accented chars
|
||||
* in Unicode Blocks 'Latin-1 Supplement' and 'Latin Extended-A'
|
||||
*
|
||||
* @param str a valid utf8 string or NULL
|
||||
* @param downcase if TRUE, convert the string to lowercase
|
||||
*
|
||||
* @return the normalized string, or NULL in case of error or str was
|
||||
* NULL
|
||||
*/
|
||||
char* mu_str_normalize_in_place (char *str, gboolean downcase);
|
||||
|
||||
|
||||
/**
|
||||
* escape the string for use with xapian matching. in practice, if the
|
||||
* string contains an '@', replace '@', single-'.' with '_'. Also,
|
||||
* replace ':' with '_', if it's not following a xapian-prefix (such
|
||||
* as 'subject:', 't:' etc, as defined in mu-msg-fields.[ch]).
|
||||
* changing is done in-place (by changing the argument string). in any
|
||||
* case, the string will be downcased.
|
||||
*
|
||||
* @param query a query string
|
||||
* @param esc_space escape space characters as well
|
||||
*
|
||||
* @return the escaped string or NULL in case of error
|
||||
*/
|
||||
char* mu_str_xapian_escape_in_place (char *query, gboolean esc_space);
|
||||
|
||||
/**
|
||||
* escape the string for use with xapian matching. in practice, if the
|
||||
* string contains an '@', replace '@', single-'.' with '_'. Also,
|
||||
* replace ':' with '_', if it's not following a xapian-prefix (such
|
||||
* as 'subject:', 't:' etc, as defined in mu-msg-fields.[ch]).
|
||||
*
|
||||
* @param query a query string
|
||||
* @param esc_space escape space characters as well
|
||||
*
|
||||
* @return the escaped string (free with g_free) or NULL in case of error
|
||||
*/
|
||||
char* mu_str_xapian_escape (const char *query, gboolean esc_space)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* parse a byte size; a size is a number, with optionally a
|
||||
* unit. Units recognized are b/B (bytes) k/K (1000) and m/M
|
||||
* (1000*1000). Only the first letter is checked and the function is
|
||||
* not case-sensitive, so 1000Kb, 3M will work equally well. Note,
|
||||
* for kB, MB etc., we then follow the SI standards, not 2^10 etc. The
|
||||
* 'b' may be omitted.
|
||||
*
|
||||
* practical sizes for email messages are in terms of Mb; even in
|
||||
* extreme cases it should be under 100 Mb. Function return
|
||||
* GUINT64_MAX if there a parsing error
|
||||
*
|
||||
* @param str a string with a size, such a "100", "100Kb", "1Mb"
|
||||
*
|
||||
* @return the corresponding size in bytes, or -1 in case of error
|
||||
*/
|
||||
gint64 mu_str_size_parse_bkm (const char* str);
|
||||
|
||||
/**
|
||||
* create a full path from a path + a filename. function is _not_
|
||||
* reentrant.
|
||||
*
|
||||
* @param path a path (!= NULL)
|
||||
* @param name a name (may be NULL)
|
||||
*
|
||||
* @return the path as a statically allocated buffer. don't free.
|
||||
*/
|
||||
const char* mu_str_fullpath_s (const char* path, const char* name);
|
||||
|
||||
|
||||
/**
|
||||
* escape a string like a string literal in C; ie. replace \ with \\,
|
||||
* and " with \"
|
||||
*
|
||||
* @param str a non-NULL str
|
||||
* @param in_quotes whether the result should be enclosed in ""
|
||||
*
|
||||
* @return the escaped string, newly allocated (free with g_free)
|
||||
*/
|
||||
char* mu_str_escape_c_literal (const gchar* str, gboolean in_quotes)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* turn a string into plain ascii by replacing each non-ascii
|
||||
* character with a dot ('.'). replacement is done in-place.
|
||||
*
|
||||
* @param buf a buffer to asciify
|
||||
*
|
||||
* @return the buf ptr (as to allow for function composition)
|
||||
*/
|
||||
char* mu_str_asciify_in_place (char *buf);
|
||||
|
||||
|
||||
/**
|
||||
* turn string in buf into valid utf8. If this string is not valid
|
||||
* utf8 already, the function massages the offending characters.
|
||||
*
|
||||
* @param buf a buffer to utf8ify
|
||||
*
|
||||
* @return a newly allocated utf8 string
|
||||
*/
|
||||
char* mu_str_utf8ify (const char *buf);
|
||||
|
||||
|
||||
/**
|
||||
* convert a string in a certain charset into utf8
|
||||
*
|
||||
* @param buffer a buffer to convert
|
||||
* @param charset source character set.
|
||||
*
|
||||
* @return a UTF8 string (which you need to g_free when done with it),
|
||||
* or NULL in case of error
|
||||
*/
|
||||
gchar* mu_str_convert_to_utf8 (const char* buffer, const char *charset);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* macro to check whether the string is empty, ie. if it's NULL or
|
||||
* it's length is 0
|
||||
*
|
||||
* @param S a string
|
||||
*
|
||||
* @return TRUE if the string is empty, FALSE otherwise
|
||||
*/
|
||||
#define mu_str_is_empty(S) ((!(S)||!(*S))?TRUE:FALSE)
|
||||
|
||||
|
||||
/**
|
||||
* convert a GSList of strings to a #sepa-separated list
|
||||
*
|
||||
* @param lst a GSList
|
||||
* @param the separator character
|
||||
*
|
||||
* @return a newly allocated string
|
||||
*/
|
||||
char* mu_str_from_list (const GSList *lst, char sepa);
|
||||
|
||||
|
||||
/**
|
||||
* convert a #sepa-separated list of strings in to a GSList
|
||||
*
|
||||
* @param str a #sepa-separated list of strings
|
||||
* @param the separator character
|
||||
* @param remove leading/trailing whitespace from the string
|
||||
*
|
||||
* @return a newly allocated GSList (free with mu_str_free_list)
|
||||
*/
|
||||
GSList* mu_str_to_list (const char *str, char sepa, gboolean strip);
|
||||
|
||||
|
||||
/**
|
||||
* convert a string (with possible escaping) to a list. list items are
|
||||
* separated by one or more spaces. list items can be quoted (using
|
||||
* '"'), and '"', ' ' and '\' use their special meaning when prefixed
|
||||
* with \.
|
||||
*
|
||||
* @param str a string
|
||||
*
|
||||
* @return a list of elements or NULL in case of error
|
||||
*/
|
||||
GSList* mu_str_esc_to_list (const char *str, GError **err);
|
||||
|
||||
|
||||
/**
|
||||
* free a GSList consisting of allocated strings
|
||||
*
|
||||
* @param lst a GSList
|
||||
*/
|
||||
void mu_str_free_list (GSList *lst);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* strip the subject of Re:, Fwd: etc.
|
||||
*
|
||||
* @param str a subject string
|
||||
*
|
||||
* @return a new string -- this is pointing somewhere inside the @str;
|
||||
* no copy is made, don't free
|
||||
*/
|
||||
const gchar* mu_str_subject_normalize (const gchar* str);
|
||||
|
||||
|
||||
/**
|
||||
* guess some nick name for the given name; if we can determine an
|
||||
* first name, last name, the nick will be first name + the first char
|
||||
* of the last name. otherwise, it's just the first name. clearly,
|
||||
* this is just a rough guess for setting an initial value for nicks.
|
||||
*
|
||||
* @param name a name
|
||||
*
|
||||
* @return the guessed nick, as a newly allocated string (free with g_free)
|
||||
*/
|
||||
gchar* mu_str_guess_nick (const char* name)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* guess the first name for the given name; clearly,
|
||||
* this is just a rough guess for setting an initial value.
|
||||
*
|
||||
* @param name a name
|
||||
*
|
||||
* @return the first name, as a newly allocated string (free with
|
||||
* g_free)
|
||||
*/
|
||||
gchar* mu_str_guess_first_name (const char* name)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* guess the last name for the given name; clearly,
|
||||
* this is just a rough guess for setting an initial value.
|
||||
*
|
||||
* @param name a name
|
||||
*
|
||||
* @return the last name, as a newly allocated string (free with
|
||||
* g_free)
|
||||
*/
|
||||
gchar* mu_str_guess_last_name (const char* name)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_STR_H__*/
|
||||
426
lib/mu-threader.c
Normal file
426
lib/mu-threader.c
Normal file
@ -0,0 +1,426 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
#include <math.h> /* for log, ceil */
|
||||
#include <string.h> /* for memset */
|
||||
|
||||
#include "mu-threader.h"
|
||||
#include "mu-container.h"
|
||||
#include "mu-str.h"
|
||||
|
||||
/* msg threading implementation based on JWZ's algorithm, as described in:
|
||||
* http://www.jwz.org/doc/threading.html
|
||||
*
|
||||
* the implementation follows the terminology from that doc, so should
|
||||
* be understandable from that... I did change things a bit though
|
||||
*
|
||||
* the end result of the threading operation is a hashtable which maps
|
||||
* docids (ie., Xapian documents == messages) to 'thread paths'; a
|
||||
* thread path is a string denoting the 2-dimensional place of a
|
||||
* message in a list of messages,
|
||||
*
|
||||
* Msg1 => 00000
|
||||
* Msg2 => 00001
|
||||
* Msg3 (child of Msg2) => 00001:00000
|
||||
* Msg4 (child of Msg2) => 00001:00001
|
||||
* Msg5 (child of Msg4) => 00001:00001:00000
|
||||
* Msg6 => 00002
|
||||
*
|
||||
* the padding-0's are added to make them easy to sort using strcmp;
|
||||
* the number hexadecimal numbers, and the length of the 'segments'
|
||||
* (the parts separated by the ':') is equal to ceil(log_16(matchnum))
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/* step 1 */ static GHashTable* create_containers (MuMsgIter *iter);
|
||||
/* step 2 */ static MuContainer *find_root_set (GHashTable *ids);
|
||||
static MuContainer* prune_empty_containers (MuContainer *root);
|
||||
/* static void group_root_set_by_subject (GSList *root_set); */
|
||||
GHashTable* create_doc_id_thread_path_hash (MuContainer *root, size_t match_num);
|
||||
|
||||
/* msg threading algorithm, based on JWZ's algorithm,
|
||||
* http://www.jwz.org/doc/threading.html */
|
||||
GHashTable*
|
||||
mu_threader_calculate (MuMsgIter *iter, size_t matchnum,
|
||||
MuMsgFieldId sortfield, gboolean revert)
|
||||
{
|
||||
GHashTable *id_table, *thread_ids;
|
||||
MuContainer *root_set;
|
||||
|
||||
g_return_val_if_fail (iter, FALSE);
|
||||
g_return_val_if_fail (mu_msg_field_id_is_valid (sortfield) ||
|
||||
sortfield == MU_MSG_FIELD_ID_NONE,
|
||||
FALSE);
|
||||
|
||||
/* step 1 */
|
||||
id_table = create_containers (iter);
|
||||
|
||||
/* step 2 -- the root_set is the list of children without parent */
|
||||
root_set = find_root_set (id_table);
|
||||
|
||||
/* step 3: skip until the end; we still need to containers */
|
||||
|
||||
/* step 4: prune empty containers */
|
||||
root_set = prune_empty_containers (root_set);
|
||||
|
||||
/* sort root set */
|
||||
if (sortfield != MU_MSG_FIELD_ID_NONE)
|
||||
root_set = mu_container_sort (root_set, sortfield, revert,
|
||||
NULL);
|
||||
|
||||
/* step 5: group root set by subject */
|
||||
/* group_root_set_by_subject (root_set); */
|
||||
|
||||
/* sort */
|
||||
mu_msg_iter_reset (iter); /* go all the way back */
|
||||
|
||||
/* finally, deliver the docid => thread-path hash */
|
||||
thread_ids = mu_container_thread_info_hash_new (root_set,
|
||||
matchnum);
|
||||
|
||||
g_hash_table_destroy (id_table); /* step 3*/
|
||||
|
||||
return thread_ids;
|
||||
}
|
||||
|
||||
G_GNUC_UNUSED static void
|
||||
check_dup (const char *msgid, MuContainer *c, GHashTable *hash)
|
||||
{
|
||||
if (g_hash_table_lookup (hash, c)) {
|
||||
g_warning ("ALREADY!!");
|
||||
mu_container_dump (c, FALSE);
|
||||
g_assert (0);
|
||||
} else
|
||||
g_hash_table_insert (hash, c, GUINT_TO_POINTER(TRUE));
|
||||
}
|
||||
|
||||
|
||||
G_GNUC_UNUSED static void
|
||||
assert_no_duplicates (GHashTable *ids)
|
||||
{
|
||||
GHashTable *hash;
|
||||
|
||||
hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
|
||||
g_hash_table_foreach (ids, (GHFunc)check_dup, hash);
|
||||
|
||||
g_hash_table_destroy (hash);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* a referred message is a message that is refered by some other message */
|
||||
static MuContainer*
|
||||
find_or_create_referred (GHashTable *id_table, const char *msgid,
|
||||
gboolean *created)
|
||||
{
|
||||
MuContainer *c;
|
||||
|
||||
g_return_val_if_fail (msgid, NULL);
|
||||
|
||||
c = g_hash_table_lookup (id_table, msgid);
|
||||
*created = !c;
|
||||
if (!c) {
|
||||
c = mu_container_new (NULL, 0, msgid);
|
||||
g_hash_table_insert (id_table, (gpointer)msgid, c);
|
||||
/* assert_no_duplicates (id_table); */
|
||||
}
|
||||
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/* find a container for the given msgid; if it does not exist yet,
|
||||
* create a new one, and register it */
|
||||
static MuContainer*
|
||||
find_or_create (GHashTable *id_table, MuMsg *msg, guint docid)
|
||||
{
|
||||
MuContainer *c;
|
||||
const char* msgid;
|
||||
|
||||
g_return_val_if_fail (msg, NULL);
|
||||
g_return_val_if_fail (docid != 0, NULL);
|
||||
|
||||
msgid = mu_msg_get_msgid (msg);
|
||||
if (!msgid)
|
||||
msgid = mu_msg_get_path (msg); /* fake it */
|
||||
|
||||
c = g_hash_table_lookup (id_table, msgid);
|
||||
|
||||
/* If id_table contains an empty MuContainer for this ID: * *
|
||||
* Store this message in the MuContainer's message slot. */
|
||||
if (c) {
|
||||
if (!c->msg) {
|
||||
c->msg = mu_msg_ref (msg);
|
||||
c->docid = docid;
|
||||
return c;
|
||||
} else {
|
||||
/* special case, not in the JWZ algorithm: the
|
||||
* container exists already and has a message; this
|
||||
* means that we are seeing *another message* with a
|
||||
* message-id we already saw... create this message,
|
||||
* and mark it as a duplicate, and a child of the one
|
||||
* we saw before; use its path as a fake message-id
|
||||
* */
|
||||
MuContainer *c2;
|
||||
const char* fake_msgid;
|
||||
|
||||
fake_msgid = mu_msg_get_path (msg);
|
||||
|
||||
c2 = mu_container_new (msg, docid, fake_msgid);
|
||||
c2->flags = MU_CONTAINER_FLAG_DUP;
|
||||
c = mu_container_append_children (c, c2);
|
||||
|
||||
g_hash_table_insert (id_table, (gpointer)fake_msgid, c2);
|
||||
|
||||
return NULL; /* don't process this message further */
|
||||
}
|
||||
} else { /* Else: Create a new MuContainer object holding
|
||||
this message; Index the MuContainer by
|
||||
Message-ID in id_table. */
|
||||
c = mu_container_new (msg, docid, msgid);
|
||||
g_hash_table_insert (id_table, (gpointer)msgid, c);
|
||||
/* assert_no_duplicates (id_table); */
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
child_elligible (MuContainer *parent, MuContainer *child, gboolean created)
|
||||
{
|
||||
if (!parent || !child)
|
||||
return FALSE;
|
||||
if (child->parent)
|
||||
return FALSE;
|
||||
/* if (created) */
|
||||
/* return TRUE; */
|
||||
if (mu_container_reachable (parent, child))
|
||||
return FALSE;
|
||||
if (mu_container_reachable (child, parent))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void /* 1B */
|
||||
handle_references (GHashTable *id_table, MuContainer *c)
|
||||
{
|
||||
const GSList *refs, *cur;
|
||||
MuContainer *parent;
|
||||
gboolean created;
|
||||
|
||||
refs = mu_msg_get_references (c->msg);
|
||||
if (!refs)
|
||||
return; /* nothing to do */
|
||||
|
||||
/* For each element in the message's References field:
|
||||
|
||||
Find a MuContainer object for the given Message-ID: If
|
||||
there's one in id_table use that; Otherwise, make (and
|
||||
index) one with a null Message. */
|
||||
|
||||
/* go over over our list of refs, until 1 before the last... */
|
||||
created = FALSE;
|
||||
for (parent = NULL, cur = refs; cur; cur = g_slist_next (cur)) {
|
||||
|
||||
MuContainer *child;
|
||||
child = find_or_create_referred (id_table, (gchar*)cur->data,
|
||||
&created);
|
||||
|
||||
/*Link the References field's MuContainers together in
|
||||
* the order implied by the References header.
|
||||
|
||||
If they are already linked, don't change the existing
|
||||
links. Do not add a link if adding that link would
|
||||
introduce a loop: that is, before asserting A->B,
|
||||
search down the children of B to see if A is
|
||||
reachable, and also search down the children of A to
|
||||
see if B is reachable. If either is already reachable
|
||||
as a child of the other, don't add the link. */
|
||||
|
||||
if (child_elligible (parent, child, created))
|
||||
parent = mu_container_append_children (parent, child);
|
||||
|
||||
parent = child;
|
||||
}
|
||||
|
||||
/* 'parent' points to the last ref: our direct parent;
|
||||
|
||||
Set the parent of this message to be the last element in
|
||||
References. Note that this message may have a parent
|
||||
already: this can happen because we saw this ID in a
|
||||
References field, and presumed a parent based on the other
|
||||
entries in that field. Now that we have the actual message,
|
||||
we can be more definitive, so throw away the old parent and
|
||||
use this new one. Find this MuContainer in the parent's
|
||||
children list, and unlink it.
|
||||
|
||||
Note that this could cause this message to now have no
|
||||
parent, if it has no references field, but some message
|
||||
referred to it as the non-first element of its
|
||||
references. (Which would have been some kind of lie...)
|
||||
|
||||
Note that at all times, the various ``parent'' and ``child'' fields
|
||||
must be kept inter-consistent. */
|
||||
|
||||
/* optimization: if the the message was newly added, it's by
|
||||
* definition not reachable yet */
|
||||
if (child_elligible (parent, c, created))
|
||||
parent = mu_container_append_children (parent, c);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* step 1: create the containers, connect them, and fill the id_table */
|
||||
static GHashTable*
|
||||
create_containers (MuMsgIter *iter)
|
||||
{
|
||||
GHashTable *id_table;
|
||||
id_table = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
NULL,
|
||||
(GDestroyNotify)mu_container_destroy);
|
||||
|
||||
for (mu_msg_iter_reset (iter); !mu_msg_iter_is_done (iter);
|
||||
mu_msg_iter_next (iter)) {
|
||||
|
||||
MuContainer *c;
|
||||
MuMsg *msg;
|
||||
unsigned docid;
|
||||
|
||||
/* 1.A */
|
||||
msg = mu_msg_iter_get_msg_floating (iter); /* don't unref */
|
||||
docid = mu_msg_iter_get_docid (iter);
|
||||
|
||||
c = find_or_create (id_table, msg, docid);
|
||||
|
||||
/* 1.B and C */
|
||||
if (c)
|
||||
handle_references (id_table, c);
|
||||
}
|
||||
|
||||
return id_table;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
filter_root_set (const gchar *msgid, MuContainer *c, MuContainer **root_set)
|
||||
{
|
||||
/* ignore children */
|
||||
if (c->parent)
|
||||
return;
|
||||
|
||||
/* ignore duplicates */
|
||||
if (c->flags & MU_CONTAINER_FLAG_DUP)
|
||||
return;
|
||||
|
||||
if (*root_set == NULL) {
|
||||
*root_set = c;
|
||||
return;
|
||||
} else
|
||||
*root_set = mu_container_append_siblings (*root_set, c);
|
||||
}
|
||||
|
||||
|
||||
/* 2. Walk over the elements of id_table, and gather a list of the
|
||||
MuContainer objects that have no parents, but do have children */
|
||||
static MuContainer*
|
||||
find_root_set (GHashTable *ids)
|
||||
{
|
||||
MuContainer *root_set;
|
||||
|
||||
root_set = NULL;
|
||||
g_hash_table_foreach (ids, (GHFunc)filter_root_set, &root_set);
|
||||
|
||||
return root_set;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
prune_maybe (MuContainer *c)
|
||||
{
|
||||
MuContainer *cur;
|
||||
|
||||
for (cur = c->child; cur; cur = cur->next) {
|
||||
if (cur->flags & MU_CONTAINER_FLAG_DELETE)
|
||||
c = mu_container_remove_child (c, cur);
|
||||
else if (cur->flags & MU_CONTAINER_FLAG_SPLICE)
|
||||
c = mu_container_splice_children (c, cur);
|
||||
}
|
||||
|
||||
g_return_val_if_fail (c, FALSE);
|
||||
|
||||
/* don't touch containers with messages */
|
||||
if (c->msg)
|
||||
return TRUE;
|
||||
|
||||
/* A. If it is an msg-less container with no children, mark it
|
||||
* for deletion. */
|
||||
if (!c->child) {
|
||||
c->flags |= MU_CONTAINER_FLAG_DELETE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* B. If the MuContainer has no Message, but does have
|
||||
* children, remove this container but promote its
|
||||
* children to this level (that is, splice them in to
|
||||
* the current child list.)
|
||||
*
|
||||
* Do not promote the children if doing so would
|
||||
* promote them to the root set -- unless there is
|
||||
* only one child, in which case, do.
|
||||
*/
|
||||
if (c->child->next) /* ie., > 1 child */
|
||||
return TRUE;
|
||||
|
||||
c->flags |= MU_CONTAINER_FLAG_SPLICE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static MuContainer*
|
||||
prune_empty_containers (MuContainer *root_set)
|
||||
{
|
||||
MuContainer *cur;
|
||||
|
||||
mu_container_foreach (root_set, (MuContainerForeachFunc)prune_maybe, NULL);
|
||||
|
||||
/* and prune the root_set itself... */
|
||||
for (cur = root_set; cur; cur = cur->next) {
|
||||
|
||||
if (cur->flags & MU_CONTAINER_FLAG_DELETE)
|
||||
root_set = mu_container_remove_sibling (root_set, cur);
|
||||
|
||||
else if (cur->flags & MU_CONTAINER_FLAG_SPLICE) {
|
||||
MuContainer *newchild;
|
||||
newchild = cur->child;
|
||||
cur->child = NULL;
|
||||
root_set = mu_container_append_siblings (root_set, newchild);
|
||||
}
|
||||
}
|
||||
|
||||
return root_set;
|
||||
}
|
||||
56
lib/mu-threader.h
Normal file
56
lib/mu-threader.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_THREADER_H__
|
||||
#define __MU_THREADER_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <mu-msg-iter.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* takes an iter and the total number of matches, and from this
|
||||
* generates a hash-table with information about the thread structure
|
||||
* of these matches.
|
||||
*
|
||||
* the algorithm to find this structure is based on JWZ's
|
||||
* message-threading algorithm, as descrbed in:
|
||||
* http://www.jwz.org/doc/threading.html
|
||||
*
|
||||
* the returned hashtable maps the Xapian docid of iter (msg) to a ptr
|
||||
* to a MuMsgIterThreadInfo structure (see mu-msg-iter.h)
|
||||
*
|
||||
* @param iter an iter; note this function will mu_msgi_iter_reset this iterator
|
||||
* @param matches the number of matches in the set *
|
||||
* @param sortfield the field to sort results by, or
|
||||
* MU_MSG_FIELD_ID_NONE if no sorting should be performed
|
||||
* @param revert if TRUE, if revert the sorting order
|
||||
*
|
||||
* @return a hashtable; free with g_hash_table_destroy when done with it
|
||||
*/
|
||||
GHashTable *mu_threader_calculate (MuMsgIter *iter, size_t matches,
|
||||
MuMsgFieldId sortfield, gboolean revert);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_THREADER_H__*/
|
||||
509
lib/mu-util.c
Normal file
509
lib/mu-util.c
Normal file
@ -0,0 +1,509 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
/*
|
||||
**
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include "mu-util.h"
|
||||
|
||||
#define _XOPEN_SOURCE 500
|
||||
#include <wordexp.h> /* for shell-style globbing */
|
||||
#include <stdlib.h>
|
||||
|
||||
/* hopefully, this should get us a sane PATH_MAX */
|
||||
#include <limits.h>
|
||||
/* not all systems provide PATH_MAX in limits.h */
|
||||
#ifndef PATH_MAX
|
||||
#include <sys/param.h>
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX MAXPATHLEN
|
||||
#endif /*!PATH_MAX*/
|
||||
#endif /*PATH_MAX*/
|
||||
|
||||
#include <string.h>
|
||||
#include <locale.h> /* for setlocale() */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <langinfo.h>
|
||||
|
||||
static char*
|
||||
do_wordexp (const char *path)
|
||||
{
|
||||
wordexp_t wexp;
|
||||
char *dir;
|
||||
|
||||
if (!path) {
|
||||
/* g_debug ("%s: path is empty", __FUNCTION__); */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (wordexp (path, &wexp, 0) != 0) {
|
||||
/* g_debug ("%s: expansion failed for %s", __FUNCTION__, path); */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we just pick the first one */
|
||||
dir = g_strdup (wexp.we_wordv[0]);
|
||||
|
||||
/* strangely, below seems to lead to a crash on MacOS (BSD);
|
||||
so we have to allow for a tiny leak here on that
|
||||
platform... maybe instead of __APPLE__ it should be
|
||||
__BSD__?*/
|
||||
#ifndef __APPLE__
|
||||
wordfree (&wexp);
|
||||
#endif /*__APPLE__*/
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* note, the g_debugs are commented out because this function may be
|
||||
* called before the log handler is installed. */
|
||||
char*
|
||||
mu_util_dir_expand (const char *path)
|
||||
{
|
||||
char *dir;
|
||||
char resolved[PATH_MAX + 1];
|
||||
|
||||
g_return_val_if_fail (path, NULL);
|
||||
|
||||
dir = do_wordexp (path);
|
||||
if (!dir)
|
||||
return NULL; /* error */
|
||||
|
||||
/* don't try realpath if the dir does not exist */
|
||||
if (access (dir, F_OK) != 0)
|
||||
return dir;
|
||||
|
||||
/* now resolve any symlinks, .. etc. */
|
||||
if (realpath (dir, resolved) == NULL) {
|
||||
/* g_debug ("%s: could not get realpath for '%s': %s", */
|
||||
/* __FUNCTION__, dir, strerror(errno)); */
|
||||
g_free (dir);
|
||||
return NULL;
|
||||
} else
|
||||
g_free (dir);
|
||||
|
||||
return g_strdup (resolved);
|
||||
}
|
||||
|
||||
|
||||
char*
|
||||
mu_util_create_tmpdir (void)
|
||||
{
|
||||
gchar *dirname;
|
||||
|
||||
dirname = g_strdup_printf ("%s%cmu-%d%c%x",
|
||||
g_get_tmp_dir(),
|
||||
G_DIR_SEPARATOR,
|
||||
getuid(),
|
||||
G_DIR_SEPARATOR,
|
||||
(int)random()*getpid()*(int)time(NULL));
|
||||
|
||||
if (!mu_util_create_dir_maybe (dirname, 0700, FALSE)) {
|
||||
g_free (dirname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dirname;
|
||||
}
|
||||
|
||||
|
||||
GQuark
|
||||
mu_util_error_quark (void)
|
||||
{
|
||||
static GQuark error_domain = 0;
|
||||
|
||||
if (G_UNLIKELY(error_domain == 0))
|
||||
error_domain = g_quark_from_static_string
|
||||
("mu-error-quark");
|
||||
|
||||
return error_domain;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const char*
|
||||
mu_util_cache_dir (void)
|
||||
{
|
||||
static char cachedir [PATH_MAX];
|
||||
|
||||
snprintf (cachedir, sizeof(cachedir), "%s%cmu-%u",
|
||||
g_get_tmp_dir(), G_DIR_SEPARATOR,
|
||||
getuid());
|
||||
|
||||
return cachedir;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_util_check_dir (const gchar* path, gboolean readable, gboolean writeable)
|
||||
{
|
||||
int mode;
|
||||
struct stat statbuf;
|
||||
|
||||
if (!path)
|
||||
return FALSE;
|
||||
|
||||
mode = F_OK | (readable ? R_OK : 0) | (writeable ? W_OK : 0);
|
||||
|
||||
if (access (path, mode) != 0) {
|
||||
/* g_debug ("Cannot access %s: %s", path, strerror (errno)); */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (stat (path, &statbuf) != 0) {
|
||||
/* g_debug ("Cannot stat %s: %s", path, strerror (errno)); */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return S_ISDIR(statbuf.st_mode) ? TRUE: FALSE;
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_util_guess_maildir (void)
|
||||
{
|
||||
const gchar *mdir1, *home;
|
||||
|
||||
/* first, try MAILDIR */
|
||||
mdir1 = g_getenv ("MAILDIR");
|
||||
|
||||
if (mdir1 && mu_util_check_dir (mdir1, TRUE, FALSE))
|
||||
return g_strdup (mdir1);
|
||||
|
||||
/* then, try <home>/Maildir */
|
||||
home = g_get_home_dir();
|
||||
if (home) {
|
||||
char *mdir2;
|
||||
mdir2 = g_strdup_printf ("%s%cMaildir",
|
||||
home, G_DIR_SEPARATOR);
|
||||
if (mu_util_check_dir (mdir2, TRUE, FALSE))
|
||||
return mdir2;
|
||||
g_free (mdir2);
|
||||
}
|
||||
|
||||
/* nope; nothing found */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_util_guess_mu_homedir (void)
|
||||
{
|
||||
const char* home;
|
||||
|
||||
/* g_get_home_dir use /etc/passwd, not $HOME; this is better,
|
||||
* as HOME may be wrong when using 'sudo' etc.*/
|
||||
home = g_get_home_dir ();
|
||||
|
||||
if (!home) {
|
||||
MU_WRITE_LOG ("failed to determine homedir");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_strdup_printf ("%s%c%s", home ? home : ".",
|
||||
G_DIR_SEPARATOR, ".mu");
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_util_create_dir_maybe (const gchar *path, mode_t mode, gboolean nowarn)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
|
||||
/* if it exists, it must be a readable dir */
|
||||
if (stat (path, &statbuf) == 0) {
|
||||
if ((!S_ISDIR(statbuf.st_mode)) ||
|
||||
(access (path, W_OK|R_OK) != 0)) {
|
||||
if (!nowarn)
|
||||
g_warning ("not a read-writable"
|
||||
"directory: %s", path);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_mkdir_with_parents (path, mode) != 0) {
|
||||
if (!nowarn)
|
||||
g_warning ("failed to create %s: %s",
|
||||
path, strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
gchar*
|
||||
mu_util_str_from_strv (const gchar **params)
|
||||
{
|
||||
GString *str;
|
||||
int i;
|
||||
|
||||
g_return_val_if_fail (params, NULL);
|
||||
|
||||
if (!params[0])
|
||||
return g_strdup ("");
|
||||
|
||||
str = g_string_sized_new (64); /* just a guess */
|
||||
|
||||
for (i = 0; params[i]; ++i) {
|
||||
|
||||
if (i > 0)
|
||||
g_string_append_c (str, ' ');
|
||||
|
||||
g_string_append (str, params[i]);
|
||||
}
|
||||
|
||||
return g_string_free (str, FALSE);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
mu_util_create_writeable_fd (const char* path, mode_t mode,
|
||||
gboolean overwrite)
|
||||
{
|
||||
errno = 0; /* clear! */
|
||||
g_return_val_if_fail (path, -1);
|
||||
|
||||
if (overwrite)
|
||||
return open (path, O_WRONLY|O_CREAT|O_TRUNC, mode);
|
||||
else
|
||||
return open (path, O_WRONLY|O_CREAT|O_EXCL, mode);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_util_is_local_file (const char* path)
|
||||
{
|
||||
/* if it starts with file:// it's a local file (for the
|
||||
* purposes of this function -- if it's on a remote FS it's
|
||||
* still considered local) */
|
||||
if (g_ascii_strncasecmp ("file://", path, strlen("file://")) == 0)
|
||||
return TRUE;
|
||||
|
||||
if (access (path, R_OK) == 0)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
gboolean
|
||||
mu_util_play (const char *path, gboolean allow_local, gboolean allow_remote,
|
||||
GError **err)
|
||||
{
|
||||
gboolean rv;
|
||||
const gchar *argv[3];
|
||||
const char *prog;
|
||||
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
g_return_val_if_fail (mu_util_is_local_file (path) || allow_remote,
|
||||
FALSE);
|
||||
g_return_val_if_fail (!mu_util_is_local_file (path) || allow_local,
|
||||
FALSE);
|
||||
|
||||
prog = g_getenv ("MU_PLAY_PROGRAM");
|
||||
if (!prog) {
|
||||
#ifdef __APPLE__
|
||||
prog = "open";
|
||||
#else
|
||||
prog = "xdg-open";
|
||||
#endif /*!__APPLE__*/
|
||||
}
|
||||
|
||||
argv[0] = prog;
|
||||
argv[1] = path;
|
||||
argv[2] = NULL;
|
||||
|
||||
err = NULL;
|
||||
rv = g_spawn_async (NULL,
|
||||
(gchar**)&argv,
|
||||
NULL,
|
||||
G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, NULL,
|
||||
err);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
unsigned char
|
||||
mu_util_get_dtype_with_lstat (const char *path)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
g_return_val_if_fail (path, DT_UNKNOWN);
|
||||
|
||||
if (lstat (path, &statbuf) != 0) {
|
||||
g_warning ("stat failed on %s: %s", path, strerror(errno));
|
||||
return DT_UNKNOWN;
|
||||
}
|
||||
|
||||
/* we only care about dirs, regular files and links */
|
||||
if (S_ISREG (statbuf.st_mode))
|
||||
return DT_REG;
|
||||
else if (S_ISDIR (statbuf.st_mode))
|
||||
return DT_DIR;
|
||||
else if (S_ISLNK (statbuf.st_mode))
|
||||
return DT_LNK;
|
||||
|
||||
return DT_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_util_locale_is_utf8 (void)
|
||||
{
|
||||
const gchar *dummy;
|
||||
static int is_utf8 = -1;
|
||||
|
||||
if (G_UNLIKELY(is_utf8 == -1))
|
||||
is_utf8 = g_get_charset(&dummy) ? 1 : 0;
|
||||
|
||||
return is_utf8 ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_util_fputs_encoded (const char *str, FILE *stream)
|
||||
{
|
||||
char *conv;
|
||||
int rv;
|
||||
|
||||
g_return_val_if_fail (str, FALSE);
|
||||
g_return_val_if_fail (stream, FALSE);
|
||||
|
||||
/* g_get_charset return TRUE when the locale is UTF8 */
|
||||
if (mu_util_locale_is_utf8())
|
||||
rv = fputs (str, stream);
|
||||
else { /* charset is _not_ utf8, so we actually have to
|
||||
* convert it..*/
|
||||
GError *err;
|
||||
unsigned bytes;
|
||||
err = NULL;
|
||||
conv = g_locale_from_utf8 (str, -1, (gsize*)&bytes, NULL, &err);
|
||||
if (!conv || err) {
|
||||
/* conversion failed; this happens because is
|
||||
* some cases GMime may gives us non-UTF-8
|
||||
* string from e.g. wrongly encoded
|
||||
* message-subjects; if so, we escape the
|
||||
* string */
|
||||
g_warning ("%s: g_locale_from_utf8 failed: %s",
|
||||
__FUNCTION__,
|
||||
err ? err->message : "conversion failed");
|
||||
g_clear_error (&err);
|
||||
g_free (conv);
|
||||
conv = g_strescape (str, NULL);
|
||||
}
|
||||
rv = fputs (conv, stream);
|
||||
g_free (conv);
|
||||
}
|
||||
|
||||
if (rv == EOF) { /* note, apparently, does not set errno */
|
||||
g_warning ("%s: fputs failed", __FUNCTION__);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
mu_util_g_set_error (GError **err, MuError errcode, const char *frm, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char *msg;
|
||||
|
||||
/* don't bother with NULL errors, or errors already set */
|
||||
if (!err || *err)
|
||||
return;
|
||||
|
||||
msg = NULL;
|
||||
va_start (ap, frm);
|
||||
g_vasprintf (&msg, frm, ap);
|
||||
va_end (ap);
|
||||
|
||||
g_set_error (err, MU_ERROR_DOMAIN, errcode, "%s", msg);
|
||||
|
||||
g_free (msg);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
print_args (FILE *stream, const char *frm, va_list args)
|
||||
{
|
||||
gchar *str;
|
||||
gboolean rv;
|
||||
|
||||
str = g_strdup_vprintf (frm, args);
|
||||
|
||||
rv = mu_util_fputs_encoded (str, stream);
|
||||
|
||||
g_free (str);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
mu_util_print_encoded (const char *frm, ...)
|
||||
{
|
||||
va_list args;
|
||||
gboolean rv;
|
||||
|
||||
g_return_val_if_fail (frm, FALSE);
|
||||
|
||||
va_start (args, frm);
|
||||
rv = print_args (stdout, frm, args);
|
||||
va_end (args);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mu_util_printerr_encoded (const char *frm, ...)
|
||||
{
|
||||
va_list args;
|
||||
gboolean rv;
|
||||
|
||||
g_return_val_if_fail (frm, FALSE);
|
||||
|
||||
va_start (args, frm);
|
||||
rv = print_args (stderr, frm, args);
|
||||
va_end (args);
|
||||
|
||||
return rv;
|
||||
}
|
||||
462
lib/mu-util.h
Normal file
462
lib/mu-util.h
Normal file
@ -0,0 +1,462 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation; either version 3 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __MU_UTIL_H__
|
||||
#define __MU_UTIL_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h> /* for mode_t */
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* get the expanded path; ie. perform shell expansion on the path. the
|
||||
* path does not have to exist
|
||||
*
|
||||
* @param path path to expand
|
||||
*
|
||||
* @return the expanded path as a newly allocated string, or NULL in
|
||||
* case of error
|
||||
*/
|
||||
char* mu_util_dir_expand (const char* path)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* guess the maildir; first try $MAILDIR; if it is unset or
|
||||
* non-existant, try ~/Maildir if both fail, return NULL
|
||||
*
|
||||
* @return full path of the guessed Maildir, or NULL; must be freed (gfree)
|
||||
*/
|
||||
char* mu_util_guess_maildir (void)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* guess the place of the mu homedir (typically, ~/.mu). Note, this
|
||||
* directory does not necessarily exist. mu_util_check_dir can be used
|
||||
* to check that
|
||||
*
|
||||
* @return the guessed mu homedir, which needs to be freed with g_free
|
||||
* when no longer needed.
|
||||
*/
|
||||
gchar* mu_util_guess_mu_homedir (void)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* if path exists, check that's a read/writeable dir; otherwise try to
|
||||
* create it (with perms 0700)
|
||||
*
|
||||
* @param path path to the dir
|
||||
* @param mode to set for the dir (as per chmod(1))
|
||||
* @param nowarn, if TRUE, don't write warnings (if any) to stderr
|
||||
*
|
||||
* @return TRUE if a read/writeable directory `path' exists after
|
||||
* leaving this function, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_util_create_dir_maybe (const gchar *path, mode_t mode, gboolean nowarn)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* check whether path is a directory, and optionally, if it's readable
|
||||
* and/or writeable
|
||||
*
|
||||
* @param path dir path
|
||||
* @param readable check for readability
|
||||
* @param writeable check for writability
|
||||
*
|
||||
* @return TRUE if dir exist and has the specified properties
|
||||
*/
|
||||
gboolean mu_util_check_dir (const gchar* path, gboolean readable,
|
||||
gboolean writeable)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* get our the cache directory, typically, /tmp/mu-<userid>/
|
||||
*
|
||||
* @return the cache directory; don't free
|
||||
*/
|
||||
const char* mu_util_cache_dir (void) G_GNUC_CONST;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* create a writeable file and return its file descriptor (which
|
||||
* you'll need to close(2) when done with it.)
|
||||
*
|
||||
* @param path the full path of the file to create
|
||||
* @param the mode to open (ie. 0644 or 0600 etc., see chmod(3)
|
||||
* @param overwrite should we allow for overwriting existing files?
|
||||
*
|
||||
* @return a file descriptor, or -1 in case of error. If it's a file
|
||||
* system error, 'errno' may contain more info. use 'close()' when done
|
||||
* with the file descriptor
|
||||
*/
|
||||
int mu_util_create_writeable_fd (const char* path, mode_t mode,
|
||||
gboolean overwrite)
|
||||
G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
|
||||
/**
|
||||
* check if file is local, ie. on the local file system. this means
|
||||
* that it's either having a file URI, *or* that it's an existing file
|
||||
*
|
||||
* @param path a path
|
||||
*
|
||||
* @return TRUE if the file is local, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_util_is_local_file (const char* path);
|
||||
|
||||
|
||||
/**
|
||||
* is the current locale utf-8 compatible?
|
||||
*
|
||||
* @return TRUE if it's utf8 compatible, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_util_locale_is_utf8 (void) G_GNUC_CONST;
|
||||
|
||||
|
||||
/**
|
||||
* write a string (assumed to be in utf8-format) to a stream,
|
||||
* converted to the current locale
|
||||
*
|
||||
* @param str a string
|
||||
* @param stream a stream
|
||||
*
|
||||
* @return TRUE if printing worked, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_util_fputs_encoded (const char *str, FILE *stream);
|
||||
|
||||
/**
|
||||
* print a formatted string (assumed to be in utf8-format) to stdout,
|
||||
* converted to the current locale
|
||||
*
|
||||
* @param a standard printf() format string, followed by a parameter list
|
||||
*
|
||||
* @return TRUE if printing worked, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_util_print_encoded (const char *frm, ...) G_GNUC_PRINTF(1,2);
|
||||
|
||||
/**
|
||||
* print a formatted string (assumed to be in utf8-format) to stderr,
|
||||
* converted to the current locale
|
||||
*
|
||||
* @param a standard printf() format string, followed by a parameter list
|
||||
*
|
||||
* @return TRUE if printing worked, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_util_printerr_encoded (const char *frm, ...) G_GNUC_PRINTF(1,2);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Try to 'play' (ie., open with it's associated program) a file. On
|
||||
* MacOS, the the program 'open' is used for this; on other platforms
|
||||
* 'xdg-open' to do the actual opening. In addition you can set it to another program
|
||||
* by setting the MU_PLAY_PROGRAM environment variable
|
||||
*
|
||||
* @param path full path of the file to open
|
||||
* @param allow_local allow local files (ie. with file:// prefix or fs paths)
|
||||
* @param allow_remote allow URIs (ie., http, mailto)
|
||||
* @param err receives error information, if any
|
||||
*
|
||||
* @return TRUE if it succeeded, FALSE otherwise
|
||||
*/
|
||||
gboolean mu_util_play (const char *path, gboolean allow_local,
|
||||
gboolean allow_remote, GError **err);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get an error-query for mu, to be used in `g_set_error'. Recent
|
||||
* version of Glib warn when using 0 for the error-domain in
|
||||
* g_set_error.
|
||||
*
|
||||
*
|
||||
* @return an error quark for mu
|
||||
*/
|
||||
GQuark mu_util_error_quark (void) G_GNUC_CONST;
|
||||
#define MU_ERROR_DOMAIN (mu_util_error_quark())
|
||||
|
||||
|
||||
/**
|
||||
* convert a string array in to a string, with the elements separated
|
||||
* by ' '
|
||||
*
|
||||
* @param params a non-NULL, NULL-terminated string array
|
||||
*
|
||||
* @return a newly allocated string
|
||||
*/
|
||||
gchar* mu_util_str_from_strv (const gchar **params)
|
||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
/*
|
||||
* for OSs with out support for direntry->d_type, like Solaris
|
||||
*/
|
||||
#ifndef DT_UNKNOWN
|
||||
enum {
|
||||
DT_UNKNOWN = 0,
|
||||
# define DT_UNKNOWN DT_UNKNOWN
|
||||
DT_FIFO = 1,
|
||||
# define DT_FIFO DT_FIFO
|
||||
DT_CHR = 2,
|
||||
# define DT_CHR DT_CHR
|
||||
DT_DIR = 4,
|
||||
# define DT_DIR DT_DIR
|
||||
DT_BLK = 6,
|
||||
# define DT_BLK DT_BLK
|
||||
DT_REG = 8,
|
||||
# define DT_REG DT_REG
|
||||
DT_LNK = 10,
|
||||
# define DT_LNK DT_LNK
|
||||
DT_SOCK = 12,
|
||||
# define DT_SOCK DT_SOCK
|
||||
DT_WHT = 14
|
||||
# define DT_WHT DT_WHT
|
||||
};
|
||||
#endif /*DT_UNKNOWN*/
|
||||
|
||||
|
||||
/**
|
||||
* get the d_type (as in direntry->d_type) for the file at path, using
|
||||
* lstat(3)
|
||||
*
|
||||
* @param path full path
|
||||
*
|
||||
* @return DT_REG, DT_DIR, DT_LNK, or DT_UNKNOWN (other values are not
|
||||
* supported currently )
|
||||
*/
|
||||
unsigned char mu_util_get_dtype_with_lstat (const char *path);
|
||||
|
||||
|
||||
/**
|
||||
* we need this when using Xapian::Document* from C
|
||||
*
|
||||
*/
|
||||
typedef gpointer XapianDocument;
|
||||
|
||||
/**
|
||||
* we need this when using Xapian::Enquire* from C
|
||||
*
|
||||
*/
|
||||
typedef gpointer XapianEnquire;
|
||||
|
||||
|
||||
/* print a warning for a GError, and free it */
|
||||
#define MU_HANDLE_G_ERROR(GE) \
|
||||
do { \
|
||||
if (!(GE)) \
|
||||
g_warning ("%s:%u: an error occured in %s", \
|
||||
__FILE__, __LINE__, __FUNCTION__); \
|
||||
else { \
|
||||
g_warning ("error %u: %s", (GE)->code, (GE)->message); \
|
||||
g_error_free ((GE)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* don't repeat these catch blocks everywhere...
|
||||
*
|
||||
*/
|
||||
|
||||
#define MU_STORE_CATCH_BLOCK_RETURN(GE,R) \
|
||||
catch (const MuStoreError& merr) { \
|
||||
mu_util_g_set_error ((GE), \
|
||||
merr.mu_error(), "%s", \
|
||||
merr.what().c_str()); \
|
||||
return (R); \
|
||||
} \
|
||||
|
||||
|
||||
#define MU_XAPIAN_CATCH_BLOCK \
|
||||
catch (const Xapian::Error &xerr) { \
|
||||
g_critical ("%s: xapian error '%s'", \
|
||||
__FUNCTION__, xerr.get_msg().c_str()); \
|
||||
} catch (...) { \
|
||||
g_critical ("%s: caught exception", __FUNCTION__); \
|
||||
}
|
||||
|
||||
#define MU_XAPIAN_CATCH_BLOCK_G_ERROR(GE,E) \
|
||||
catch (const Xapian::DatabaseLockError &xerr) { \
|
||||
mu_util_g_set_error ((GE), \
|
||||
MU_ERROR_XAPIAN_CANNOT_GET_WRITELOCK, \
|
||||
"%s: xapian error '%s'", \
|
||||
__FUNCTION__, xerr.get_msg().c_str()); \
|
||||
} catch (const Xapian::DatabaseCorruptError &xerr) { \
|
||||
mu_util_g_set_error ((GE), \
|
||||
MU_ERROR_XAPIAN_CORRUPTION, \
|
||||
"%s: xapian error '%s'", \
|
||||
__FUNCTION__, xerr.get_msg().c_str()); \
|
||||
} catch (const Xapian::DatabaseError &xerr) { \
|
||||
mu_util_g_set_error ((GE),MU_ERROR_XAPIAN, \
|
||||
"%s: xapian error '%s'", \
|
||||
__FUNCTION__, xerr.get_msg().c_str()); \
|
||||
} catch (const Xapian::Error &xerr) { \
|
||||
mu_util_g_set_error ((GE),(E), \
|
||||
"%s: xapian error '%s'", \
|
||||
__FUNCTION__, xerr.get_msg().c_str()); \
|
||||
} catch (...) { \
|
||||
mu_util_g_set_error ((GE),(MU_ERROR_INTERNAL), \
|
||||
"%s: caught exception", __FUNCTION__); \
|
||||
}
|
||||
|
||||
|
||||
#define MU_XAPIAN_CATCH_BLOCK_RETURN(R) \
|
||||
catch (const Xapian::Error &xerr) { \
|
||||
g_critical ("%s: xapian error '%s'", \
|
||||
__FUNCTION__, xerr.get_msg().c_str()); \
|
||||
return (R); \
|
||||
} catch (...) { \
|
||||
g_critical ("%s: caught exception", __FUNCTION__); \
|
||||
return (R); \
|
||||
}
|
||||
|
||||
#define MU_XAPIAN_CATCH_BLOCK_G_ERROR_RETURN(GE,E,R) \
|
||||
catch (const Xapian::Error &xerr) { \
|
||||
mu_util_g_set_error ((GE),(E), \
|
||||
"%s: xapian error '%s'", \
|
||||
__FUNCTION__, xerr.get_msg().c_str()); \
|
||||
return (R); \
|
||||
} catch (...) { \
|
||||
if ((GE)&&!(*(GE))) \
|
||||
mu_util_g_set_error ((GE), \
|
||||
(MU_ERROR_INTERNAL), \
|
||||
"%s: caught exception", __FUNCTION__); \
|
||||
return (R); \
|
||||
}
|
||||
|
||||
/* the name of the (leaf) dir which has the xapian database */
|
||||
#define MU_XAPIAN_DIR_NAME "xapian"
|
||||
|
||||
/* name of the bookmark file */
|
||||
#define MU_BOOKMARK_FILENAME "bookmarks"
|
||||
|
||||
/* metadata key for the xapian 'schema' version */
|
||||
#define MU_STORE_VERSION_KEY "db_version"
|
||||
|
||||
|
||||
/**
|
||||
* log something in the log file; note, we use G_LOG_LEVEL_INFO
|
||||
* for such messages
|
||||
*/
|
||||
#define MU_WRITE_LOG(...) \
|
||||
G_STMT_START { \
|
||||
g_log (G_LOG_DOMAIN, \
|
||||
G_LOG_LEVEL_INFO, \
|
||||
__VA_ARGS__); \
|
||||
} G_STMT_END
|
||||
|
||||
|
||||
|
||||
#define MU_G_ERROR_CODE(GE) ((GE)&&(*(GE))?(*(GE))->code:MU_ERROR)
|
||||
|
||||
|
||||
enum _MuError {
|
||||
/* no error at all! */
|
||||
MU_OK = 0,
|
||||
|
||||
/* generic error */
|
||||
MU_ERROR = 1,
|
||||
MU_ERROR_IN_PARAMETERS = 2,
|
||||
MU_ERROR_INTERNAL = 3,
|
||||
MU_ERROR_NO_MATCHES = 4,
|
||||
|
||||
/* general xapian related error */
|
||||
MU_ERROR_XAPIAN = 11,
|
||||
|
||||
/* (parsing) error in the query */
|
||||
MU_ERROR_XAPIAN_QUERY = 13,
|
||||
/* xapian dir is not accessible */
|
||||
MU_ERROR_XAPIAN_DIR_NOT_ACCESSIBLE = 14,
|
||||
/* database version is not up-to-date */
|
||||
MU_ERROR_XAPIAN_NOT_UP_TO_DATE = 15,
|
||||
/* missing data for a document */
|
||||
MU_ERROR_XAPIAN_MISSING_DATA = 16,
|
||||
/* database corruption */
|
||||
MU_ERROR_XAPIAN_CORRUPTION = 17,
|
||||
/* can't get write lock */
|
||||
MU_ERROR_XAPIAN_CANNOT_GET_WRITELOCK = 18,
|
||||
/* database is empty */
|
||||
MU_ERROR_XAPIAN_IS_EMPTY = 19,
|
||||
/* could not write */
|
||||
MU_ERROR_XAPIAN_STORE_FAILED = 20,
|
||||
/* could not remove */
|
||||
MU_ERROR_XAPIAN_REMOVE_FAILED = 21,
|
||||
|
||||
|
||||
/* GMime related errors */
|
||||
|
||||
/* gmime parsing related error */
|
||||
MU_ERROR_GMIME = 30,
|
||||
|
||||
/* contacts related errors */
|
||||
MU_ERROR_CONTACTS = 50,
|
||||
MU_ERROR_CONTACTS_CANNOT_RETRIEVE = 51,
|
||||
|
||||
|
||||
/* File errors */
|
||||
/* generic file-related error */
|
||||
MU_ERROR_FILE = 70,
|
||||
MU_ERROR_FILE_INVALID_NAME = 71,
|
||||
MU_ERROR_FILE_CANNOT_LINK = 72,
|
||||
MU_ERROR_FILE_CANNOT_OPEN = 73,
|
||||
MU_ERROR_FILE_CANNOT_READ = 74,
|
||||
MU_ERROR_FILE_CANNOT_CREATE = 75,
|
||||
MU_ERROR_FILE_CANNOT_MKDIR = 76,
|
||||
MU_ERROR_FILE_STAT_FAILED = 77,
|
||||
MU_ERROR_FILE_READDIR_FAILED = 78,
|
||||
MU_ERROR_FILE_INVALID_SOURCE = 79,
|
||||
MU_ERROR_FILE_TARGET_EQUALS_SOURCE = 80,
|
||||
MU_ERROR_FILE_CANNOT_WRITE = 81,
|
||||
MU_ERROR_FILE_CANNOT_UNLINK = 82,
|
||||
|
||||
/* not really an error, used in callbacks */
|
||||
MU_STOP = 99
|
||||
};
|
||||
typedef enum _MuError MuError;
|
||||
|
||||
|
||||
/**
|
||||
* set an error if it's not already set
|
||||
*
|
||||
* @param err errptr, or NULL
|
||||
* @param errcode error code
|
||||
* @param frm printf-style format, followed by paremeters
|
||||
*/
|
||||
void mu_util_g_set_error (GError **err, MuError errcode, const char *frm, ...)
|
||||
G_GNUC_PRINTF(3,4);
|
||||
|
||||
|
||||
#define MU_COLOR_RED "\x1b[31m"
|
||||
#define MU_COLOR_GREEN "\x1b[32m"
|
||||
#define MU_COLOR_YELLOW "\x1b[33m"
|
||||
#define MU_COLOR_BLUE "\x1b[34m"
|
||||
#define MU_COLOR_MAGENTA "\x1b[35m"
|
||||
#define MU_COLOR_CYAN "\x1b[36m"
|
||||
#define MU_COLOR_DEFAULT "\x1b[0m"
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__MU_UTIL_H__*/
|
||||
128
lib/tests/Makefile.am
Normal file
128
lib/tests/Makefile.am
Normal file
@ -0,0 +1,128 @@
|
||||
## Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify it
|
||||
## under the terms of the GNU General Public License as published by the
|
||||
## Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
include $(top_srcdir)/gtest.mk
|
||||
|
||||
INCLUDES=$(XAPIAN_CXXFLAGS) \
|
||||
$(GMIME_CFLAGS) \
|
||||
$(GLIB_CFLAGS) \
|
||||
-I ${top_srcdir} \
|
||||
-I ${top_srcdir}/lib \
|
||||
-DMU_TESTMAILDIR=\"${abs_srcdir}/testdir\" \
|
||||
-DMU_TESTMAILDIR2=\"${abs_srcdir}/testdir2\" \
|
||||
-DMU_TESTMAILDIR3=\"${abs_srcdir}/testdir3\" \
|
||||
-DABS_CURDIR=\"${abs_builddir}\" \
|
||||
-DABS_SRCDIR=\"${abs_srcdir}\"
|
||||
|
||||
# don't use -Werror, as it might break on other compilers
|
||||
# use -Wno-unused-parameters, because some callbacks may not
|
||||
# really need all the params they get
|
||||
AM_CFLAGS=-Wall -Wextra -Wno-unused-parameter -Wdeclaration-after-statement
|
||||
AM_CXXFLAGS=-Wall -Wextra -Wno-unused-parameter
|
||||
|
||||
noinst_PROGRAMS= $(TEST_PROGS)
|
||||
|
||||
noinst_LTLIBRARIES=libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-util
|
||||
test_mu_util_SOURCES= test-mu-util.c dummy.cc
|
||||
test_mu_util_LDADD= libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-str
|
||||
test_mu_str_SOURCES= test-mu-str.c dummy.cc
|
||||
test_mu_str_LDADD= libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-maildir
|
||||
test_mu_maildir_SOURCES= test-mu-maildir.c dummy.cc
|
||||
test_mu_maildir_LDADD= libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-msg-fields
|
||||
test_mu_msg_fields_SOURCES= test-mu-msg-fields.c dummy.cc
|
||||
test_mu_msg_fields_LDADD= libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-msg
|
||||
test_mu_msg_SOURCES= test-mu-msg.c dummy.cc
|
||||
test_mu_msg_LDADD= libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-store
|
||||
test_mu_store_SOURCES= test-mu-store.c dummy.cc
|
||||
test_mu_store_LDADD= libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-date
|
||||
test_mu_date_SOURCES= test-mu-date.c dummy.cc
|
||||
test_mu_date_LDADD= libtestmucommon.la
|
||||
|
||||
TEST_PROGS += test-mu-flags
|
||||
test_mu_flags_SOURCES= test-mu-flags.c dummy.cc
|
||||
test_mu_flags_LDADD= libtestmucommon.la
|
||||
|
||||
# we need to use dummy.cc to enforce c++ linking...
|
||||
BUILT_SOURCES= \
|
||||
dummy.cc
|
||||
|
||||
dummy.cc:
|
||||
touch dummy.cc
|
||||
|
||||
|
||||
libtestmucommon_la_SOURCES= \
|
||||
test-mu-common.c \
|
||||
test-mu-common.h
|
||||
libtestmucommon_la_LIBADD= ../libmu.la
|
||||
|
||||
|
||||
|
||||
|
||||
# note the question marks; make does not like files with ':', so we
|
||||
# use the (also supported) version with '!' instead. We could escape
|
||||
# the : with \: but automake does not recognize that....
|
||||
|
||||
# test messages, the '.ignore' message should be ignored
|
||||
# when indexing
|
||||
EXTRA_DIST= \
|
||||
testdir/tmp/1220863087.12663.ignore \
|
||||
testdir/new/1220863087.12663_9.mindcrime \
|
||||
testdir/new/1220863087.12663_25.mindcrime \
|
||||
testdir/new/1220863087.12663_21.mindcrime \
|
||||
testdir/new/1220863087.12663_23.mindcrime \
|
||||
testdir/cur/1220863087.12663_5.mindcrime!2,S \
|
||||
testdir/cur/1220863087.12663_7.mindcrime!2,RS \
|
||||
testdir/cur/1220863087.12663_15.mindcrime!2,PS \
|
||||
testdir/cur/1220863087.12663_19.mindcrime!2,S \
|
||||
testdir/cur/1220863042.12663_1.mindcrime!2,S \
|
||||
testdir/cur/1220863060.12663_3.mindcrime!2,S \
|
||||
testdir/cur/1283599333.1840_11.cthulhu!2, \
|
||||
testdir/cur/1305664394.2171_402.cthulhu!2, \
|
||||
testdir/cur/1252168370_3.14675.cthulhu!2,S \
|
||||
testdir/cur/signed!2,S \
|
||||
testdir/cur/encrypted!2,S \
|
||||
testdir/cur/signed-encrypted!2,S \
|
||||
testdir/cur/multimime!2,FS \
|
||||
testdir2/bar/cur/mail1 \
|
||||
testdir2/bar/cur/mail2 \
|
||||
testdir2/bar/cur/mail3 \
|
||||
testdir2/bar/cur/mail4 \
|
||||
testdir2/bar/cur/mail5 \
|
||||
testdir2/bar/cur/181736.eml \
|
||||
testdir2/bar/cur/mail6 \
|
||||
testdir2/bar/tmp/.noindex \
|
||||
testdir2/bar/new/.noindex \
|
||||
testdir2/Foo/cur/mail5 \
|
||||
testdir2/Foo/cur/arto.eml \
|
||||
testdir2/Foo/tmp/.noindex \
|
||||
testdir2/Foo/new/.noindex \
|
||||
testdir2/wom_bat/cur/atomic \
|
||||
testdir2/wom_bat/cur/rfc822.1 \
|
||||
testdir2/wom_bat/cur/rfc822.2
|
||||
0
lib/tests/dummy.cc
Normal file
0
lib/tests/dummy.cc
Normal file
86
lib/tests/test-mu-common.c
Normal file
86
lib/tests/test-mu-common.c
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2012 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <langinfo.h>
|
||||
#include <locale.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
|
||||
char*
|
||||
test_mu_common_get_random_tmpdir (void)
|
||||
{
|
||||
return g_strdup_printf ("%s%cmu-test-%d%ctest-%x", g_get_tmp_dir(),
|
||||
G_DIR_SEPARATOR,
|
||||
getuid(),
|
||||
G_DIR_SEPARATOR,
|
||||
(int)random()*getpid()*(int)time(NULL));
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
set_tz (const char* tz)
|
||||
{
|
||||
static const char* oldtz;
|
||||
|
||||
oldtz = getenv ("TZ");
|
||||
if (tz)
|
||||
setenv ("TZ", tz, 1);
|
||||
else
|
||||
unsetenv ("TZ");
|
||||
|
||||
tzset ();
|
||||
return oldtz;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
set_en_us_utf8_locale (void)
|
||||
{
|
||||
setenv ("LC_ALL", "en_US.utf8", 1);
|
||||
setlocale (LC_ALL, "en_US.utf8");
|
||||
|
||||
if (strcmp (nl_langinfo(CODESET), "UTF-8") != 0) {
|
||||
g_print ("Note: Unit tests require the en_US.utf8 locale. "
|
||||
"Ignoring test cases.\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void
|
||||
black_hole (void)
|
||||
{
|
||||
return; /* do nothing */
|
||||
}
|
||||
61
lib/tests/test-mu-common.h
Normal file
61
lib/tests/test-mu-common.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2012 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef __TEST_MU_COMMON_H__
|
||||
#define __TEST_MU_COMMON_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* get a dir name for a random temporary directory to do tests
|
||||
*
|
||||
* @return a random dir name, g_free when it's no longer needed
|
||||
*/
|
||||
char* test_mu_common_get_random_tmpdir (void);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* set the output to /dev/null
|
||||
*
|
||||
*/
|
||||
void black_hole (void);
|
||||
|
||||
/**
|
||||
* set the timezone
|
||||
*
|
||||
* @param tz timezone
|
||||
*
|
||||
* @return the old timezone
|
||||
*/
|
||||
const char* set_tz (const char* tz);
|
||||
|
||||
|
||||
/**
|
||||
* switch the locale to en_US.utf8, return TRUE if it succeeds
|
||||
*
|
||||
* @return TRUE if the switch succeeds, FALSE otherwise
|
||||
*/
|
||||
gboolean set_en_us_utf8_locale (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /*__TEST_MU_COMMON_H__*/
|
||||
189
lib/tests/test-mu-date.c
Normal file
189
lib/tests/test-mu-date.c
Normal file
@ -0,0 +1,189 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
#include "mu-date.h"
|
||||
|
||||
|
||||
static void
|
||||
test_mu_date_str (void)
|
||||
{
|
||||
struct tm *tmbuf;
|
||||
char buf[64];
|
||||
gchar *tmp;
|
||||
time_t some_time;
|
||||
|
||||
some_time = 1234567890;
|
||||
tmbuf = localtime (&some_time);
|
||||
strftime (buf, 64, "%x", tmbuf);
|
||||
|
||||
/* $ date -ud@1234567890; Fri Feb 13 23:31:30 UTC 2009 */
|
||||
g_assert_cmpstr (mu_date_str_s ("%x", some_time), ==, buf);
|
||||
|
||||
/* date -ud@987654321 Thu Apr 19 04:25:21 UTC 2001 */
|
||||
some_time = 987654321;
|
||||
tmbuf = localtime (&some_time);
|
||||
strftime (buf, 64, "%c", tmbuf);
|
||||
tmp = mu_date_str ("%c", some_time);
|
||||
|
||||
g_assert_cmpstr (tmp, ==, buf);
|
||||
g_free (tmp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_date_parse_hdwmy (void)
|
||||
{
|
||||
time_t diff;
|
||||
|
||||
diff = time(NULL) - mu_date_parse_hdwmy ("3h");
|
||||
g_assert (diff > 0);
|
||||
g_assert_cmpuint (3 * 60 * 60 - diff, <=, 1);
|
||||
|
||||
diff = time(NULL) - mu_date_parse_hdwmy ("5y");
|
||||
g_assert (diff > 0);
|
||||
g_assert_cmpuint (5 * 365 * 24 * 60 * 60 - diff, <=, 1);
|
||||
|
||||
diff = time(NULL) - mu_date_parse_hdwmy ("3m");
|
||||
g_assert (diff > 0);
|
||||
g_assert_cmpuint (3 * 30 * 24 * 60 * 60 - diff, <=, 1);
|
||||
|
||||
diff = time(NULL) - mu_date_parse_hdwmy ("21d");
|
||||
g_assert (diff > 0);
|
||||
g_assert_cmpuint (21 * 24 * 60 * 60 - diff, <=, 1);
|
||||
|
||||
diff = time(NULL) - mu_date_parse_hdwmy ("2w");
|
||||
g_assert (diff > 0);
|
||||
g_assert_cmpuint (2 * 7 * 24 * 60 * 60 - diff, <=, 1);
|
||||
|
||||
|
||||
g_assert_cmpint (mu_date_parse_hdwmy("-1y"),==, (time_t)-1);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_date_complete_begin (void)
|
||||
{
|
||||
g_assert_cmpstr (mu_date_complete_s("2000", TRUE), ==,
|
||||
"20000101000000");
|
||||
g_assert_cmpstr (mu_date_complete_s("2", TRUE), ==,
|
||||
"20000101000000");
|
||||
g_assert_cmpstr (mu_date_complete_s ("", TRUE), ==,
|
||||
"00000101000000");
|
||||
g_assert_cmpstr (mu_date_complete_s ("201007", TRUE), ==,
|
||||
"20100701000000");
|
||||
g_assert_cmpstr (mu_date_complete_s ("19721214", TRUE), ==,
|
||||
"19721214000000");
|
||||
g_assert_cmpstr (mu_date_complete_s ("19721214234512", TRUE), ==,
|
||||
"19721214234512");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_date_complete_end (void)
|
||||
{
|
||||
g_assert_cmpstr (mu_date_complete_s ("2000", FALSE), ==,
|
||||
"20001231235959");
|
||||
g_assert_cmpstr (mu_date_complete_s ("2", FALSE), ==,
|
||||
"29991231235959");
|
||||
g_assert_cmpstr (mu_date_complete_s ("", FALSE), ==,
|
||||
"99991231235959");
|
||||
g_assert_cmpstr (mu_date_complete_s ("201007", FALSE), ==,
|
||||
"20100731235959");
|
||||
g_assert_cmpstr (mu_date_complete_s ("19721214", FALSE), ==,
|
||||
"19721214235959");
|
||||
g_assert_cmpstr (mu_date_complete_s ("19721214234512", FALSE), ==,
|
||||
"19721214234512");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_date_interpret_begin (void)
|
||||
{
|
||||
time_t now;
|
||||
now = time (NULL);
|
||||
|
||||
g_assert_cmpstr (mu_date_interpret_s ("now", TRUE) , ==,
|
||||
mu_date_str_s("%Y%m%d%H%M%S", now));
|
||||
|
||||
g_assert_cmpstr (mu_date_interpret_s ("today", TRUE) , ==,
|
||||
mu_date_str_s("%Y%m%d000000", now));
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_date_interpret_end (void)
|
||||
{
|
||||
time_t now;
|
||||
now = time (NULL);
|
||||
|
||||
g_assert_cmpstr (mu_date_interpret_s ("now", FALSE) , ==,
|
||||
mu_date_str_s("%Y%m%d%H%M%S", now));
|
||||
|
||||
g_assert_cmpstr (mu_date_interpret_s ("today", FALSE) , ==,
|
||||
mu_date_str_s("%Y%m%d235959", now));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* mu_str_date */
|
||||
g_test_add_func ("/mu-str/mu-date-str",
|
||||
test_mu_date_str);
|
||||
|
||||
g_test_add_func ("/mu-str/mu_date_parse_hdwmy",
|
||||
test_mu_date_parse_hdwmy);
|
||||
g_test_add_func ("/mu-str/mu_date_complete_begin",
|
||||
test_mu_date_complete_begin);
|
||||
g_test_add_func ("/mu-str/mu_date_complete_end",
|
||||
test_mu_date_complete_end);
|
||||
|
||||
g_test_add_func ("/mu-str/mu_date_interpret_begin",
|
||||
test_mu_date_interpret_begin);
|
||||
g_test_add_func ("/mu-str/mu_date_interpret_end",
|
||||
test_mu_date_interpret_end);
|
||||
|
||||
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,
|
||||
(GLogFunc)black_hole, NULL);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
160
lib/tests/test-mu-flags.c
Normal file
160
lib/tests/test-mu-flags.c
Normal file
@ -0,0 +1,160 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include "mu-flags.h"
|
||||
#include "test-mu-common.h"
|
||||
|
||||
|
||||
static void
|
||||
test_mu_flag_char (void)
|
||||
{
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_DRAFT), ==, 'D');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_FLAGGED), ==, 'F');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_PASSED), ==, 'P');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_REPLIED), ==, 'R');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_SEEN), ==, 'S');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_TRASHED), ==, 'T');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_NEW), ==, 'N');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_SIGNED), ==, 'z');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_ENCRYPTED), ==, 'x');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_HAS_ATTACH), ==, 'a');
|
||||
g_assert_cmpuint (mu_flag_char (MU_FLAG_UNREAD), ==, 'u');
|
||||
g_assert_cmpuint (mu_flag_char (12345), ==, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_flag_name (void)
|
||||
{
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_DRAFT), ==, "draft");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_FLAGGED), ==, "flagged");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_PASSED), ==, "passed");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_REPLIED), ==, "replied");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_SEEN), ==, "seen");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_TRASHED), ==, "trashed");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_NEW), ==, "new");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_SIGNED), ==, "signed");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_ENCRYPTED), ==, "encrypted");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_HAS_ATTACH), ==, "attach");
|
||||
g_assert_cmpstr (mu_flag_name (MU_FLAG_UNREAD), ==, "unread");
|
||||
g_assert_cmpstr (mu_flag_name (12345), ==, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_flags_to_str_s (void)
|
||||
{
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_PASSED|MU_FLAG_SIGNED,
|
||||
MU_FLAG_TYPE_ANY),
|
||||
==, "Pz");
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NEW, MU_FLAG_TYPE_ANY),
|
||||
==, "N");
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_HAS_ATTACH|MU_FLAG_TRASHED,
|
||||
MU_FLAG_TYPE_ANY),
|
||||
==, "Ta");
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NONE, MU_FLAG_TYPE_ANY),
|
||||
==, "");
|
||||
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_PASSED|MU_FLAG_SIGNED,
|
||||
MU_FLAG_TYPE_CONTENT),
|
||||
==, "z");
|
||||
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NEW, MU_FLAG_TYPE_MAILDIR),
|
||||
==, "N");
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_HAS_ATTACH|MU_FLAG_TRASHED,
|
||||
MU_FLAG_TYPE_MAILFILE),
|
||||
==, "T");
|
||||
|
||||
g_assert_cmpstr (mu_flags_to_str_s(MU_FLAG_NONE, MU_FLAG_TYPE_PSEUDO),
|
||||
==, "");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_flags_from_str (void)
|
||||
{
|
||||
g_assert_cmpuint (mu_flags_from_str ("RP", MU_FLAG_TYPE_ANY), ==,
|
||||
MU_FLAG_REPLIED | MU_FLAG_PASSED);
|
||||
g_assert_cmpuint (mu_flags_from_str ("Nz", MU_FLAG_TYPE_ANY), ==,
|
||||
MU_FLAG_NEW | MU_FLAG_SIGNED);
|
||||
g_assert_cmpuint (mu_flags_from_str ("axD", MU_FLAG_TYPE_ANY), ==,
|
||||
MU_FLAG_HAS_ATTACH | MU_FLAG_ENCRYPTED | MU_FLAG_DRAFT);
|
||||
|
||||
g_assert_cmpuint (mu_flags_from_str ("RP", MU_FLAG_TYPE_MAILFILE), ==,
|
||||
MU_FLAG_REPLIED | MU_FLAG_PASSED);
|
||||
g_assert_cmpuint (mu_flags_from_str ("Nz", MU_FLAG_TYPE_MAILFILE), ==,
|
||||
MU_FLAG_NONE);
|
||||
|
||||
g_assert_cmpuint (mu_flags_from_str ("qwi", MU_FLAG_TYPE_MAILFILE), ==,
|
||||
MU_FLAG_INVALID);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_flags_from_str_delta (void)
|
||||
{
|
||||
g_assert_cmpuint (mu_flags_from_str_delta ("+S-R",
|
||||
MU_FLAG_REPLIED | MU_FLAG_DRAFT,
|
||||
MU_FLAG_TYPE_ANY),==,
|
||||
MU_FLAG_SEEN | MU_FLAG_DRAFT);
|
||||
|
||||
g_assert_cmpuint (mu_flags_from_str_delta ("",
|
||||
MU_FLAG_REPLIED | MU_FLAG_DRAFT,
|
||||
MU_FLAG_TYPE_ANY),==,
|
||||
MU_FLAG_REPLIED | MU_FLAG_DRAFT);
|
||||
|
||||
g_assert_cmpuint (mu_flags_from_str_delta ("-N+P+S-D",
|
||||
MU_FLAG_SIGNED | MU_FLAG_DRAFT,
|
||||
MU_FLAG_TYPE_ANY),==,
|
||||
MU_FLAG_PASSED | MU_FLAG_SEEN | MU_FLAG_SIGNED);
|
||||
|
||||
/* g_assert_cmpuint (mu_flags_from_str_delta ("foobar", */
|
||||
/* MU_FLAG_INVALID, */
|
||||
/* MU_FLAG_TYPE_ANY),==, */
|
||||
/* MU_FLAG_INVALID); */
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
int rv;
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* mu_msg_str_date */
|
||||
g_test_add_func ("/mu-flags/test-mu-flag-char", test_mu_flag_char);
|
||||
g_test_add_func ("/mu-flags/test-mu-flag-name",test_mu_flag_name);
|
||||
g_test_add_func ("/mu-flags/test-mu-flags-to-str-s",test_mu_flags_to_str_s);
|
||||
g_test_add_func ("/mu-flags/test-mu-flags-from-str",test_mu_flags_from_str);
|
||||
g_test_add_func ("/mu-flags/test-mu-flags-from-str-delta",test_mu_flags_from_str_delta );
|
||||
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,
|
||||
(GLogFunc)black_hole, NULL);
|
||||
|
||||
rv = g_test_run ();
|
||||
|
||||
return rv;
|
||||
}
|
||||
502
lib/tests/test-mu-maildir.c
Normal file
502
lib/tests/test-mu-maildir.c
Normal file
@ -0,0 +1,502 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
#include "mu-maildir.h"
|
||||
#include "mu-util.h"
|
||||
|
||||
static void
|
||||
test_mu_maildir_mkdir_01 (void)
|
||||
{
|
||||
int i;
|
||||
gchar *tmpdir, *mdir, *tmp;
|
||||
const gchar *subs[] = {"tmp", "cur", "new"};
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir ();
|
||||
mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR,
|
||||
"cuux");
|
||||
|
||||
g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, FALSE, NULL),
|
||||
==, TRUE);
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(subs); ++i) {
|
||||
gchar* dir;
|
||||
|
||||
dir = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR,
|
||||
subs[i]);
|
||||
g_assert_cmpuint (g_access (dir, R_OK), ==, 0);
|
||||
g_assert_cmpuint (g_access (dir, W_OK), ==, 0);
|
||||
g_free (dir);
|
||||
}
|
||||
|
||||
tmp = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, ".noindex");
|
||||
g_assert_cmpuint (g_access (tmp, F_OK), !=, 0);
|
||||
|
||||
g_free (tmp);
|
||||
g_free (tmpdir);
|
||||
g_free (mdir);
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_mkdir_02 (void)
|
||||
{
|
||||
int i;
|
||||
gchar *tmpdir, *mdir, *tmp;
|
||||
const gchar *subs[] = {"tmp", "cur", "new"};
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir ();
|
||||
mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR,
|
||||
"cuux");
|
||||
|
||||
g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, TRUE, NULL),
|
||||
==, TRUE);
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(subs); ++i) {
|
||||
gchar* dir;
|
||||
|
||||
dir = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR,
|
||||
subs[i]);
|
||||
g_assert_cmpuint (g_access (dir, R_OK), ==, 0);
|
||||
|
||||
g_assert_cmpuint (g_access (dir, W_OK), ==, 0);
|
||||
g_free (dir);
|
||||
}
|
||||
|
||||
tmp = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, ".noindex");
|
||||
g_assert_cmpuint (g_access (tmp, F_OK), ==, 0);
|
||||
|
||||
g_free (tmp);
|
||||
g_free (tmpdir);
|
||||
g_free (mdir);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_mkdir_03 (void)
|
||||
{
|
||||
int i;
|
||||
gchar *tmpdir, *mdir, *tmp;
|
||||
const gchar *subs[] = {"tmp", "cur", "new"};
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir ();
|
||||
mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR,
|
||||
"cuux");
|
||||
|
||||
/* create part of the structure already... */
|
||||
{
|
||||
gchar *dir;
|
||||
dir = g_strdup_printf ("%s%ccur", mdir, G_DIR_SEPARATOR);
|
||||
g_assert_cmpuint (g_mkdir_with_parents (dir, 0755), ==, 0);
|
||||
g_free (dir);
|
||||
}
|
||||
|
||||
/* this should still work */
|
||||
g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, FALSE, NULL),
|
||||
==, TRUE);
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(subs); ++i) {
|
||||
gchar* dir;
|
||||
|
||||
dir = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR,
|
||||
subs[i]);
|
||||
g_assert_cmpuint (g_access (dir, R_OK), ==, 0);
|
||||
g_assert_cmpuint (g_access (dir, W_OK), ==, 0);
|
||||
g_free (dir);
|
||||
}
|
||||
|
||||
tmp = g_strdup_printf ("%s%c%s", mdir, G_DIR_SEPARATOR, ".noindex");
|
||||
g_assert_cmpuint (g_access (tmp, F_OK), !=, 0);
|
||||
|
||||
g_free (tmp);
|
||||
g_free (tmpdir);
|
||||
g_free (mdir);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_mkdir_04 (void)
|
||||
{
|
||||
gchar *tmpdir, *mdir;
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir ();
|
||||
mdir = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR,
|
||||
"cuux");
|
||||
|
||||
/* create part of the structure already... */
|
||||
{
|
||||
gchar *dir;
|
||||
g_assert_cmpuint (g_mkdir_with_parents (mdir, 0755), ==, 0);
|
||||
dir = g_strdup_printf ("%s%ccur", mdir, G_DIR_SEPARATOR);
|
||||
g_assert_cmpuint (g_mkdir_with_parents (dir, 0000), ==, 0);
|
||||
g_free (dir);
|
||||
}
|
||||
|
||||
/* this should fail now, because cur is not read/writable */
|
||||
g_assert_cmpuint (mu_maildir_mkdir (mdir, 0755, FALSE, NULL),
|
||||
==, FALSE);
|
||||
g_free (tmpdir);
|
||||
g_free (mdir);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
ignore_error (const char* log_domain, GLogLevelFlags log_level, const gchar* msg,
|
||||
gpointer user_data)
|
||||
{
|
||||
return FALSE; /* don't abort */
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_maildir_mkdir_05 (void)
|
||||
{
|
||||
/* this must fail */
|
||||
g_test_log_set_fatal_handler ((GTestLogFatalFunc)ignore_error, NULL);
|
||||
|
||||
g_assert_cmpuint (mu_maildir_mkdir (NULL, 0755, TRUE, NULL),
|
||||
==, FALSE);
|
||||
}
|
||||
|
||||
|
||||
static gchar*
|
||||
copy_test_data (void)
|
||||
{
|
||||
gchar *dir, *cmd;
|
||||
|
||||
dir = test_mu_common_get_random_tmpdir();
|
||||
cmd = g_strdup_printf ("mkdir -m 0700 %s", dir);
|
||||
if (g_test_verbose())
|
||||
g_print ("cmd: %s\n", cmd);
|
||||
g_assert (g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL));
|
||||
g_free (cmd);
|
||||
|
||||
cmd = g_strdup_printf ("cp -R %s %s", MU_TESTMAILDIR, dir);
|
||||
if (g_test_verbose())
|
||||
g_print ("cmd: %s\n", cmd);
|
||||
g_assert (g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL));
|
||||
g_free (cmd);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
int _file_count;
|
||||
int _dir_entered;
|
||||
int _dir_left;
|
||||
} WalkData;
|
||||
|
||||
static MuError
|
||||
dir_cb (const char *fullpath, gboolean enter, WalkData *data)
|
||||
{
|
||||
if (enter)
|
||||
++data->_dir_entered;
|
||||
else
|
||||
++data->_dir_left;
|
||||
|
||||
if (g_test_verbose())
|
||||
g_print ("%s: %s: %s (%u)\n", __FUNCTION__, enter ? "entering" : "leaving",
|
||||
fullpath, enter ? data->_dir_entered : data->_dir_left);
|
||||
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
|
||||
static MuError
|
||||
msg_cb (const char *fullpath, const char* mdir, struct stat *statinfo,
|
||||
WalkData *data)
|
||||
{
|
||||
++data->_file_count;
|
||||
return MU_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_walk_01 (void)
|
||||
{
|
||||
char *tmpdir;
|
||||
WalkData data;
|
||||
MuError rv;
|
||||
|
||||
tmpdir = copy_test_data ();
|
||||
memset (&data, 0, sizeof(WalkData));
|
||||
|
||||
rv = mu_maildir_walk (tmpdir,
|
||||
(MuMaildirWalkMsgCallback)msg_cb,
|
||||
(MuMaildirWalkDirCallback)dir_cb,
|
||||
&data);
|
||||
|
||||
g_assert_cmpuint (MU_OK, ==, rv);
|
||||
g_assert_cmpuint (data._file_count, ==, 17);
|
||||
|
||||
g_assert_cmpuint (data._dir_entered,==, 5);
|
||||
g_assert_cmpuint (data._dir_left,==, 5);
|
||||
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_walk_02 (void)
|
||||
{
|
||||
char *tmpdir, *cmd, *dir;
|
||||
WalkData data;
|
||||
MuError rv;
|
||||
|
||||
tmpdir = copy_test_data ();
|
||||
memset (&data, 0, sizeof(WalkData));
|
||||
|
||||
/* mark the 'new' dir with '.noindex', to ignore it */
|
||||
dir = g_strdup_printf ("%s%ctestdir%cnew", tmpdir,
|
||||
G_DIR_SEPARATOR, G_DIR_SEPARATOR);
|
||||
cmd = g_strdup_printf ("chmod 700 %s", dir);
|
||||
g_assert (g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL));
|
||||
g_free (cmd);
|
||||
|
||||
cmd = g_strdup_printf ("touch %s%c.noindex", dir, G_DIR_SEPARATOR);
|
||||
g_assert (g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL));
|
||||
|
||||
g_free (cmd);
|
||||
g_free (dir);
|
||||
|
||||
rv = mu_maildir_walk (tmpdir,
|
||||
(MuMaildirWalkMsgCallback)msg_cb,
|
||||
(MuMaildirWalkDirCallback)dir_cb,
|
||||
&data);
|
||||
|
||||
g_assert_cmpuint (MU_OK, ==, rv);
|
||||
g_assert_cmpuint (data._file_count, ==, 13);
|
||||
|
||||
g_assert_cmpuint (data._dir_entered,==, 4);
|
||||
g_assert_cmpuint (data._dir_left,==, 4);
|
||||
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_get_flags_from_path (void)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char *path;
|
||||
MuFlags flags;
|
||||
} paths[] = {
|
||||
{
|
||||
"/home/foo/Maildir/test/cur/123456:2,FSR",
|
||||
MU_FLAG_REPLIED | MU_FLAG_SEEN | MU_FLAG_FLAGGED
|
||||
},
|
||||
{
|
||||
"/home/foo/Maildir/test/new/123456",
|
||||
MU_FLAG_NEW
|
||||
},
|
||||
{
|
||||
/* NOTE: when in new/, the :2,.. stuff is ignored */
|
||||
"/home/foo/Maildir/test/new/123456:2,FR",
|
||||
MU_FLAG_NEW
|
||||
},
|
||||
{
|
||||
"/home/foo/Maildir/test/cur/123456:2,DTP",
|
||||
MU_FLAG_DRAFT | MU_FLAG_TRASHED |
|
||||
MU_FLAG_PASSED
|
||||
},
|
||||
{
|
||||
"/home/foo/Maildir/test/cur/123456:2,S",
|
||||
MU_FLAG_SEEN
|
||||
}
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(paths); ++i) {
|
||||
MuFlags flags;
|
||||
flags = mu_maildir_get_flags_from_path(paths[i].path);
|
||||
g_assert_cmpuint(flags, ==, paths[i].flags);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_maildir_get_new_path_01 (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
struct {
|
||||
const char *oldpath;
|
||||
MuFlags flags;
|
||||
const char *newpath;
|
||||
} paths[] = {
|
||||
{
|
||||
"/home/foo/Maildir/test/cur/123456:2,FR",
|
||||
MU_FLAG_REPLIED,
|
||||
"/home/foo/Maildir/test/cur/123456:2,R"
|
||||
}, {
|
||||
"/home/foo/Maildir/test/cur/123456:2,FR",
|
||||
MU_FLAG_NEW,
|
||||
"/home/foo/Maildir/test/new/123456"
|
||||
}, {
|
||||
"/home/foo/Maildir/test/new/123456:2,FR",
|
||||
MU_FLAG_SEEN | MU_FLAG_REPLIED,
|
||||
"/home/foo/Maildir/test/cur/123456:2,RS"
|
||||
}, {
|
||||
"/home/foo/Maildir/test/new/1313038887_0.697:2,",
|
||||
MU_FLAG_SEEN | MU_FLAG_FLAGGED | MU_FLAG_PASSED,
|
||||
"/home/foo/Maildir/test/cur/1313038887_0.697:2,FPS"
|
||||
}, {
|
||||
"/home/djcb/Maildir/trash/new/1312920597.2206_16.cthulhu",
|
||||
MU_FLAG_SEEN,
|
||||
"/home/djcb/Maildir/trash/cur/1312920597.2206_16.cthulhu:2,S"
|
||||
}
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(paths); ++i) {
|
||||
gchar *str;
|
||||
str = mu_maildir_get_new_path(paths[i].oldpath, NULL,
|
||||
paths[i].flags);
|
||||
g_assert_cmpstr(str, ==, paths[i].newpath);
|
||||
g_free(str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_get_new_path_02 (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
struct {
|
||||
const char *oldpath;
|
||||
MuFlags flags;
|
||||
const char *targetdir;
|
||||
const char *newpath;
|
||||
} paths[] = {
|
||||
{
|
||||
"/home/foo/Maildir/test/cur/123456:2,FR",
|
||||
MU_FLAG_REPLIED, "/home/foo/Maildir/blabla",
|
||||
"/home/foo/Maildir/blabla/cur/123456:2,R"
|
||||
}, {
|
||||
"/home/foo/Maildir/test/cur/123456:2,FR",
|
||||
MU_FLAG_NEW, "/home/bar/Maildir/coffee",
|
||||
"/home/bar/Maildir/coffee/new/123456"
|
||||
}, {
|
||||
"/home/foo/Maildir/test/new/123456",
|
||||
MU_FLAG_SEEN | MU_FLAG_REPLIED,
|
||||
"/home/cuux/Maildir/tea",
|
||||
"/home/cuux/Maildir/tea/cur/123456:2,RS"
|
||||
}, {
|
||||
"/home/foo/Maildir/test/new/1313038887_0.697:2,",
|
||||
MU_FLAG_SEEN | MU_FLAG_FLAGGED | MU_FLAG_PASSED,
|
||||
"/home/boy/Maildir/stuff",
|
||||
"/home/boy/Maildir/stuff/cur/1313038887_0.697:2,FPS"
|
||||
}
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(paths); ++i) {
|
||||
gchar *str;
|
||||
str = mu_maildir_get_new_path(paths[i].oldpath,
|
||||
paths[i].targetdir,
|
||||
paths[i].flags);
|
||||
g_assert_cmpstr(str, ==, paths[i].newpath);
|
||||
g_free(str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_maildir_get_maildir_from_path (void)
|
||||
{
|
||||
unsigned u;
|
||||
|
||||
struct {
|
||||
const char *path, *exp;
|
||||
} cases[] = {
|
||||
{"/home/foo/Maildir/test/cur/123456:2,FR",
|
||||
"/home/foo/Maildir/test"},
|
||||
{"/home/foo/Maildir/lala/new/1313038887_0.697:2,",
|
||||
"/home/foo/Maildir/lala"}
|
||||
};
|
||||
|
||||
|
||||
for (u = 0; u != G_N_ELEMENTS(cases); ++u) {
|
||||
gchar *mdir;
|
||||
mdir = mu_maildir_get_maildir_from_path (cases[u].path);
|
||||
g_assert_cmpstr(mdir,==,cases[u].exp);
|
||||
g_free (mdir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* mu_util_maildir_mkmdir */
|
||||
g_test_add_func ("/mu-maildir/mu-maildir-mkdir-01",
|
||||
test_mu_maildir_mkdir_01);
|
||||
g_test_add_func ("/mu-maildir/mu-maildir-mkdir-02",
|
||||
test_mu_maildir_mkdir_02);
|
||||
g_test_add_func ("/mu-maildir/mu-maildir-mkdir-03",
|
||||
test_mu_maildir_mkdir_03);
|
||||
g_test_add_func ("/mu-maildir/mu-maildir-mkdir-04",
|
||||
test_mu_maildir_mkdir_04);
|
||||
g_test_add_func ("/mu-maildir/mu-maildir-mkdir-05",
|
||||
test_mu_maildir_mkdir_05);
|
||||
|
||||
|
||||
/* mu_util_maildir_walk */
|
||||
g_test_add_func ("/mu-maildir/mu-maildir-walk-01",
|
||||
test_mu_maildir_walk_01);
|
||||
g_test_add_func ("/mu-maildir/mu-maildir-walk-02",
|
||||
test_mu_maildir_walk_02);
|
||||
|
||||
/* get/set flags */
|
||||
g_test_add_func("/mu-maildir/mu-maildir-get-new-path-01",
|
||||
test_mu_maildir_get_new_path_01);
|
||||
g_test_add_func("/mu-maildir/mu-maildir-get-new-path-02",
|
||||
test_mu_maildir_get_new_path_02);
|
||||
g_test_add_func("/mu-maildir/mu-maildir-get-flags-from-path",
|
||||
test_mu_maildir_get_flags_from_path);
|
||||
|
||||
|
||||
g_test_add_func("/mu-maildir/mu-maildir-get-maildir-from-path",
|
||||
test_mu_maildir_get_maildir_from_path);
|
||||
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL|
|
||||
G_LOG_FLAG_RECURSION,
|
||||
(GLogFunc)black_hole, NULL);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
134
lib/tests/test-mu-msg-fields.c
Normal file
134
lib/tests/test-mu-msg-fields.c
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
#include "mu-msg-fields.h"
|
||||
|
||||
static void
|
||||
test_mu_msg_field_body (void)
|
||||
{
|
||||
MuMsgFieldId field;
|
||||
|
||||
field = MU_MSG_FIELD_ID_BODY_TEXT;
|
||||
|
||||
g_assert_cmpstr (mu_msg_field_name(field),==, "body");
|
||||
g_assert_cmpuint (mu_msg_field_shortcut(field),==, 'b');
|
||||
g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'B');
|
||||
|
||||
g_assert_cmpuint (mu_msg_field_is_numeric(field), ==, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_msg_field_subject (void)
|
||||
{
|
||||
MuMsgFieldId field;
|
||||
|
||||
field = MU_MSG_FIELD_ID_SUBJECT;
|
||||
|
||||
g_assert_cmpstr (mu_msg_field_name(field),==, "subject");
|
||||
g_assert_cmpuint (mu_msg_field_shortcut(field),==, 's');
|
||||
g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'S');
|
||||
|
||||
g_assert_cmpuint (mu_msg_field_is_numeric(field), ==,FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_msg_field_to (void)
|
||||
{
|
||||
MuMsgFieldId field;
|
||||
|
||||
field = MU_MSG_FIELD_ID_TO;
|
||||
|
||||
g_assert_cmpstr (mu_msg_field_name(field),==, "to");
|
||||
g_assert_cmpuint (mu_msg_field_shortcut(field),==, 't');
|
||||
g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'T');
|
||||
|
||||
g_assert_cmpuint (mu_msg_field_is_numeric(field), ==, FALSE);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_field_prio (void)
|
||||
{
|
||||
MuMsgFieldId field;
|
||||
|
||||
field = MU_MSG_FIELD_ID_PRIO;
|
||||
|
||||
g_assert_cmpstr (mu_msg_field_name(field),==, "prio");
|
||||
g_assert_cmpuint (mu_msg_field_shortcut(field),==, 'p');
|
||||
g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'P');
|
||||
|
||||
g_assert_cmpuint (mu_msg_field_is_numeric(field), ==, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_msg_field_flags (void)
|
||||
{
|
||||
MuMsgFieldId field;
|
||||
|
||||
field = MU_MSG_FIELD_ID_FLAGS;
|
||||
|
||||
g_assert_cmpstr (mu_msg_field_name(field),==, "flag");
|
||||
g_assert_cmpuint (mu_msg_field_shortcut(field),==, 'g');
|
||||
g_assert_cmpuint (mu_msg_field_xapian_prefix(field),==, 'G');
|
||||
|
||||
g_assert_cmpuint (mu_msg_field_is_numeric(field),==, TRUE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* mu_msg_str_date */
|
||||
g_test_add_func ("/mu-msg-fields/mu-msg-field-body",
|
||||
test_mu_msg_field_body);
|
||||
g_test_add_func ("/mu-msg-fields/mu-msg-field-subject",
|
||||
test_mu_msg_field_subject);
|
||||
g_test_add_func ("/mu-msg-fields/mu-msg-field-to",
|
||||
test_mu_msg_field_to);
|
||||
g_test_add_func ("/mu-msg-fields/mu-msg-field-prio",
|
||||
test_mu_msg_field_prio);
|
||||
g_test_add_func ("/mu-msg-fields/mu-msg-field-flags",
|
||||
test_mu_msg_field_flags);
|
||||
|
||||
/* FIXME: add tests for mu_msg_str_flags; but note the
|
||||
* function simply calls mu_msg_field_str */
|
||||
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL |
|
||||
G_LOG_FLAG_RECURSION,
|
||||
(GLogFunc)black_hole, NULL);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
436
lib/tests/test-mu-msg.c
Normal file
436
lib/tests/test-mu-msg.c
Normal file
@ -0,0 +1,436 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
#include "mu-msg.h"
|
||||
#include "mu-str.h"
|
||||
|
||||
static gboolean
|
||||
check_contact_01 (MuMsgContact *contact, int *idx)
|
||||
{
|
||||
switch (*idx) {
|
||||
case 0:
|
||||
g_assert_cmpstr (mu_msg_contact_name (contact),
|
||||
==, "Mickey Mouse");
|
||||
g_assert_cmpstr (mu_msg_contact_address (contact),
|
||||
==, "anon@example.com");
|
||||
break;
|
||||
case 1:
|
||||
g_assert_cmpstr (mu_msg_contact_name (contact),
|
||||
==, "Donald Duck");
|
||||
g_assert_cmpstr (mu_msg_contact_address (contact),
|
||||
==, "gcc-help@gcc.gnu.org");
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
++(*idx);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_01 (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
gint i;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR
|
||||
"/cur/1220863042.12663_1.mindcrime!2,S",
|
||||
NULL, NULL);
|
||||
|
||||
g_assert_cmpstr (mu_msg_get_to(msg),
|
||||
==, "Donald Duck <gcc-help@gcc.gnu.org>");
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "gcc include search order");
|
||||
g_assert_cmpstr (mu_msg_get_from(msg),
|
||||
==, "Mickey Mouse <anon@example.com>");
|
||||
g_assert_cmpstr (mu_msg_get_msgid(msg),
|
||||
==, "3BE9E6535E3029448670913581E7A1A20D852173@"
|
||||
"emss35m06.us.lmco.com");
|
||||
g_assert_cmpstr (mu_msg_get_header(msg, "Mailing-List"),
|
||||
==,
|
||||
"contact gcc-help-help@gcc.gnu.org; run by ezmlm");
|
||||
g_assert_cmpuint (mu_msg_get_prio(msg), /* 'klub' */
|
||||
==, MU_MSG_PRIO_NORMAL);
|
||||
g_assert_cmpuint (mu_msg_get_date(msg),
|
||||
==, 1217530645);
|
||||
|
||||
i = 0;
|
||||
mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)check_contact_01,
|
||||
&i);
|
||||
g_assert_cmpint (i,==,2);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static gboolean
|
||||
check_contact_02 (MuMsgContact *contact, int *idx)
|
||||
{
|
||||
switch (*idx) {
|
||||
case 0:
|
||||
g_assert_cmpstr (mu_msg_contact_name (contact),
|
||||
==, NULL);
|
||||
g_assert_cmpstr (mu_msg_contact_address (contact),
|
||||
==, "anon@example.com");
|
||||
break;
|
||||
case 1:
|
||||
g_assert_cmpstr (mu_msg_contact_name (contact),
|
||||
==, NULL);
|
||||
g_assert_cmpstr (mu_msg_contact_address (contact),
|
||||
==, "help-gnu-emacs@gnu.org");
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
++(*idx);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_02 (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
int i;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR
|
||||
"/cur/1220863087.12663_19.mindcrime!2,S",
|
||||
NULL, NULL);
|
||||
|
||||
g_assert_cmpstr (mu_msg_get_to(msg),
|
||||
==, "help-gnu-emacs@gnu.org");
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "Re: Learning LISP; Scheme vs elisp.");
|
||||
g_assert_cmpstr (mu_msg_get_from(msg),
|
||||
==, "anon@example.com");
|
||||
g_assert_cmpstr (mu_msg_get_msgid(msg),
|
||||
==, "r6bpm5-6n6.ln1@news.ducksburg.com");
|
||||
g_assert_cmpstr (mu_msg_get_header(msg, "Errors-To"),
|
||||
==, "help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org");
|
||||
g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */
|
||||
==, MU_MSG_PRIO_LOW);
|
||||
g_assert_cmpuint (mu_msg_get_date(msg),
|
||||
==, 1218051515);
|
||||
|
||||
i = 0;
|
||||
mu_msg_contact_foreach (msg,
|
||||
(MuMsgContactForeachFunc)check_contact_02,
|
||||
&i);
|
||||
g_assert_cmpint (i,==,2);
|
||||
|
||||
g_assert_cmpuint (mu_msg_get_flags(msg),
|
||||
==, MU_FLAG_SEEN);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_msg_03 (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR
|
||||
"/cur/1283599333.1840_11.cthulhu!2,",
|
||||
NULL, NULL);
|
||||
g_assert_cmpstr (mu_msg_get_to(msg),
|
||||
==, "Bilbo Baggins <bilbo@anotherexample.com>");
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "Greetings from Lothlórien");
|
||||
g_assert_cmpstr (mu_msg_get_from(msg),
|
||||
==, "Frodo Baggins <frodo@example.com>");
|
||||
g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */
|
||||
==, MU_MSG_PRIO_NORMAL);
|
||||
g_assert_cmpuint (mu_msg_get_date(msg),
|
||||
==, 0);
|
||||
g_assert_cmpstr (mu_msg_get_body_text(msg),
|
||||
==,
|
||||
"\nLet's write some fünkÿ text\nusing umlauts.\n\nFoo.\n");
|
||||
g_assert_cmpuint (mu_msg_get_flags(msg),
|
||||
==, MU_FLAG_UNREAD); /* not seen => unread */
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_04 (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR2
|
||||
"/Foo/cur/mail5", NULL, NULL);
|
||||
|
||||
g_assert_cmpstr (mu_msg_get_to(msg),
|
||||
==, "George Custer <gac@example.com>");
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "pics for you");
|
||||
g_assert_cmpstr (mu_msg_get_from(msg),
|
||||
==, "Sitting Bull <sb@example.com>");
|
||||
g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */
|
||||
==, MU_MSG_PRIO_NORMAL);
|
||||
g_assert_cmpuint (mu_msg_get_date(msg),
|
||||
==, 0);
|
||||
|
||||
g_assert_cmpuint (mu_msg_get_flags(msg),
|
||||
==, MU_FLAG_HAS_ATTACH|MU_FLAG_UNREAD);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_multimime (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
|
||||
msg = mu_msg_new_from_file
|
||||
(MU_TESTMAILDIR "/cur/multimime!2,FS", NULL, NULL);
|
||||
/* ie., are text parts properly concatenated? */
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "multimime");
|
||||
g_assert_cmpstr (mu_msg_get_body_text(msg),
|
||||
==, "abcdef");
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_umlaut (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR
|
||||
"/cur/1305664394.2171_402.cthulhu!2,",
|
||||
NULL, NULL);
|
||||
|
||||
g_assert_cmpstr (mu_msg_get_to(msg),
|
||||
==, "Helmut Kröger <hk@testmu.xxx>");
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "Motörhead");
|
||||
g_assert_cmpstr (mu_msg_get_from(msg),
|
||||
==, "Mü <testmu@testmu.xx>");
|
||||
g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */
|
||||
==, MU_MSG_PRIO_NORMAL);
|
||||
g_assert_cmpuint (mu_msg_get_date(msg),
|
||||
==, 0);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_references (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
const GSList *refs;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR
|
||||
"/cur/1305664394.2171_402.cthulhu!2,",
|
||||
NULL, NULL);
|
||||
refs = mu_msg_get_references(msg);
|
||||
|
||||
g_assert_cmpuint (g_slist_length ((GSList*)refs), ==, 4);
|
||||
|
||||
g_assert_cmpstr ((char*)refs->data,==, "non-exist-01@msg.id");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "non-exist-02@msg.id");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "non-exist-03@msg.id");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "non-exist-04@msg.id");
|
||||
refs = g_slist_next (refs);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_references_dups (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
const GSList *refs;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR
|
||||
"/cur/1252168370_3.14675.cthulhu!2,S",
|
||||
NULL, NULL);
|
||||
refs = mu_msg_get_references(msg);
|
||||
|
||||
/* make sure duplicate msg-ids are filtered out */
|
||||
|
||||
g_assert_cmpuint (g_slist_length ((GSList*)refs), ==, 6);
|
||||
|
||||
g_assert_cmpstr ((char*)refs->data,==, "439C1136.90504@euler.org");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "4399DD94.5070309@euler.org");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "20051209233303.GA13812@gauss.org");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "439B41ED.2080402@euler.org");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "439A1E03.3090604@euler.org");
|
||||
refs = g_slist_next (refs);
|
||||
g_assert_cmpstr ((char*)refs->data,==, "20051211184308.GB13513@gauss.org");
|
||||
refs = g_slist_next (refs);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_msg_tags (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
const GSList *tags;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR2
|
||||
"/bar/cur/mail1",
|
||||
NULL, NULL);
|
||||
|
||||
g_assert_cmpstr (mu_msg_get_to(msg),
|
||||
==, "Julius Caesar <jc@example.com>");
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "Fere libenter homines id quod volunt credunt");
|
||||
g_assert_cmpstr (mu_msg_get_from(msg),
|
||||
==, "John Milton <jm@example.com>");
|
||||
g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */
|
||||
==, MU_MSG_PRIO_HIGH);
|
||||
g_assert_cmpuint (mu_msg_get_date(msg),
|
||||
==, 1217530645);
|
||||
|
||||
tags = mu_msg_get_tags (msg);
|
||||
g_assert_cmpstr ((char*)tags->data,==,"Paradise");
|
||||
g_assert_cmpstr ((char*)tags->next->data,==,"losT");
|
||||
g_assert (tags->next->next == NULL);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_msg_comp_unix_programmer (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
char *refs;
|
||||
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR2
|
||||
"/bar/cur/181736.eml", NULL, NULL);
|
||||
g_assert_cmpstr (mu_msg_get_to(msg),
|
||||
==, NULL);
|
||||
g_assert_cmpstr (mu_msg_get_subject(msg),
|
||||
==, "Re: Are writes \"atomic\" to readers of the file?");
|
||||
g_assert_cmpstr (mu_msg_get_from(msg),
|
||||
==, "Jimbo Foobarcuux <jimbo@slp53.sl.home>");
|
||||
g_assert_cmpstr (mu_msg_get_msgid(msg),
|
||||
==, "oktdp.42997$Te.22361@news.usenetserver.com");
|
||||
|
||||
refs = mu_str_from_list (mu_msg_get_references(msg), ',');
|
||||
g_assert_cmpstr (refs, ==,
|
||||
"e9065dac-13c1-4103-9e31-6974ca232a89@t15g2000prt"
|
||||
".googlegroups.com,"
|
||||
"87hbblwelr.fsf@sapphire.mobileactivedefense.com,"
|
||||
"pql248-4va.ln1@wilbur.25thandClement.com,"
|
||||
"ikns6r$li3$1@Iltempo.Update.UU.SE,"
|
||||
"8762s0jreh.fsf@sapphire.mobileactivedefense.com,"
|
||||
"ikqqp1$jv0$1@Iltempo.Update.UU.SE,"
|
||||
"87hbbjc5jt.fsf@sapphire.mobileactivedefense.com,"
|
||||
"ikr0na$lru$1@Iltempo.Update.UU.SE,"
|
||||
"tO8cp.1228$GE6.370@news.usenetserver.com,"
|
||||
"ikr6ks$nlf$1@Iltempo.Update.UU.SE,"
|
||||
"8ioh48-8mu.ln1@leafnode-msgid.gclare.org.uk");
|
||||
g_free (refs);
|
||||
|
||||
//"jimbo@slp53.sl.home (Jimbo Foobarcuux)";
|
||||
g_assert_cmpuint (mu_msg_get_prio(msg), /* 'low' */
|
||||
==, MU_MSG_PRIO_NORMAL);
|
||||
g_assert_cmpuint (mu_msg_get_date(msg),
|
||||
==, 1299603860);
|
||||
|
||||
mu_msg_unref (msg);
|
||||
}
|
||||
|
||||
/* static gboolean */
|
||||
/* ignore_error (const char* log_domain, GLogLevelFlags log_level, const gchar* msg, */
|
||||
/* gpointer user_data) */
|
||||
/* { */
|
||||
/* return FALSE; /\* don't abort *\/ */
|
||||
/* } */
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
int rv;
|
||||
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* mu_msg_str_date */
|
||||
g_test_add_func ("/mu-msg/mu-msg-01",
|
||||
test_mu_msg_01);
|
||||
g_test_add_func ("/mu-msg/mu-msg-02",
|
||||
test_mu_msg_02);
|
||||
g_test_add_func ("/mu-msg/mu-msg-03",
|
||||
test_mu_msg_03);
|
||||
g_test_add_func ("/mu-msg/mu-msg-04",
|
||||
test_mu_msg_04);
|
||||
g_test_add_func ("/mu-msg/mu-msg-multimime",
|
||||
test_mu_msg_multimime);
|
||||
g_test_add_func ("/mu-msg/mu-msg-tags",
|
||||
test_mu_msg_tags);
|
||||
g_test_add_func ("/mu-msg/mu-msg-references",
|
||||
test_mu_msg_references);
|
||||
g_test_add_func ("/mu-msg/mu-msg-references_dups",
|
||||
test_mu_msg_references_dups);
|
||||
g_test_add_func ("/mu-msg/mu-msg-umlaut",
|
||||
test_mu_msg_umlaut);
|
||||
g_test_add_func ("/mu-msg/mu-msg-comp-unix-programmer",
|
||||
test_mu_msg_comp_unix_programmer);
|
||||
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,
|
||||
(GLogFunc)black_hole, NULL);
|
||||
|
||||
rv = g_test_run ();
|
||||
|
||||
return rv;
|
||||
}
|
||||
203
lib/tests/test-mu-store.c
Normal file
203
lib/tests/test-mu-store.c
Normal file
@ -0,0 +1,203 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2011 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
#include "mu-store.h"
|
||||
|
||||
static void
|
||||
test_mu_store_new_destroy (void)
|
||||
{
|
||||
MuStore *store;
|
||||
gchar* tmpdir;
|
||||
GError *err;
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir();
|
||||
g_assert (tmpdir);
|
||||
|
||||
err = NULL;
|
||||
store = mu_store_new_writable (tmpdir, NULL, FALSE, &err);
|
||||
g_assert (store);
|
||||
g_assert (err == NULL);
|
||||
|
||||
g_assert_cmpuint (0,==,mu_store_count (store, NULL));
|
||||
|
||||
mu_store_flush (store);
|
||||
mu_store_unref (store);
|
||||
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_store_version (void)
|
||||
{
|
||||
MuStore *store;
|
||||
gchar* tmpdir;
|
||||
GError *err;
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir();
|
||||
g_assert (tmpdir);
|
||||
|
||||
err = NULL;
|
||||
store = mu_store_new_writable (tmpdir, NULL, FALSE, &err);
|
||||
g_assert (store);
|
||||
mu_store_unref (store);
|
||||
store = mu_store_new_read_only (tmpdir, &err);
|
||||
g_assert (store);
|
||||
|
||||
g_assert (err == NULL);
|
||||
|
||||
g_assert_cmpuint (0,==,mu_store_count (store, NULL));
|
||||
g_assert_cmpstr (MU_STORE_SCHEMA_VERSION,==,
|
||||
mu_store_version(store));
|
||||
|
||||
mu_store_unref (store);
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_store_store_msg_and_count (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
MuStore *store;
|
||||
gchar* tmpdir;
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir();
|
||||
g_assert (tmpdir);
|
||||
|
||||
store = mu_store_new_writable (tmpdir, NULL, FALSE, NULL);
|
||||
g_assert (store);
|
||||
|
||||
g_assert_cmpuint (0,==,mu_store_count (store, NULL));
|
||||
|
||||
/* add one */
|
||||
msg = mu_msg_new_from_file (
|
||||
MU_TESTMAILDIR "/cur/1283599333.1840_11.cthulhu!2,",
|
||||
NULL, NULL);
|
||||
g_assert (msg);
|
||||
g_assert_cmpuint (mu_store_add_msg (store, msg, NULL),
|
||||
!=, MU_STORE_INVALID_DOCID);
|
||||
g_assert_cmpuint (1,==,mu_store_count (store, NULL));
|
||||
g_assert_cmpuint (TRUE,==,mu_store_contains_message
|
||||
(store,
|
||||
MU_TESTMAILDIR "/cur/1283599333.1840_11.cthulhu!2,", NULL));
|
||||
mu_msg_unref (msg);
|
||||
|
||||
/* add another one */
|
||||
msg = mu_msg_new_from_file (MU_TESTMAILDIR2
|
||||
"/bar/cur/mail3", NULL, NULL);
|
||||
g_assert (msg);
|
||||
g_assert_cmpuint (mu_store_add_msg (store, msg, NULL),
|
||||
!=, MU_STORE_INVALID_DOCID);
|
||||
g_assert_cmpuint (2,==,mu_store_count (store, NULL));
|
||||
g_assert_cmpuint (TRUE,==,
|
||||
mu_store_contains_message (store, MU_TESTMAILDIR2
|
||||
"/bar/cur/mail3", NULL));
|
||||
mu_msg_unref (msg);
|
||||
|
||||
/* try to add the first one again. count should be 2 still */
|
||||
msg = mu_msg_new_from_file
|
||||
(MU_TESTMAILDIR "/cur/1283599333.1840_11.cthulhu!2,",
|
||||
NULL, NULL);
|
||||
g_assert (msg);
|
||||
g_assert_cmpuint (mu_store_add_msg (store, msg, NULL),
|
||||
!=, MU_STORE_INVALID_DOCID);
|
||||
g_assert_cmpuint (2,==,mu_store_count (store, NULL));
|
||||
|
||||
mu_msg_unref (msg);
|
||||
|
||||
mu_store_unref (store);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_store_store_msg_remove_and_count (void)
|
||||
{
|
||||
MuMsg *msg;
|
||||
MuStore *store;
|
||||
gchar* tmpdir;
|
||||
GError *err;
|
||||
|
||||
tmpdir = test_mu_common_get_random_tmpdir();
|
||||
g_assert (tmpdir);
|
||||
|
||||
store = mu_store_new_writable (tmpdir, NULL, FALSE, NULL);
|
||||
g_assert (store);
|
||||
|
||||
g_assert_cmpuint (0,==,mu_store_count (store, NULL));
|
||||
|
||||
/* add one */
|
||||
err = NULL;
|
||||
msg = mu_msg_new_from_file (
|
||||
MU_TESTMAILDIR "/cur/1283599333.1840_11.cthulhu!2,",
|
||||
NULL, &err);
|
||||
g_assert (msg);
|
||||
g_assert_cmpuint (mu_store_add_msg (store, msg, NULL),
|
||||
!=, MU_STORE_INVALID_DOCID);
|
||||
g_assert_cmpuint (1,==,mu_store_count (store, NULL));
|
||||
mu_msg_unref (msg);
|
||||
|
||||
/* remove one */
|
||||
mu_store_remove_path (store,
|
||||
MU_TESTMAILDIR "/cur/1283599333.1840_11.cthulhu!2,");
|
||||
g_assert_cmpuint (0,==,mu_store_count (store, NULL));
|
||||
g_assert_cmpuint (FALSE,==,mu_store_contains_message
|
||||
(store,
|
||||
MU_TESTMAILDIR "/cur/1283599333.1840_11.cthulhu!2,", NULL));
|
||||
g_free (tmpdir);
|
||||
mu_store_unref (store);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* mu_runtime_init/uninit */
|
||||
g_test_add_func ("/mu-store/mu-store-new-destroy",
|
||||
test_mu_store_new_destroy);
|
||||
g_test_add_func ("/mu-store/mu-store-version",
|
||||
test_mu_store_version);
|
||||
g_test_add_func ("/mu-store/mu-store-store-and-count",
|
||||
test_mu_store_store_msg_and_count);
|
||||
g_test_add_func ("/mu-store/mu-store-store-remove-and-count",
|
||||
test_mu_store_store_msg_remove_and_count);
|
||||
|
||||
if (!g_test_verbose())
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,
|
||||
(GLogFunc)black_hole, NULL);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
525
lib/tests/test-mu-str.c
Normal file
525
lib/tests/test-mu-str.c
Normal file
@ -0,0 +1,525 @@
|
||||
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
||||
|
||||
/*
|
||||
** Copyright (C) 2008-2010 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
#include "mu-str.h"
|
||||
#include "mu-msg-prio.h"
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_size_01 (void)
|
||||
{
|
||||
struct lconv *lc;
|
||||
char *tmp2;
|
||||
|
||||
lc = localeconv();
|
||||
|
||||
tmp2 = g_strdup_printf ("0%s0 kB", lc->decimal_point);
|
||||
g_assert_cmpstr (mu_str_size_s (0), ==, tmp2);
|
||||
g_free (tmp2);
|
||||
|
||||
tmp2 = g_strdup_printf ("100%s0 kB", lc->decimal_point);
|
||||
g_assert_cmpstr (mu_str_size_s (100000), ==, tmp2);
|
||||
g_free (tmp2);
|
||||
|
||||
tmp2 = g_strdup_printf ("1%s1 MB", lc->decimal_point);
|
||||
g_assert_cmpstr (mu_str_size_s (1100*1000), ==, tmp2);
|
||||
g_free (tmp2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_size_02 (void)
|
||||
{
|
||||
struct lconv *lc;
|
||||
char *tmp1, *tmp2;
|
||||
|
||||
lc = localeconv();
|
||||
|
||||
tmp2 = g_strdup_printf ("1%s0 MB", lc->decimal_point);
|
||||
tmp1 = mu_str_size (999999);
|
||||
g_assert_cmpstr (tmp1, !=, tmp2);
|
||||
|
||||
g_free (tmp1);
|
||||
g_free (tmp2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_prio_01 (void)
|
||||
{
|
||||
g_assert_cmpstr(mu_msg_prio_name(MU_MSG_PRIO_LOW), ==, "low");
|
||||
g_assert_cmpstr(mu_msg_prio_name(MU_MSG_PRIO_NORMAL), ==, "normal");
|
||||
g_assert_cmpstr(mu_msg_prio_name(MU_MSG_PRIO_HIGH), ==, "high");
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
ignore_error (const char* log_domain, GLogLevelFlags log_level,
|
||||
const gchar* msg, gpointer user_data)
|
||||
{
|
||||
return FALSE; /* don't abort */
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_prio_02 (void)
|
||||
{
|
||||
/* this must fail */
|
||||
g_test_log_set_fatal_handler ((GTestLogFatalFunc)ignore_error, NULL);
|
||||
g_assert_cmpstr (mu_msg_prio_name(666), ==, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_normalize_01 (void)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char* word;
|
||||
const char* norm;
|
||||
} words [] = {
|
||||
{ "dantès", "dantes"},
|
||||
{ "foo", "foo" },
|
||||
{ "Föö", "foo" },
|
||||
{ "číslo", "cislo" },
|
||||
{ "hÆvý mëÐal ümláõt", "haevy medal umlaot"}
|
||||
};
|
||||
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(words); ++i) {
|
||||
gchar *str;
|
||||
str = mu_str_normalize (words[i].word, TRUE);
|
||||
g_assert_cmpstr (str, ==, words[i].norm);
|
||||
g_free (str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_normalize_02 (void)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char* word;
|
||||
const char* norm;
|
||||
} words [] = {
|
||||
{ "DantèS", "DanteS"},
|
||||
{ "foo", "foo" },
|
||||
{ "Föö", "Foo" },
|
||||
{ "číslO", "cislO" },
|
||||
{ "hÆvý mëÐal ümláõt", "hAevy meDal umlaot"}
|
||||
};
|
||||
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(words); ++i) {
|
||||
gchar *str;
|
||||
str = mu_str_normalize (words[i].word, FALSE);
|
||||
g_assert_cmpstr (str, ==, words[i].norm);
|
||||
g_free (str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_esc_to_list (void)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char* str;
|
||||
const char* strs[3];
|
||||
} strings [] = {
|
||||
{ "maildir:foo", {"maildir:foo", NULL, NULL}},
|
||||
{ "maildir:sent items", {"maildir:sent", "items", NULL}},
|
||||
{ "\"maildir:sent items\"", {"maildir:sent items", NULL, NULL}},
|
||||
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(strings); ++i) {
|
||||
GSList *lst, *cur;
|
||||
unsigned u;
|
||||
lst = mu_str_esc_to_list (strings[i].str, NULL);
|
||||
for (cur = lst, u = 0; cur; cur = g_slist_next(cur), ++u)
|
||||
g_assert_cmpstr ((const char*)cur->data,==,strings[i].strs[u]);
|
||||
mu_str_free_list (lst);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_str_xapian_escape (void)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char* word;
|
||||
const char* esc;
|
||||
} words [] = {
|
||||
{ "aap@noot.mies", "aap_noot_mies"},
|
||||
{ "Foo..Bar", "foo__bar" },
|
||||
{ "Foo.Bar", "foo_bar" },
|
||||
{ "Foo. Bar", "foo__bar" },
|
||||
{ "subject:test@foo", "subject:test_foo" },
|
||||
{ "xxx:test@bar", "xxx_test_bar" },
|
||||
{ "aa$bb$cc", "aa_bb_cc" },
|
||||
{ "date:2010..2012", "date:2010..2012"},
|
||||
{ "d:2010..2012", "d:2010..2012"},
|
||||
{ "size:10..20", "size:10..20"},
|
||||
{ "x:2010..2012", "x:2010__2012"},
|
||||
{ "q:2010..2012", "q_2010__2012"},
|
||||
{ "subject:2010..2012", "subject:2010__2012"},
|
||||
{ "(maildir:foo)", "(maildir:foo)"}
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(words); ++i) {
|
||||
gchar *a = g_strdup (words[i].word);
|
||||
mu_str_xapian_escape_in_place (a, FALSE);
|
||||
|
||||
if (g_test_verbose())
|
||||
g_print ("expected: '%s' <=> got: '%s'\n",
|
||||
words[i].esc, a);
|
||||
|
||||
g_assert_cmpstr (a, ==, words[i].esc);
|
||||
g_free (a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_xapian_escape_non_ascii (void)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char* word;
|
||||
const char* esc;
|
||||
} words [] = {
|
||||
{ "Тесла, Никола", "тесла__никола"},
|
||||
{ "Masha@Аркона.ru", "masha_аркона_ru" },
|
||||
{ "foo:ελληνικά", "foo_ελληνικά" },
|
||||
{ "日本語!!", "日本語__" },
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(words); ++i) {
|
||||
gchar *a = g_strdup (words[i].word);
|
||||
mu_str_xapian_escape_in_place (a, FALSE);
|
||||
|
||||
if (g_test_verbose())
|
||||
g_print ("(%s) expected: '%s' <=> got: '%s'\n",
|
||||
words[i].word, words[i].esc, a);
|
||||
|
||||
g_assert_cmpstr (a, ==, words[i].esc);
|
||||
g_free (a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_display_contact (void)
|
||||
{
|
||||
int i;
|
||||
struct {
|
||||
const char* word;
|
||||
const char* disp;
|
||||
} words [] = {
|
||||
{ "\"Foo Bar\" <aap@noot.mies>", "Foo Bar"},
|
||||
{ "Foo Bar <aap@noot.mies>", "Foo Bar" },
|
||||
{ "<aap@noot.mies>", "aap@noot.mies" },
|
||||
{ "foo@bar.nl", "foo@bar.nl" }
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(words); ++i)
|
||||
g_assert_cmpstr (mu_str_display_contact_s (words[i].word), ==,
|
||||
words[i].disp);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
assert_cmplst (GSList *lst, const char *items[])
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!lst)
|
||||
g_assert (!items);
|
||||
|
||||
for (i = 0; lst; lst = g_slist_next(lst), ++i)
|
||||
g_assert_cmpstr ((char*)lst->data,==,items[i]);
|
||||
|
||||
g_assert (items[i] == NULL);
|
||||
}
|
||||
|
||||
|
||||
static GSList*
|
||||
create_list (const char *items[])
|
||||
{
|
||||
GSList *lst;
|
||||
|
||||
lst = NULL;
|
||||
while (items && *items) {
|
||||
lst = g_slist_prepend (lst, g_strdup(*items));
|
||||
++items;
|
||||
}
|
||||
|
||||
return g_slist_reverse (lst);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_str_from_list (void)
|
||||
{
|
||||
{
|
||||
const char *strs[] = {"aap", "noot", "mies", NULL};
|
||||
GSList *lst = create_list (strs);
|
||||
gchar *str = mu_str_from_list (lst, ',');
|
||||
g_assert_cmpstr ("aap,noot,mies", ==, str);
|
||||
mu_str_free_list (lst);
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
{
|
||||
const char *strs[] = {"aap", "no,ot", "mies", NULL};
|
||||
GSList *lst = create_list (strs);
|
||||
gchar *str = mu_str_from_list (lst, ',');
|
||||
g_assert_cmpstr ("aap,no,ot,mies", ==, str);
|
||||
mu_str_free_list (lst);
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
{
|
||||
const char *strs[] = {NULL};
|
||||
GSList *lst = create_list (strs);
|
||||
gchar *str = mu_str_from_list (lst,'@');
|
||||
g_assert_cmpstr (NULL, ==, str);
|
||||
mu_str_free_list (lst);
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_to_list (void)
|
||||
{
|
||||
{
|
||||
const char *items[]= {"foo", "bar ", "cuux", NULL};
|
||||
GSList *lst = mu_str_to_list ("foo@bar @cuux",'@', FALSE);
|
||||
assert_cmplst (lst, items);
|
||||
mu_str_free_list (lst);
|
||||
}
|
||||
|
||||
{
|
||||
GSList *lst = mu_str_to_list (NULL,'x',FALSE);
|
||||
g_assert (lst == NULL);
|
||||
mu_str_free_list (lst);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_str_to_list_strip (void)
|
||||
{
|
||||
{
|
||||
const char *items[]= {"foo", "bar", "cuux", NULL};
|
||||
GSList *lst = mu_str_to_list ("foo@bar @cuux",'@', TRUE);
|
||||
assert_cmplst (lst, items);
|
||||
mu_str_free_list (lst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_guess_first_name (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
struct {
|
||||
char *src, *exp;
|
||||
} tests[] = {
|
||||
{ "Richard M. Stallman", "Richard M." },
|
||||
{ "John Rambo", "John" },
|
||||
{ "Ivanhoe", "Ivanhoe" },
|
||||
{ "", "" }
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(tests); ++i) {
|
||||
gchar *s;
|
||||
|
||||
s = mu_str_guess_first_name (tests[i].src);
|
||||
g_assert_cmpstr (s, ==, tests[i].exp);
|
||||
g_free (s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_guess_last_name (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
struct {
|
||||
char *src, *exp;
|
||||
} tests[] = {
|
||||
{ "Richard M. Stallman", "Stallman" },
|
||||
{ "John Rambo", "Rambo" },
|
||||
{ "Ivanhoe", "" },
|
||||
{ "", "" }
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(tests); ++i) {
|
||||
gchar *s;
|
||||
|
||||
s = mu_str_guess_last_name (tests[i].src);
|
||||
g_assert_cmpstr (s, ==, tests[i].exp);
|
||||
g_free (s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_guess_nick (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
struct {
|
||||
char *src, *exp;
|
||||
} tests[] = {
|
||||
{ "Richard M. Stallman", "RichardMS" },
|
||||
{ "John Rambo", "JohnR" },
|
||||
{ "Ivanhoe", "Ivanhoe" },
|
||||
{ "", "" }
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(tests); ++i) {
|
||||
gchar *s;
|
||||
|
||||
s = mu_str_guess_nick (tests[i].src);
|
||||
g_assert_cmpstr (s, ==, tests[i].exp);
|
||||
g_free (s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_str_subject_normalize (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
struct {
|
||||
char *src, *exp;
|
||||
} tests[] = {
|
||||
{ "test123", "test123" },
|
||||
{ "Re:test123", "test123" },
|
||||
{ "Re: Fwd: test123", "test123" },
|
||||
{ "Re[3]: Fwd: test123", "test123" },
|
||||
{ "operation: mindcrime", "mindcrime" }, /*...*/
|
||||
{ "", "" }
|
||||
};
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS(tests); ++i)
|
||||
g_assert_cmpstr (mu_str_subject_normalize (tests[i].src), ==,
|
||||
tests[i].exp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
|
||||
/* mu_str_size */
|
||||
g_test_add_func ("/mu-str/mu-str-size-01",
|
||||
test_mu_str_size_01);
|
||||
g_test_add_func ("/mu-str/mu-str-size-02",
|
||||
test_mu_str_size_02);
|
||||
|
||||
/* mu_str_prio */
|
||||
g_test_add_func ("/mu-str/mu-str-prio-01",
|
||||
test_mu_str_prio_01);
|
||||
g_test_add_func ("/mu-str/mu-str-prio-02",
|
||||
test_mu_str_prio_02);
|
||||
|
||||
/* mu_str_normalize */
|
||||
g_test_add_func ("/mu-str/mu-str-normalize-01",
|
||||
test_mu_str_normalize_01);
|
||||
g_test_add_func ("/mu-str/mu-str-normalize-02",
|
||||
test_mu_str_normalize_02);
|
||||
|
||||
g_test_add_func ("/mu-str/mu-str-xapian-escape",
|
||||
test_mu_str_xapian_escape);
|
||||
g_test_add_func ("/mu-str/mu-str-xapian-escape-non-ascii",
|
||||
test_mu_str_xapian_escape_non_ascii);
|
||||
|
||||
g_test_add_func ("/mu-str/mu-str-display_contact",
|
||||
test_mu_str_display_contact);
|
||||
|
||||
g_test_add_func ("/mu-str/mu-str-from-list",
|
||||
test_mu_str_from_list);
|
||||
g_test_add_func ("/mu-str/mu-str-to-list",
|
||||
test_mu_str_to_list);
|
||||
g_test_add_func ("/mu-str/mu-str-to-list-strip",
|
||||
test_mu_str_to_list_strip);
|
||||
|
||||
g_test_add_func ("/mu-str/mu-str-esc-to-list",
|
||||
test_mu_str_esc_to_list);
|
||||
|
||||
g_test_add_func ("/mu-str/mu_str_guess_first_name",
|
||||
test_mu_str_guess_first_name);
|
||||
g_test_add_func ("/mu-str/mu_str_guess_last_name",
|
||||
test_mu_str_guess_last_name);
|
||||
g_test_add_func ("/mu-str/mu_str_guess_nick",
|
||||
test_mu_str_guess_nick);
|
||||
|
||||
g_test_add_func ("/mu-str/mu_str_subject_normalize",
|
||||
test_mu_str_subject_normalize);
|
||||
|
||||
|
||||
/* FIXME: add tests for mu_str_flags; but note the
|
||||
* function simply calls mu_msg_field_str */
|
||||
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,
|
||||
(GLogFunc)black_hole, NULL);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
238
lib/tests/test-mu-util.c
Normal file
238
lib/tests/test-mu-util.c
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
** Copyright (C) 2008-2012 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
** Free Software Foundation; either version 3, 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "test-mu-common.h"
|
||||
#include "lib/mu-util.h"
|
||||
|
||||
static void
|
||||
test_mu_util_dir_expand_00 (void)
|
||||
{
|
||||
gchar *got, *expected;
|
||||
|
||||
got = mu_util_dir_expand ("~/IProbablyDoNotExist");
|
||||
expected = g_strdup_printf ("%s%cIProbablyDoNotExist",
|
||||
getenv("HOME"), G_DIR_SEPARATOR);
|
||||
|
||||
g_assert_cmpstr (got,==,expected);
|
||||
|
||||
g_free (got);
|
||||
g_free (expected);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_util_dir_expand_01 (void)
|
||||
{
|
||||
gchar *got, *expected;
|
||||
|
||||
got = mu_util_dir_expand ("~/Desktop");
|
||||
expected = g_strdup_printf ("%s%cDesktop",
|
||||
getenv("HOME"), G_DIR_SEPARATOR);
|
||||
|
||||
g_assert_cmpstr (got,==,expected);
|
||||
|
||||
g_free (got);
|
||||
g_free (expected);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_guess_maildir_01 (void)
|
||||
{
|
||||
char *got;
|
||||
const char *expected;
|
||||
|
||||
/* skip the test if there's no /tmp */
|
||||
if (access ("/tmp", F_OK))
|
||||
return;
|
||||
|
||||
g_setenv ("MAILDIR", "/tmp", TRUE);
|
||||
|
||||
got = mu_util_guess_maildir ();
|
||||
expected = "/tmp";
|
||||
|
||||
g_assert_cmpstr (got,==,expected);
|
||||
g_free (got);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_guess_maildir_02 (void)
|
||||
{
|
||||
char *got, *mdir;
|
||||
|
||||
g_unsetenv ("MAILDIR");
|
||||
|
||||
mdir = g_strdup_printf ("%s%cMaildir",
|
||||
getenv("HOME"), G_DIR_SEPARATOR);
|
||||
got = mu_util_guess_maildir ();
|
||||
|
||||
if (access (mdir, F_OK) == 0)
|
||||
g_assert_cmpstr (got, ==, mdir);
|
||||
else
|
||||
g_assert_cmpstr (got, == , NULL);
|
||||
|
||||
g_free (got);
|
||||
g_free (mdir);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_check_dir_01 (void)
|
||||
{
|
||||
if (g_access ("/usr/bin", F_OK) == 0) {
|
||||
g_assert_cmpuint (
|
||||
mu_util_check_dir ("/usr/bin", TRUE, FALSE) == TRUE,
|
||||
==,
|
||||
g_access ("/usr/bin", R_OK) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_check_dir_02 (void)
|
||||
{
|
||||
if (g_access ("/tmp", F_OK) == 0) {
|
||||
g_assert_cmpuint (
|
||||
mu_util_check_dir ("/tmp", FALSE, TRUE) == TRUE,
|
||||
==,
|
||||
g_access ("/tmp", W_OK) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_check_dir_03 (void)
|
||||
{
|
||||
if (g_access (".", F_OK) == 0) {
|
||||
g_assert_cmpuint (
|
||||
mu_util_check_dir (".", TRUE, TRUE) == TRUE,
|
||||
==,
|
||||
g_access (".", W_OK | R_OK) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_check_dir_04 (void)
|
||||
{
|
||||
/* not a dir, so it must be false */
|
||||
g_assert_cmpuint (
|
||||
mu_util_check_dir ("test-util.c", TRUE, TRUE),
|
||||
==,
|
||||
FALSE);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_str_from_strv_01 (void)
|
||||
{
|
||||
const gchar *strv[] = { "aap", "noot", "mies", NULL };
|
||||
gchar *str = mu_util_str_from_strv (strv);
|
||||
g_assert_cmpstr (str, ==, "aap noot mies");
|
||||
g_free (str);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_util_str_from_strv_02 (void)
|
||||
{
|
||||
const gchar *strv[] = { "test", NULL };
|
||||
gchar *str = mu_util_str_from_strv (strv);
|
||||
g_assert_cmpstr (str, ==, "test");
|
||||
g_free (str);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
test_mu_util_str_from_strv_03 (void)
|
||||
{
|
||||
const gchar *strv[] = { NULL };
|
||||
gchar *str = mu_util_str_from_strv (strv);
|
||||
g_assert_cmpstr (str, ==, "");
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
test_mu_util_get_dtype_with_lstat (void)
|
||||
{
|
||||
g_assert_cmpuint (
|
||||
mu_util_get_dtype_with_lstat (MU_TESTMAILDIR), ==, DT_DIR);
|
||||
g_assert_cmpuint (
|
||||
mu_util_get_dtype_with_lstat (MU_TESTMAILDIR2), ==, DT_DIR);
|
||||
g_assert_cmpuint (
|
||||
mu_util_get_dtype_with_lstat (MU_TESTMAILDIR2 "/Foo/cur/mail5"),
|
||||
==, DT_REG);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* mu_util_dir_expand */
|
||||
g_test_add_func ("/mu-util/mu-util-dir-expand-00", test_mu_util_dir_expand_00);
|
||||
g_test_add_func ("/mu-util/mu-util-dir-expand-01", test_mu_util_dir_expand_01);
|
||||
|
||||
/* mu_util_guess_maildir */
|
||||
g_test_add_func ("/mu-util/mu-util-guess-maildir-01",
|
||||
test_mu_util_guess_maildir_01);
|
||||
g_test_add_func ("/mu-util/mu-util-guess-maildir-02",
|
||||
test_mu_util_guess_maildir_02);
|
||||
|
||||
/* mu_util_check_dir */
|
||||
g_test_add_func ("/mu-util/mu-util-check-dir-01", test_mu_util_check_dir_01);
|
||||
g_test_add_func ("/mu-util/mu-util-check-dir-02", test_mu_util_check_dir_02);
|
||||
g_test_add_func ("/mu-util/mu-util-check-dir-03", test_mu_util_check_dir_03);
|
||||
g_test_add_func ("/mu-util/mu-util-check-dir-04", test_mu_util_check_dir_04);
|
||||
|
||||
/* test_mu_util_str_from_strv */
|
||||
g_test_add_func ("/mu-util/mu-util-str-from-strv-01",
|
||||
test_mu_util_str_from_strv_01);
|
||||
g_test_add_func ("/mu-util/mu-util-str-from-strv-02",
|
||||
test_mu_util_str_from_strv_02);
|
||||
g_test_add_func ("/mu-util/mu-util-str-from-strv-03",
|
||||
test_mu_util_str_from_strv_03);
|
||||
|
||||
g_test_add_func ("/mu-util/mu-util-get-dtype-with-lstat",
|
||||
test_mu_util_get_dtype_with_lstat);
|
||||
|
||||
g_log_set_handler (NULL,
|
||||
G_LOG_LEVEL_DEBUG|
|
||||
G_LOG_LEVEL_MESSAGE|
|
||||
G_LOG_LEVEL_INFO, (GLogFunc)black_hole, NULL);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
146
lib/tests/testdir/cur/1220863042.12663_1.mindcrime!2,S
Normal file
146
lib/tests/testdir/cur/1220863042.12663_1.mindcrime!2,S
Normal file
@ -0,0 +1,146 @@
|
||||
Return-Path: <gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-4.9 required=3.0 tests=BAYES_00,DATE_IN_PAST_96_XX,
|
||||
RCVD_IN_DNSWL_MED autolearn=ham version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id 5123469CB3
|
||||
for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:19 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [66.249.91.109]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:19 +0300 (EEST)
|
||||
Received: by 10.142.237.21 with SMTP id k21cs39272wfh; Wed, 6 Aug 2008
|
||||
20:15:17 -0700 (PDT)
|
||||
Received: by 10.65.133.8 with SMTP id k8mr2071878qbn.7.1218078916289; Wed, 06
|
||||
Aug 2008 20:15:16 -0700 (PDT)
|
||||
Received: from sourceware.org (sourceware.org [209.132.176.174]) by
|
||||
mx.google.com with SMTP id 28si7904461qbw.0.2008.08.06.20.15.15; Wed, 06 Aug
|
||||
2008 20:15:16 -0700 (PDT)
|
||||
Received-SPF: neutral (google.com: 209.132.176.174 is neither permitted nor
|
||||
denied by domain of gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org)
|
||||
client-ip=209.132.176.174;
|
||||
Authentication-Results: mx.google.com; spf=neutral (google.com:
|
||||
209.132.176.174 is neither permitted nor denied by domain of
|
||||
gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org)
|
||||
smtp.mail=gcc-help-return-33661-xxxx.klub=gmail.com@gcc.gnu.org
|
||||
Received: (qmail 13493 invoked by alias); 7 Aug 2008 03:15:13 -0000
|
||||
Received: (qmail 13485 invoked by uid 22791); 7 Aug 2008 03:15:12 -0000
|
||||
Received: from mailgw1a.lmco.com (HELO mailgw1a.lmco.com) (192.31.106.7)
|
||||
by sourceware.org (qpsmtpd/0.31) with ESMTP; Thu, 07 Aug 2008 03:14:27 +0000
|
||||
Received: from emss07g01.ems.lmco.com (relay5.ems.lmco.com [166.29.2.16])by
|
||||
mailgw1a.lmco.com (LM-6) with ESMTP id m773EPZH014730for
|
||||
<gcc-help@gcc.gnu.org>; Wed, 6 Aug 2008 21:14:25 -0600 (MDT)
|
||||
Received: from CONVERSION2-DAEMON.lmco.com by lmco.com (PMDF V6.3-x14 #31428)
|
||||
id <0K5700601NO18J@lmco.com> for gcc-help@gcc.gnu.org; Wed, 06 Aug 2008
|
||||
21:14:25 -0600 (MDT)
|
||||
Received: from EMSS04I00.us.lmco.com ([166.17.13.135]) by lmco.com (PMDF
|
||||
V6.3-x14 #31428) with ESMTP id <0K5700H5MNNWGX@lmco.com> for
|
||||
gcc-help@gcc.gnu.org; Wed, 06 Aug 2008 21:14:20 -0600 (MDT)
|
||||
Received: from EMSS35M06.us.lmco.com ([158.187.107.143]) by
|
||||
EMSS04I00.us.lmco.com with Microsoft SMTPSVC(5.0.2195.6713); Wed, 06 Aug
|
||||
2008 23:14:20 -0400
|
||||
Date: Thu, 31 Jul 2008 14:57:25 -0400
|
||||
From: "Mickey Mouse" <anon@example.com>
|
||||
Subject: gcc include search order
|
||||
To: "Donald Duck" <gcc-help@gcc.gnu.org>
|
||||
Message-id: <3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com>
|
||||
MIME-version: 1.0
|
||||
Content-type: text/plain; charset=us-ascii
|
||||
Content-transfer-encoding: 7BIT
|
||||
Content-class: urn:content-classes:message
|
||||
Mailing-List: contact gcc-help-help@gcc.gnu.org; run by ezmlm
|
||||
Precedence: klub
|
||||
List-Id: <gcc-help.gcc.gnu.org>
|
||||
List-Unsubscribe: <mailto:gcc-help-unsubscribe-xxxx.klub=gmail.com@gcc.gnu.org>
|
||||
List-Archive: <http://gcc.gnu.org/ml/gcc-help/>
|
||||
List-Post: <mailto:gcc-help@gcc.gnu.org>
|
||||
List-Help: <mailto:gcc-help-help@gcc.gnu.org>
|
||||
Sender: gcc-help-owner@gcc.gnu.org
|
||||
Delivered-To: mailing list gcc-help@gcc.gnu.org
|
||||
Content-Length: 3024
|
||||
|
||||
|
||||
Hi.
|
||||
In my unit testing I need to change some header files (target is
|
||||
vxWorks, which supports some things that the sun does not).
|
||||
So, what I do is fetch the development tree, and then in a new unit test
|
||||
directory I attempt to compile the unit under test. Since this is NOT
|
||||
vxworks, I use sed to change some of the .h files and put them in a
|
||||
./changed directory.
|
||||
|
||||
When I try to compile the file, it is still using the .h file from the
|
||||
original location, even though I have listed the include path for
|
||||
./changed before the include path for the development tree.
|
||||
|
||||
Here is a partial output from gcc using the -v option
|
||||
|
||||
GNU CPP version 3.1 (cpplib) (sparc ELF)
|
||||
GNU C++ version 3.1 (sparc-sun-solaris2.8)
|
||||
compiled by GNU C version 3.1.
|
||||
ignoring nonexistent directory "NONE/include"
|
||||
#include "..." search starts here:
|
||||
#include <...> search starts here:
|
||||
.
|
||||
changed
|
||||
/export/home4/xxx/yyyy/builds/int_rel5_latest/src/mp/interface
|
||||
/export/home4/xxx/yyyy/builds/int_rel5_latest/src/ap/app
|
||||
/export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common
|
||||
/export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/interface
|
||||
/usr/local/include/g++-v3
|
||||
/usr/local/include/g++-v3/sparc-sun-solaris2.8
|
||||
/usr/local/include/g++-v3/backward
|
||||
/usr/local/include
|
||||
/usr/local/lib/gcc-lib/sparc-sun-solaris2.8/3.1/include
|
||||
/usr/local/sparc-sun-solaris2.8/include
|
||||
/usr/include
|
||||
End of search list.
|
||||
|
||||
I know the changed file is correct and that the include is not working
|
||||
as expected, because when I copy the file from ./changed, back into the
|
||||
development tree, the compilation works as expected.
|
||||
|
||||
One more bit of information. The source that I cam compiling is in
|
||||
/export/home4/xxx/yyyy/builds/int_rel5_latest/src/ap/app
|
||||
And it is including files from
|
||||
/export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common
|
||||
These include files should be including the files from ./changed (when
|
||||
they exist) but they are ignoring the .h files in the ./changed
|
||||
directory and are instead using other, unchanged files in the
|
||||
/export/home4/xxx/yyyy/builds/int_rel5_latest/src/shared/common
|
||||
directory.
|
||||
|
||||
The gcc command line is something like
|
||||
|
||||
TEST_DIR="."
|
||||
|
||||
CHANGED_DIR_NAME=changed
|
||||
CHANGED_FILES_DIR=${TEST_DIR}/${CHANGED_DIR_NAME}
|
||||
|
||||
CICU_HEADER_FILES="-I ${AP_INTERFACE_FILES} -I ${AP_APP_FILES} -I
|
||||
${SHARED_COMMON_FILES} -I ${SHARED_INTERFACE_FILES}"
|
||||
|
||||
HEADERS="-I ./ -I ${CHANGED_FILES_DIR} ${CICU_HEADER_FILES}"
|
||||
DEFINES="-DSUNRUN -DA10_DEBUG -DJOETEST"
|
||||
|
||||
CFLAGS="-v -c -g -O1 -pipe -Wformat -Wunused -Wuninitialized -Wshadow
|
||||
-Wmissing-prototypes -Wmissing-declarations"
|
||||
|
||||
printf "Compiling the UUT File\n"
|
||||
gcc -fprofile-arcs -ftest-coverage ${CFLAGS} ${HEADERS} ${DEFINES}
|
||||
${AP_APP_FILES}/unitUnderTest.cpp
|
||||
|
||||
|
||||
I hope this explanation is clear. If anyone knows how to fix the command
|
||||
line so that it gets the .h files in the "changed" directory are used
|
||||
instead of files in the other include directories.
|
||||
|
||||
Thanks
|
||||
Joe
|
||||
|
||||
----------------------------------------------------
|
||||
Time Flies like an Arrow. Fruit Flies like a Banana
|
||||
|
||||
|
||||
230
lib/tests/testdir/cur/1220863060.12663_3.mindcrime!2,S
Normal file
230
lib/tests/testdir/cur/1220863060.12663_3.mindcrime!2,S
Normal file
@ -0,0 +1,230 @@
|
||||
Return-Path: <sqlite-dev-bounces@sqlite.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00,HTML_MESSAGE
|
||||
autolearn=ham version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id D724F6963B
|
||||
for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:27 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [72.14.221.111]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:27 +0300 (EEST)
|
||||
Received: by 10.142.51.12 with SMTP id y12cs86537wfy; Mon, 4 Aug 2008 00:38:51
|
||||
-0700 (PDT)
|
||||
Received: by 10.151.113.5 with SMTP id q5mr272266ybm.37.1217835529913; Mon, 04
|
||||
Aug 2008 00:38:49 -0700 (PDT)
|
||||
Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with
|
||||
ESMTP id 5si5754915ywd.8.2008.08.04.00.38.30; Mon, 04 Aug 2008 00:38:50 -0700
|
||||
(PDT)
|
||||
Received-SPF: pass (google.com: best guess record for domain of
|
||||
sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender)
|
||||
client-ip=67.18.92.124;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: best guess record
|
||||
for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as
|
||||
permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org
|
||||
Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with
|
||||
ESMTP id 765A511C46; Mon, 4 Aug 2008 03:38:27 -0400 (EDT)
|
||||
X-Original-To: sqlite-dev@sqlite.org
|
||||
Delivered-To: sqlite-dev@sqlite.org
|
||||
Received: from ik-out-1112.google.com (ik-out-1112.google.com [66.249.90.176])
|
||||
by sqlite.org (Postfix) with ESMTP id 4C59511C41 for <sqlite-dev@sqlite.org>;
|
||||
Mon, 4 Aug 2008 03:38:23 -0400 (EDT)
|
||||
Received: by ik-out-1112.google.com with SMTP id b32so2163423ika.0 for
|
||||
<sqlite-dev@sqlite.org>; Mon, 04 Aug 2008 00:38:23 -0700 (PDT)
|
||||
Received: by 10.210.54.19 with SMTP id c19mr14589042eba.107.1217835502549;
|
||||
Mon, 04 Aug 2008 00:38:22 -0700 (PDT)
|
||||
Received: by 10.210.115.10 with HTTP; Mon, 4 Aug 2008 00:38:22 -0700 (PDT)
|
||||
Message-ID: <477821040808040038s381bf382p7411451e3c1a2e4e@mail.gmail.com>
|
||||
Date: Mon, 4 Aug 2008 10:38:22 +0300
|
||||
From: anon@example.com
|
||||
To: sqlite-dev@sqlite.org
|
||||
In-Reply-To: <73d4fc50808030747g303a170ieac567723c2d4f24@mail.gmail.com>
|
||||
MIME-Version: 1.0
|
||||
References: <477821040808030533y41f1501dq32447b568b6e6ca5@mail.gmail.com>
|
||||
<73d4fc50808030747g303a170ieac567723c2d4f24@mail.gmail.com>
|
||||
Subject: Re: [sqlite-dev] SQLite exception
|
||||
X-BeenThere: sqlite-dev@sqlite.org
|
||||
X-Mailman-Version: 2.1.9
|
||||
Priority: normal
|
||||
Reply-To: sqlite-dev@sqlite.org
|
||||
List-Id: <sqlite-dev.sqlite.org>
|
||||
List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe>
|
||||
List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev>
|
||||
List-Post: <mailto:sqlite-dev@sqlite.org>
|
||||
List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help>
|
||||
List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=subscribe>
|
||||
Content-Type: multipart/mixed; boundary="===============2123623832=="
|
||||
Mime-version: 1.0
|
||||
Sender: sqlite-dev-bounces@sqlite.org
|
||||
Errors-To: sqlite-dev-bounces@sqlite.org
|
||||
Content-Length: 8475
|
||||
|
||||
--===============2123623832==
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----=_Part_29556_25702991.1217835502493"
|
||||
|
||||
------=_Part_29556_25702991.1217835502493
|
||||
Content-Type: text/plain; charset=ISO-8859-1
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: inline
|
||||
|
||||
Hi Grant,
|
||||
|
||||
Thanks for your reply.
|
||||
I am using a different session for each thread, whenever a thread wishes to
|
||||
access the database it gets a session from the session pool and works with
|
||||
that session until its work is done.
|
||||
|
||||
Most of the actions the threads are doing on the database are quite
|
||||
complicated and are required to be fully committed or completely ignored, so
|
||||
yes, I am (most of the time) explicitly beginning and committing my
|
||||
transactions.
|
||||
|
||||
Regarding the SQLiteStatementImpl, I believe the Poco manual explains that
|
||||
sessions and statements for that matter cannot be shared between threads,
|
||||
therefore if you are using a session via one thread only it should work
|
||||
fine.
|
||||
|
||||
My first impression was that the problem was in the Poco infrastructure (I
|
||||
have found several Poco related bugs in the past), but the problem ALWAYS
|
||||
occurs when I perform the "BEGIN IMMEDIATE" action, if it were a Poco
|
||||
related bug, I would expect to see it here and there without any relation to
|
||||
this specific statement, but that is not the case.
|
||||
|
||||
None the less, I will also post my question on the Poco forums.
|
||||
|
||||
Nadav.
|
||||
|
||||
On Sun, Aug 3, 2008 at 5:47 PM, Grant Gatchel <grant.gatchel@gmail.com>wrote:
|
||||
|
||||
> Are you using the same Poco::Session for every thread or does each call
|
||||
> create a new session/handle to the database?
|
||||
>
|
||||
> Are you explicitly BEGINning and COMMITting your transactions?
|
||||
>
|
||||
> In looking at the 1.3.2 branch of Poco::Data::SQLite, there appears to be a
|
||||
> race condition in the SQLiteStatementImpl::next() method in which the member
|
||||
> _nextResponse is being accessed before the SQLiteStatementImpl::hasNext()
|
||||
> method has a chance to interpret that value and throw an exception.
|
||||
>
|
||||
> This question might be more suitable in the Poco forums or mailinglist.
|
||||
>
|
||||
> - Grant
|
||||
>
|
||||
> On Sun, Aug 3, 2008 at 8:33 AM, nadav g <nadav.gr@gmail.com> wrote:
|
||||
>
|
||||
>> Hi All,
|
||||
>>
|
||||
>> I have been using SQLite with Poco (www.appinf.com) as my infrastructure.
|
||||
>> The program is running several threads that access this database very
|
||||
>> often and are synchronized by SQLite itself.
|
||||
>> Everything seems to work just fine most of time (usually days - weeks) but
|
||||
>> I do get an occasional exception:
|
||||
>>
|
||||
>> Exception: SQL error or missing database: Iterator Error: trying to check
|
||||
>> if there is a next value
|
||||
>>
|
||||
>> The backtrace leads to this statement:
|
||||
>> *"BEGIN IMMEDIATE"*
|
||||
>>
|
||||
>> This specific code runs numerous times before an exception occurs (if
|
||||
>> occurs at all) and I cannot think of any reason for it to fail later rather
|
||||
>> than sooner.
|
||||
>> It is pretty obvious that this situation occurs due to some rare thread
|
||||
>> state, but I could not find any information that gives me any hint as to
|
||||
>> what this state might be.
|
||||
>>
|
||||
>> So what I am asking is:
|
||||
>> 1) Does anyone know why this sort of exception occurs?
|
||||
>> 2) Can anyone think of a reason for such an exception to occur in the
|
||||
>> situation I have described?
|
||||
>>
|
||||
>> Thanks in advance,
|
||||
>> Nadav.
|
||||
>>
|
||||
>>
|
||||
>> _______________________________________________
|
||||
>> sqlite-dev mailing list
|
||||
>> sqlite-dev@sqlite.org
|
||||
>> http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev
|
||||
>>
|
||||
>>
|
||||
>
|
||||
> _______________________________________________
|
||||
> sqlite-dev mailing list
|
||||
> sqlite-dev@sqlite.org
|
||||
> http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev
|
||||
>
|
||||
>
|
||||
|
||||
------=_Part_29556_25702991.1217835502493
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: inline
|
||||
|
||||
<div dir="ltr">Hi Grant,<br><br>Thanks for your reply.<br>I am using a different session for each thread, whenever a thread wishes to access the database it gets a session from the session pool and works with that session until its work is done.<br>
|
||||
<br>Most of the actions the threads are doing on the database are quite complicated and are required to be fully committed or completely ignored, so yes, I am (most of the time) explicitly beginning and committing my transactions.<br>
|
||||
<br>Regarding the SQLiteStatementImpl, I believe the Poco manual explains that sessions and statements for that matter cannot be shared between threads, therefore if you are using a session via one thread only it should work fine.<br>
|
||||
<br>My first impression was that the problem was in the Poco infrastructure (I have found several Poco related bugs in the past), but the problem ALWAYS occurs when I perform the "BEGIN IMMEDIATE" action, if it were a Poco related bug, I would expect to see it here and there without any relation to this specific statement, but that is not the case.<br>
|
||||
<br>None the less, I will also post my question on the Poco forums.<br><br>Nadav.<br><br><div class="gmail_quote">On Sun, Aug 3, 2008 at 5:47 PM, Grant Gatchel <span dir="ltr"><<a href="mailto:grant.gatchel@gmail.com">grant.gatchel@gmail.com</a>></span> wrote:<br>
|
||||
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div dir="ltr">Are you using the same Poco::Session for every thread or does each call create a new session/handle to the database?<br>
|
||||
<br>Are you explicitly BEGINning and COMMITting your transactions?<br><br>In looking at the 1.3.2 branch of Poco::Data::SQLite, there appears to be a race condition in the SQLiteStatementImpl::next() method in which the member _nextResponse is being accessed before the SQLiteStatementImpl::hasNext() method has a chance to interpret that value and throw an exception.<br>
|
||||
|
||||
<br>This question might be more suitable in the Poco forums or mailinglist.<br><br>- Grant<br>
|
||||
<br><div class="gmail_quote"><div><div></div><div class="Wj3C7c">
|
||||
On Sun, Aug 3, 2008 at 8:33 AM, nadav g <span dir="ltr"><<a href="http://nadav.gr" target="_blank">nadav.gr</a>@<a href="http://gmail.com" target="_blank">gmail.com</a>></span> wrote:<br></div></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
|
||||
<div><div></div><div class="Wj3C7c">
|
||||
|
||||
|
||||
<div dir="ltr">Hi All,<br><br>I have been using SQLite with Poco (<a href="http://www.appinf.com" target="_blank">www.appinf.com</a>) as my infrastructure.<br>The program is running several threads that access this database very often and are synchronized by SQLite itself.<br>
|
||||
|
||||
|
||||
|
||||
|
||||
Everything seems to work just fine most of time (usually days - weeks) but I do get an occasional exception:<br><br>Exception: SQL error or missing database: Iterator Error: trying to check if there is a next value<br><br>
|
||||
|
||||
|
||||
|
||||
|
||||
The backtrace leads to this statement:<br><b>"BEGIN IMMEDIATE"</b><br><br>This specific code runs numerous times before an exception occurs (if occurs at all) and I cannot think of any reason for it to fail later rather than sooner.<br>
|
||||
|
||||
|
||||
|
||||
|
||||
It is pretty obvious that this situation occurs due to some rare thread state, but I could not find any information that gives me any hint as to what this state might be.<br><br>So what I am asking is:<br>1) Does anyone know why this sort of exception occurs?<br>
|
||||
|
||||
|
||||
|
||||
|
||||
2) Can anyone think of a reason for such an exception to occur in the situation I have described?<br><br>Thanks in advance,<br>Nadav.<br><br></div>
|
||||
<br></div></div>_______________________________________________<br>
|
||||
sqlite-dev mailing list<br>
|
||||
<a href="mailto:sqlite-dev@sqlite.org" target="_blank">sqlite-dev@sqlite.org</a><br>
|
||||
<a href="http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev" target="_blank">http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev</a><br>
|
||||
<br></blockquote></div><br></div>
|
||||
<br>_______________________________________________<br>
|
||||
sqlite-dev mailing list<br>
|
||||
<a href="mailto:sqlite-dev@sqlite.org">sqlite-dev@sqlite.org</a><br>
|
||||
<a href="http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev" target="_blank">http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev</a><br>
|
||||
<br></blockquote></div><br></div>
|
||||
|
||||
------=_Part_29556_25702991.1217835502493--
|
||||
|
||||
--===============2123623832==
|
||||
Content-Type: text/plain; charset="us-ascii"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: inline
|
||||
|
||||
_______________________________________________
|
||||
sqlite-dev mailing list
|
||||
sqlite-dev@sqlite.org
|
||||
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev
|
||||
|
||||
--===============2123623832==--
|
||||
|
||||
136
lib/tests/testdir/cur/1220863087.12663_15.mindcrime!2,PS
Normal file
136
lib/tests/testdir/cur/1220863087.12663_15.mindcrime!2,PS
Normal file
@ -0,0 +1,136 @@
|
||||
Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-3.6 required=3.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW,
|
||||
SPF_PASS,WHOIS_NETSOLPR autolearn=ham version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id 1A6CD69CB6
|
||||
for <xxxx@localhost>; Tue, 12 Aug 2008 21:42:38 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [72.14.221.109]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Tue, 12 Aug 2008 21:42:38 +0300 (EEST)
|
||||
Received: by 10.142.237.21 with SMTP id k21cs123119wfh; Sun, 10 Aug 2008
|
||||
22:06:31 -0700 (PDT)
|
||||
Received: by 10.100.166.10 with SMTP id o10mr9327844ane.0.1218431190107; Sun,
|
||||
10 Aug 2008 22:06:30 -0700 (PDT)
|
||||
Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com
|
||||
with ESMTP id c29si10110392anc.13.2008.08.10.22.06.29; Sun, 10 Aug 2008
|
||||
22:06:30 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender) client-ip=199.232.76.165;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender)
|
||||
smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Received: from localhost ([127.0.0.1]:45637 helo=lists.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KSPbx-0006dj-96 for
|
||||
xxxx.klub@gmail.com; Mon, 11 Aug 2008 01:06:29 -0400
|
||||
Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id
|
||||
1KSPbE-0006cQ-Nd for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:44 -0400
|
||||
Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id
|
||||
1KSPbD-0006bs-Px for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:44 -0400
|
||||
Received: from [199.232.76.173] (port=37426 helo=monty-python.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KSPbD-0006bk-HT for
|
||||
help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:43 -0400
|
||||
Received: from main.gmane.org ([80.91.229.2]:46446 helo=ciao.gmane.org) by
|
||||
monty-python.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim
|
||||
4.60) (envelope-from <geh-help-gnu-emacs@m.gmane.org>) id 1KSPbD-0003Kl-CA
|
||||
for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 01:05:43 -0400
|
||||
Received: from list by ciao.gmane.org with local (Exim 4.43) id
|
||||
1KSPb9-00080r-CX for help-gnu-emacs@gnu.org; Mon, 11 Aug 2008 05:05:39 +0000
|
||||
Received: from bas2-toronto63-1088792724.dsl.bell.ca ([64.229.168.148]) by
|
||||
main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for
|
||||
<help-gnu-emacs@gnu.org>; Mon, 11 Aug 2008 05:05:39 +0000
|
||||
Received: from cpchan by bas2-toronto63-1088792724.dsl.bell.ca with local
|
||||
(Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for <help-gnu-emacs@gnu.org>; Mon,
|
||||
11 Aug 2008 05:05:39 +0000
|
||||
X-Injected-Via-Gmane: http://gmane.org/
|
||||
To: help-gnu-emacs@gnu.org
|
||||
From: anon@example.com
|
||||
Date: Mon, 11 Aug 2008 01:03:22 -0400
|
||||
Organization: Linux Private Site
|
||||
Message-ID: <87bq00nnxh.fsf@MagnumOpus.Mercurius>
|
||||
References: <877iav5s49.fsf@163.com> <86hc9yc5sj.fsf@timbral.net>
|
||||
<877iat7udd.fsf@163.com> <87fxphcsxi.fsf@lion.rapttech.com.au>
|
||||
<8504ddd4-5e3b-4ed5-bf77-aa9cce81b59a@1g2000pre.googlegroups.com>
|
||||
<87k5es59we.fsf@lion.rapttech.com.au>
|
||||
<63c824e3-62b1-4a93-8fa8-2813e1f9397f@v13g2000pro.googlegroups.com>
|
||||
<874p5vsgg8.fsf@nonospaz.fatphil.org>
|
||||
<8250972e-1886-4021-80bc-376e34881c80@v39g2000pro.googlegroups.com>
|
||||
<87zlnnqvvs.fsf@nonospaz.fatphil.org>
|
||||
<57add0e0-b39d-4c71-8d2c-d3b9ddfaa1a9@1g2000pre.googlegroups.com>
|
||||
<87sktfnz5p.fsf@atthis.clsnet.nl>
|
||||
<562e1111-d9e7-4b6a-b661-3f9af13fea17@b30g2000prf.googlegroups.com>
|
||||
<87d4khoq97.fsf@atthis.clsnet.nl>
|
||||
<0fe404c5-cab8-4692-8a27-532e737a7813@i24g2000prf.googlegroups.com>
|
||||
Mime-Version: 1.0
|
||||
Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha1;
|
||||
protocol="application/pgp-signature"
|
||||
X-Complaints-To: usenet@ger.gmane.org
|
||||
X-Gmane-NNTP-Posting-Host: bas2-toronto63-1088792724.dsl.bell.ca
|
||||
X-Face: G;
|
||||
Z,`sm>)4t4LB/GUrgH$W`!AmfHMj,LG)Z}X0ax@s9:0>0)B&@vcm{v-le)wng)?|o]D<V6&ay<F=H{M5?$T%p!dPdJeF,au\E@TA"v22K!Zl\\mzpU4]6$ZnAI3_L)h;
|
||||
fpd}mn2py/7gv^|*85-D_f:07cT>\Z}0:6X
|
||||
User-Agent: Gnus/5.110011 (No Gnus v0.11) Emacs/23.0.60 (gnu/linux)
|
||||
Cancel-Lock: sha1:IKyfrl5drOw6HllHFSmWHAKEeC8=
|
||||
X-detected-kernel: by monty-python.gnu.org: Linux 2.6, seldom 2.4 (older, 4)
|
||||
Subject: Re: Can anybody tell me how to send HTML-format mail in gnus
|
||||
X-BeenThere: help-gnu-emacs@gnu.org
|
||||
X-Mailman-Version: 2.1.5
|
||||
Precedence: list
|
||||
List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org>
|
||||
List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe>
|
||||
List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs>
|
||||
List-Post: <mailto:help-gnu-emacs@gnu.org>
|
||||
List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help>
|
||||
List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=subscribe>
|
||||
Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Content-Length: 1229
|
||||
Lines: 36
|
||||
|
||||
--=-=-=
|
||||
Content-Type: text/plain
|
||||
|
||||
Xah <xahlee@gmail.com> writes:
|
||||
|
||||
> So, i was reading about it in Wikipedia. Although i don't have a TV,
|
||||
> and haven't had since 2000, but i still enjoyed the festive spirits
|
||||
> anyhow. After all, i'm Chinese by blood. So, in my wandering, i ran
|
||||
> into this welcome song on youtube:
|
||||
>
|
||||
> http://www.youtube.com/watch?v=1HEndNYVhZo
|
||||
|
||||
What is your point? Your email is in plain text and I can click on the
|
||||
link just fine- it is not exactly rocket science to implement parsing of
|
||||
URL's to workable links in an Email program (a lot of programs does
|
||||
that, including Gnus). Images can be included inline if you want. Also
|
||||
mail markups such as *this*, **this** and _this_ have been around since
|
||||
the Usenet days and displayed appropriately by a number of mailers. Like
|
||||
others have said, most html messages that I have seen either contains
|
||||
useless information, or are plain spam and can introduce a host of
|
||||
security problems in some mailers.
|
||||
|
||||
Charles
|
||||
|
||||
|
||||
--=-=-=
|
||||
Content-Type: application/pgp-signature
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v2.0.4-svn0 (GNU/Linux)
|
||||
|
||||
iD8DBQFIn8gm3epPyyKbwPYRApbvAKDRirXwzMzI+NHV77+QcP3EgTPaCgCfb/6m
|
||||
GtNVKdYAeftaYm1nwRVoCDA=
|
||||
=ULo3
|
||||
-----END PGP SIGNATURE-----
|
||||
--=-=-=--
|
||||
|
||||
|
||||
|
||||
77
lib/tests/testdir/cur/1220863087.12663_19.mindcrime!2,S
Normal file
77
lib/tests/testdir/cur/1220863087.12663_19.mindcrime!2,S
Normal file
@ -0,0 +1,77 @@
|
||||
Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham
|
||||
version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id C4D6569CB3
|
||||
for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:08 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [66.249.91.109]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:08 +0300 (EEST)
|
||||
Received: by 10.142.237.21 with SMTP id k21cs34794wfh; Wed, 6 Aug 2008
|
||||
13:40:29 -0700 (PDT)
|
||||
Received: by 10.100.33.13 with SMTP id g13mr1093301ang.79.1218055228418; Wed,
|
||||
06 Aug 2008 13:40:28 -0700 (PDT)
|
||||
Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com
|
||||
with ESMTP id d19si15908789and.17.2008.08.06.13.40.27; Wed, 06 Aug 2008
|
||||
13:40:28 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender) client-ip=199.232.76.165;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender)
|
||||
smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Received: from localhost ([127.0.0.1]:56316 helo=lists.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KQpo3-0007Pc-Qk for
|
||||
xxxx.klub@gmail.com; Wed, 06 Aug 2008 16:40:27 -0400
|
||||
From: anon@example.com
|
||||
Newsgroups: gnu.emacs.help
|
||||
Date: Wed, 6 Aug 2008 20:38:35 +0100
|
||||
Message-ID: <r6bpm5-6n6.ln1@news.ducksburg.com>
|
||||
References: <55dbm5-qcl.ln1@news.ducksburg.com>
|
||||
<mailman.15710.1217599959.18990.help-gnu-emacs@gnu.org>
|
||||
Mime-Version: 1.0
|
||||
Content-Type: text/plain; charset=us-ascii
|
||||
Content-Transfer-Encoding: 7bit
|
||||
X-Trace: individual.net bABVU1hcJwWAuRwe/097AAoOXnGGeYR8G1In635iFGIyfDLPUv
|
||||
X-Orig-Path: news.ducksburg.com!news
|
||||
Cancel-Lock: sha1:wK7dsPRpNiVxpL/SfvmNzlvUR94=
|
||||
sha1:oepBoM0tJBLN52DotWmBBvW5wbg=
|
||||
User-Agent: slrn/pre0.9.9-120/mm/ao (Ubuntu Hardy)
|
||||
Path: news.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!feeder.erje.net!proxad.net!feeder1-2.proxad.net!feed.ac-versailles.fr!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail
|
||||
Xref: news.stanford.edu gnu.emacs.help:160868
|
||||
To: help-gnu-emacs@gnu.org
|
||||
Subject: Re: Learning LISP; Scheme vs elisp.
|
||||
X-BeenThere: help-gnu-emacs@gnu.org
|
||||
X-Mailman-Version: 2.1.5
|
||||
Precedence: list
|
||||
List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org>
|
||||
List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe>
|
||||
List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs>
|
||||
List-Post: <mailto:help-gnu-emacs@gnu.org>
|
||||
List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help>
|
||||
List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=subscribe>
|
||||
Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Content-Length: 417
|
||||
Lines: 11
|
||||
|
||||
On 2008-08-01, Thien-Thi Nguyen wrote:
|
||||
|
||||
> warriors attack, felling foe after foe,
|
||||
> few growing old til they realize: to know
|
||||
> what deceit is worth deflection;
|
||||
> such receipt reversed rejection!
|
||||
> then their heavy arms, e'er transformed to shields:
|
||||
> balanced hooked charms, ploughed deep, rich yields.
|
||||
|
||||
Aha: the exercise for the reader is to place the parens correctly.
|
||||
Might take me a while to solve this puzzle.
|
||||
|
||||
84
lib/tests/testdir/cur/1220863087.12663_5.mindcrime!2,S
Normal file
84
lib/tests/testdir/cur/1220863087.12663_5.mindcrime!2,S
Normal file
@ -0,0 +1,84 @@
|
||||
Return-Path: <sqlite-dev-bounces@sqlite.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham
|
||||
version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id 32F276963F
|
||||
for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:34 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [72.14.221.111]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:34 +0300 (EEST)
|
||||
Received: by 10.142.51.12 with SMTP id y12cs89397wfy; Mon, 4 Aug 2008 02:41:16
|
||||
-0700 (PDT)
|
||||
Received: by 10.150.156.20 with SMTP id d20mr963580ybe.104.1217842875596; Mon,
|
||||
04 Aug 2008 02:41:15 -0700 (PDT)
|
||||
Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with
|
||||
ESMTP id 6si3605185ywi.1.2008.08.04.02.40.57; Mon, 04 Aug 2008 02:41:15 -0700
|
||||
(PDT)
|
||||
Received-SPF: pass (google.com: best guess record for domain of
|
||||
sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender)
|
||||
client-ip=67.18.92.124;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: best guess record
|
||||
for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as
|
||||
permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org
|
||||
Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with
|
||||
ESMTP id 7147F11C45; Mon, 4 Aug 2008 05:40:55 -0400 (EDT)
|
||||
X-Original-To: sqlite-dev@sqlite.org
|
||||
Delivered-To: sqlite-dev@sqlite.org
|
||||
Received: from relay00.pair.com (relay00.pair.com [209.68.5.9]) by sqlite.org
|
||||
(Postfix) with SMTP id B5F901192C for <sqlite-dev@sqlite.org>; Mon, 4 Aug
|
||||
2008 05:40:52 -0400 (EDT)
|
||||
Received: (qmail 59961 invoked from network); 4 Aug 2008 09:40:50 -0000
|
||||
Received: from unknown (HELO ?192.168.0.17?) (unknown) by unknown with SMTP; 4
|
||||
Aug 2008 09:40:50 -0000
|
||||
X-pair-Authenticated: 87.13.75.164
|
||||
Message-Id: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net>
|
||||
From: anon@example.com
|
||||
To: sqlite-dev@sqlite.org
|
||||
Mime-Version: 1.0 (Apple Message framework v926)
|
||||
Date: Mon, 4 Aug 2008 11:40:49 +0200
|
||||
X-Mailer: Apple Mail (2.926)
|
||||
Subject: [sqlite-dev] VM optimization inside sqlite3VdbeExec
|
||||
X-BeenThere: sqlite-dev@sqlite.org
|
||||
X-Mailman-Version: 2.1.9
|
||||
Precedence: list
|
||||
Reply-To: sqlite-dev@sqlite.org
|
||||
List-Id: <sqlite-dev.sqlite.org>
|
||||
List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe>
|
||||
List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev>
|
||||
List-Post: <mailto:sqlite-dev@sqlite.org>
|
||||
List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help>
|
||||
List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=subscribe>
|
||||
Content-Type: text/plain; charset="us-ascii"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Sender: sqlite-dev-bounces@sqlite.org
|
||||
Errors-To: sqlite-dev-bounces@sqlite.org
|
||||
Content-Length: 639
|
||||
|
||||
Inside sqlite3VdbeExec there is a very big switch statement.
|
||||
In order to increase performance with few modifications to the
|
||||
original code, why not use this technique ?
|
||||
http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html
|
||||
|
||||
With a properly defined "instructions" array, instead of the switch
|
||||
statement you can use something like:
|
||||
goto * instructions[pOp->opcode];
|
||||
---
|
||||
Marco Bambini
|
||||
http://www.sqlabs.net
|
||||
http://www.sqlabs.net/blog/
|
||||
http://www.sqlabs.net/realsqlserver/
|
||||
|
||||
|
||||
|
||||
_______________________________________________
|
||||
sqlite-dev mailing list
|
||||
sqlite-dev@sqlite.org
|
||||
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev
|
||||
|
||||
138
lib/tests/testdir/cur/1220863087.12663_7.mindcrime!2,RS
Normal file
138
lib/tests/testdir/cur/1220863087.12663_7.mindcrime!2,RS
Normal file
@ -0,0 +1,138 @@
|
||||
Return-Path: <sqlite-dev-bounces@sqlite.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham
|
||||
version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id 3EBAB6963B
|
||||
for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:35 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [72.14.221.111]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:35 +0300 (EEST)
|
||||
Received: by 10.142.51.12 with SMTP id y12cs89536wfy; Mon, 4 Aug 2008 02:48:56
|
||||
-0700 (PDT)
|
||||
Received: by 10.150.134.21 with SMTP id h21mr7950048ybd.181.1217843335665;
|
||||
Mon, 04 Aug 2008 02:48:55 -0700 (PDT)
|
||||
Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with
|
||||
ESMTP id 6si5897081ywi.1.2008.08.04.02.48.35; Mon, 04 Aug 2008 02:48:55 -0700
|
||||
(PDT)
|
||||
Received-SPF: pass (google.com: best guess record for domain of
|
||||
sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender)
|
||||
client-ip=67.18.92.124;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: best guess record
|
||||
for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as
|
||||
permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org
|
||||
Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with
|
||||
ESMTP id ED01611C4E; Mon, 4 Aug 2008 05:48:31 -0400 (EDT)
|
||||
X-Original-To: sqlite-dev@sqlite.org
|
||||
Delivered-To: sqlite-dev@sqlite.org
|
||||
Received: from mx0.security.ro (mx0.security.ro [80.96.72.194]) by sqlite.org
|
||||
(Postfix) with ESMTP id EB3F51192C for <sqlite-dev@sqlite.org>; Mon, 4 Aug
|
||||
2008 05:48:28 -0400 (EDT)
|
||||
Received: (qmail 348 invoked from network); 4 Aug 2008 12:48:03 +0300
|
||||
Received: from dev.security.ro (HELO ?192.168.1.70?) (192.168.1.70) by
|
||||
mx0.security.ro with SMTP; 4 Aug 2008 12:48:03 +0300
|
||||
Message-ID: <4896D06A.8000901@security.ro>
|
||||
Date: Mon, 04 Aug 2008 12:48:26 +0300
|
||||
From: anon@example.com
|
||||
User-Agent: Thunderbird 2.0.0.16 (Windows/20080708)
|
||||
MIME-Version: 1.0
|
||||
To: sqlite-dev@sqlite.org
|
||||
References: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net>
|
||||
In-Reply-To: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net>
|
||||
Content-Type: multipart/mixed; boundary="------------000207070200050102060301"
|
||||
X-BitDefender-Scanner: Clean, Agent: BitDefender qmail 2.0.0 on
|
||||
mx0.security.ro
|
||||
X-BitDefender-Spam: No (0)
|
||||
X-BitDefender-SpamStamp: v1, whitelisted, total: 0
|
||||
Subject: Re: [sqlite-dev] VM optimization inside sqlite3VdbeExec
|
||||
X-BeenThere: sqlite-dev@sqlite.org
|
||||
X-Mailman-Version: 2.1.9
|
||||
Precedence: high
|
||||
Reply-To: sqlite-dev@sqlite.org
|
||||
List-Id: <sqlite-dev.sqlite.org>
|
||||
List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe>
|
||||
List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev>
|
||||
List-Post: <mailto:sqlite-dev@sqlite.org>
|
||||
List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help>
|
||||
List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=subscribe>
|
||||
Sender: sqlite-dev-bounces@sqlite.org
|
||||
Errors-To: sqlite-dev-bounces@sqlite.org
|
||||
Content-Length: 2212
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------000207070200050102060301
|
||||
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Marco Bambini wrote:
|
||||
> Inside sqlite3VdbeExec there is a very big switch statement.
|
||||
> In order to increase performance with few modifications to the
|
||||
> original code, why not use this technique ?
|
||||
> http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html
|
||||
>
|
||||
> With a properly defined "instructions" array, instead of the switch
|
||||
> statement you can use something like:
|
||||
> goto * instructions[pOp->opcode];
|
||||
> ---
|
||||
> Marco Bambini
|
||||
> http://www.sqlabs.net
|
||||
> http://www.sqlabs.net/blog/
|
||||
> http://www.sqlabs.net/realsqlserver/
|
||||
>
|
||||
>
|
||||
>
|
||||
> _______________________________________________
|
||||
> sqlite-dev mailing list
|
||||
> sqlite-dev@sqlite.org
|
||||
> http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev
|
||||
>
|
||||
All the world's not a VAX. This technique is GCC-specific. The SQLite
|
||||
source must be as portable as possible thus tying it to a specific
|
||||
compiler is out of the question. While one could conceivably use some
|
||||
preprocessor magic to provide alternate implementations, that would be
|
||||
impractical considering the sheer size of the code affected.
|
||||
On the other hand - perhaps you could benchmark the change and provide
|
||||
some data on whether this actually improves performance?
|
||||
|
||||
|
||||
--------------000207070200050102060301
|
||||
Content-Type: text/x-vcard; charset=utf-8;
|
||||
name="mihailim.vcf"
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment;
|
||||
filename="mihailim.vcf"
|
||||
|
||||
begin:vcard
|
||||
fn:Mihai Limbasan
|
||||
n:Limbasan;Mihai
|
||||
org:SC SECPRAL COM SRL
|
||||
adr:;;str. Actorului nr. 9;Cluj-Napoca;Cluj;400441;Romania
|
||||
email;internet:mihailim@security.ro
|
||||
title:SoftwareDeveloper
|
||||
tel;work:+40 264 449579
|
||||
tel;fax:+40 264 418594
|
||||
tel;cell:+40 729 038302
|
||||
url:http://secpral.ro/
|
||||
version:2.1
|
||||
end:vcard
|
||||
|
||||
|
||||
--------------000207070200050102060301
|
||||
Content-Type: text/plain; charset="us-ascii"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: inline
|
||||
|
||||
_______________________________________________
|
||||
sqlite-dev mailing list
|
||||
sqlite-dev@sqlite.org
|
||||
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev
|
||||
|
||||
--------------000207070200050102060301--
|
||||
|
||||
21
lib/tests/testdir/cur/1252168370_3.14675.cthulhu!2,S
Normal file
21
lib/tests/testdir/cur/1252168370_3.14675.cthulhu!2,S
Normal file
@ -0,0 +1,21 @@
|
||||
Return-Path: <dfgh@floppydisk.nl>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.1.0 (2005-09-13) on mindcrime
|
||||
X-Spam-Level:
|
||||
Delivered-To: dfgh@floppydisk.nl
|
||||
Message-ID: <43A09C49.9040902@euler.org>
|
||||
Date: Wed, 14 Dec 2005 23:27:21 +0100
|
||||
From: Fred Flintstone <fred@euler.org>
|
||||
User-Agent: Mozilla Thunderbird 1.0.7 (X11/20051010)
|
||||
X-Accept-Language: nl-NL, nl, en
|
||||
MIME-Version: 1.0
|
||||
To: dfgh@floppydisk.nl
|
||||
Subject: Re: xyz
|
||||
References: <439C1136.90504@euler.org> <4399DD94.5070309@euler.org> <20051209233303.GA13812@gauss.org> <439B41ED.2080402@euler.org> <4399DD94.5070309@euler.org> <20051209233303.GA13812@gauss.org> <439A1E03.3090604@euler.org> <20051211184308.GB13513@gauss.org>
|
||||
In-Reply-To: <20051211184308.GB13513@gauss.org>
|
||||
X-Enigmail-Version: 0.92.0.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
X-UIDL: T<?"!%LG"!cAK"!_j(#!
|
||||
Content-Length: 1879
|
||||
|
||||
Test 123.
|
||||
15
lib/tests/testdir/cur/1283599333.1840_11.cthulhu!2,
Normal file
15
lib/tests/testdir/cur/1283599333.1840_11.cthulhu!2,
Normal file
@ -0,0 +1,15 @@
|
||||
From: Frodo Baggins <frodo@example.com>
|
||||
To: Bilbo Baggins <bilbo@anotherexample.com>
|
||||
Subject: Greetings from =?UTF-8?B?TG90aGzDs3JpZW4=?=
|
||||
User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO)
|
||||
Fcc: .sent
|
||||
Organization: The Fellowship of the Ring
|
||||
MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka")
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
|
||||
Let's write some fünkÿ text
|
||||
using umlauts.
|
||||
|
||||
Foo.
|
||||
17
lib/tests/testdir/cur/1305664394.2171_402.cthulhu!2,
Normal file
17
lib/tests/testdir/cur/1305664394.2171_402.cthulhu!2,
Normal file
@ -0,0 +1,17 @@
|
||||
From: =?UTF-8?B?TcO8?= <testmu@testmu.xx>
|
||||
To: Helmut =?UTF-8?B?S3LDtmdlcg==?= <hk@testmu.xxx>
|
||||
Subject: =?UTF-8?B?TW90w7ZyaGVhZA==?=
|
||||
User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO)
|
||||
References: <non-exist-01@msg.id> <non-exist-02@msg.id> <non-exist-03@msg.id> <non-exist-04@msg.id>
|
||||
1n-Reply-To: <non-exist-04@msg.id>
|
||||
MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka")
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
|
||||
Test for issue #38, where apparently searching for accented words in subject,
|
||||
to etc. fails.
|
||||
|
||||
What about here? Queensrÿche. Mötley Crüe.
|
||||
|
||||
|
||||
57
lib/tests/testdir/cur/encrypted!2,S
Normal file
57
lib/tests/testdir/cur/encrypted!2,S
Normal file
@ -0,0 +1,57 @@
|
||||
Return-path: <>
|
||||
Envelope-to: peter@example.com
|
||||
Delivery-date: Fri, 11 May 2012 16:22:03 +0300
|
||||
Received: from localhost.example.com ([127.0.0.1] helo=borealis)
|
||||
by borealis with esmtp (Exim 4.77)
|
||||
id 1SSpnB-00038a-Ux
|
||||
for djcb@localhost; Fri, 11 May 2012 16:21:58 +0300
|
||||
Delivered-To: peter@example.com
|
||||
From: Brian <brian@example.com>
|
||||
To: Peter <peter@example.com>
|
||||
Subject: encrypted
|
||||
User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8
|
||||
Date: Fri, 11 May 2012 16:21:42 +0300
|
||||
Message-ID: <877gwi97kp.fsf@example.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/encrypted; boundary="=-=-=";
|
||||
protocol="application/pgp-encrypted"
|
||||
|
||||
--=-=-=
|
||||
Content-Type: application/pgp-encrypted
|
||||
|
||||
Version: 1
|
||||
|
||||
--=-=-=
|
||||
Content-Type: application/octet-stream
|
||||
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
||||
|
||||
hQQOA1T38TPQrHD6EA//YXkUB4Dy09ngCRyHWbXmV3XBjuKTr8xrak5ML1kwurav
|
||||
gyagOHKLMU+5CKvObChiKtXhtgU0od7IC8o+ALlHevQ0XXcqNYA2KUfX8R7akq7d
|
||||
Xx9mA6D8P7Y/P8juUCLBpfrCi2GC42DtvPZSUu3bL/ctUJ3InPHIfHibKF2HMm7/
|
||||
gUHAKY8VPJF39dLP8GLcfki6qFdeWbxgtzmuyzHfCBCLnDL0J9vpEQBpGDFMcc4v
|
||||
cCbmMJaiPOmRb6U4WOuRVnuXuTztLiIn0jMslzOSFDcLTVBAsrC01r71O+XZKfN4
|
||||
mIfcpcWJYKM2NQW8Jwf+8Hr84uznBqs8uTTlrmppjkAHZGqGMjiQDxLhDVaCQzMy
|
||||
O8PSV4xT6HPlKXOwV1OLc+vm0A0RAdSBctgZg40oFn4XdB1ur8edwAkLvc0hJKaz
|
||||
gyTQiPaXm2Uh2cDeEx4xNgXmwCKasqc9jAlnDC2QwA33+pw3OqgZT5h1obn0fAeR
|
||||
mgB+iW1503DIi/96p8HLZcr2EswLEH9ViHIEaFj/vlR5BaOncsLB0SsNV/MHRvym
|
||||
Xg5GUjzPIiyBZ3KaR9OIBiZ5eXw+bSrPAo/CAs0Zwxag7W3CH//oK39Qo1GnkYpc
|
||||
4IQxhx4IwkzqtCnripltV/kfpGu0yA/OdK8lOjkUqCwvL97o73utXIxm21Zd3mEP
|
||||
/iLNrduZjMCq+goz1pDAQa9Dez6VjwRuRPTqeAac8Fx/nzrVzIoIEAt36hpuaH1l
|
||||
KpbmHpKgsUWcrE5iYT0RRlRRtRF4PfJg8PUmP1hvw8TaEmNfT+0HgzcJB/gRsVdy
|
||||
gTzkzUDzGZLhRcpmM5eW4BkuUmIO7625pM6Jd3HOGyfCGSXyEZGYYeVKzv8xbzYf
|
||||
QM6YYKooRN9Ya2jdcWguW0sCSJO/RZ9eaORpTeOba2+Fp6w5L7lga+XM9GLfgref
|
||||
Cf39XX1RsmRBsrJTw0z5COf4bT8G3/IfQP0QyKWIFITiFjGmpZhLsKQ3KT4vSe/d
|
||||
gTY1xViVhkjvMFn3cgSOSrvktQpAhsXx0IRazN0T7pTU33a5K0SrZajY9ynFDIw9
|
||||
we7XYyVwZzYEXjGih5mTH1PhWYK5fZZEKKqaz5TyYv9SeWJ+8FrHeXUKD38SQEHM
|
||||
qkpl9Iv17RF4Qy9uASWwRoobhKO+GykTaBSTyw8R8ctG/hfAlnaZxQ3TwNyHWyvU
|
||||
9SVJsp27ulv/W9MLZtGpEMK0ckAR164Vyou1KOn200BqxbC2tJpegNeD2TP5ZtdY
|
||||
HIcxkgKr0haYcDnVEf1ulSxv23pZWIexbgvVCG7dRL0eB+6O28f9CWehle10MDyM
|
||||
0AYyw8Da2cu7PONMovqt4nayScyGTacFBp7c2KXR9DGZ0mcBwOjL/mGRKcVWN3MG
|
||||
2auCrwn2KVWmKZI3Jp0T8KhfGBnFs9lUElpDTOiED1/2bKz6Yoc385QtWx99DFMZ
|
||||
IWiH5wMxkWFpzjE+GHiJ09vSbTTL4JY9eu2n5nxQmtjYMBVxQm7S7qwH
|
||||
=0Paa
|
||||
-----END PGP MESSAGE-----
|
||||
--=-=-=--
|
||||
|
||||
36
lib/tests/testdir/cur/signed!2,S
Normal file
36
lib/tests/testdir/cur/signed!2,S
Normal file
@ -0,0 +1,36 @@
|
||||
Return-path: <>
|
||||
Envelope-to: skipio@localhost
|
||||
Delivery-date: Fri, 11 May 2012 16:21:57 +0300
|
||||
Received: from localhost.roma.net([127.0.0.1] helo=borealis)
|
||||
by borealis with esmtp (Exim 4.77)
|
||||
id 1SSpnB-00038a-55
|
||||
for djcb@localhost; Fri, 11 May 2012 16:21:57 +0300
|
||||
Delivered-To: diggler@gmail.com
|
||||
From: Skipio <skipio@roma.net>
|
||||
To: Hannibal <hanni@carthago.net>
|
||||
Subject: signed
|
||||
User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8
|
||||
Date: Fri, 11 May 2012 16:20:45 +0300
|
||||
Message-ID: <878vgy97ma.fsf@roma.net>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha1;
|
||||
protocol="application/pgp-signature"
|
||||
|
||||
--=-=-=
|
||||
Content-Type: text/plain
|
||||
|
||||
|
||||
I am signed!
|
||||
|
||||
--=-=-=
|
||||
Content-Type: application/pgp-signature
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
||||
|
||||
iEYEARECAAYFAk+tEi0ACgkQ6WrHoQF92jxTzACeKd/XxY+P7bpymWL3JBRHaW9p
|
||||
DpwAoKw7PDW4z/lNTkWjndVTjoO9jGhs
|
||||
=blXz
|
||||
-----END PGP SIGNATURE-----
|
||||
--=-=-=--
|
||||
|
||||
54
lib/tests/testdir/cur/signed-encrypted!2,S
Normal file
54
lib/tests/testdir/cur/signed-encrypted!2,S
Normal file
@ -0,0 +1,54 @@
|
||||
Return-path: <>
|
||||
Envelope-to: karjala@localhost
|
||||
Delivery-date: Fri, 11 May 2012 16:37:57 +0300
|
||||
From: karjala@example.com
|
||||
To: lapinkulta@example.com
|
||||
Subject: signed + encrypted
|
||||
User-agent: mu4e 0.9.8.5-dev1; emacs 24.1.50.8
|
||||
Date: Fri, 11 May 2012 16:36:08 +0300
|
||||
Message-ID: <874nrm96wn.fsf@example.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/encrypted; boundary="=-=-=";
|
||||
protocol="application/pgp-encrypted"
|
||||
|
||||
--=-=-=
|
||||
Content-Type: application/pgp-encrypted
|
||||
|
||||
Version: 1
|
||||
|
||||
--=-=-=
|
||||
Content-Type: application/octet-stream
|
||||
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Version: GnuPG v1.4.12 (GNU/Linux)
|
||||
|
||||
hQQOA1T38TPQrHD6EA/+K4kSpMa7zk+qihUkQnHSq28xYxisNQx6X5DVNjA/Qx16
|
||||
uZj/40ae+PoSMTVfklP+B2S/IomuTW6dwVqS7aQ3u4MTzi+YOi11k1lEMD7hR0Wb
|
||||
L0i48o3/iCPuCTpnOsaLZvRL06g+oTi0BF2pgz/YdsgsBTGrTb3pkDGSlLIhvh/J
|
||||
P8eE3OuzkXS6d8ymJKx2S2wQJrc1AFf1BgJfgc5T0iAvcV+zIMG+PIYcVd04zVpj
|
||||
cORFEfvGgfxWkeX+Ks3tu/l5PA1EesnoqFdNFZm+RKBg3RFsOm8tBlJ46xJjfeHg
|
||||
zLgifeSLy3tOX7CvWYs9torrx7s7UOI2gV8kzBqz+a7diyCMezceeQ9l0nIRybwW
|
||||
C9Egp8Bpfb02iXTOGdE/vRiNItQH14GKmXf4nCSwdtQUm3yzaqY9yL3xBxAlW53e
|
||||
YOFfPMESt+E7IlPn0c7llWGrcdrhJbUEoGOIPezES7kdeNPzi8G1lLtvT04/SSZJ
|
||||
QxPH5FNzSFaYFAQSdI7TR69P7L7vtLL8ndkjY49HfLFXochQQzsqrzVxzRCruHxA
|
||||
zbZSRptNf9SuXEaX9buO1vlFHheGvrCKzEWa6O7JD/DiyrE/zqy4jdlh9abMCouQ
|
||||
GWGSbn8jk6SMTQQ2Yv/VOyFqifHZp0UJD59tyIdenpxoYu5M0lwHLNVDlRjLEwUQ
|
||||
AIDz1tbLoM7lxs2FOKGr8QqbKIeMfL+NUmbvVIDc4mJrOlRnHh+cZYm4Z49iTl1v
|
||||
bYNMYgR5nY7W6rqh0ae7ZOW0h2NzpkAwTzuf1YrSjNavd9KBwOCFtAoZhRwfwFVx
|
||||
ju+ByHFNnf7g/R6DekHS0pSiatM0cPDJT05atEZb+13CRHHznonmLHi+VahXjrpg
|
||||
cIUA8Lhjdfm6Fsabo7gNZnTTRxNBqUXKK2vJF/XLbNrH5K2BH2dCCmUNtm3yFWiM
|
||||
DOzaw3665Y3S6MvZdyKpatbNrVoJdBpRgPxJ1YCSEituFUqHJBStay+aRb5fVkQR
|
||||
w3+9hWw+Ob0+2EumKbgfQ7iMwTZBCZP4VOxkoqdHvs9aWm4N7wHtXsyCew3icbJx
|
||||
lyUWsDx/FI+HlQRfOqeAMxmp8kKybmHNw8oGiw+uPPUHSD1NFYVm2DtwhYll3Fvs
|
||||
YY7r5s3yP1ZnwxMqWI3OsExVUXs8MS4UTAgO+cggO7YidPcANbBDihBFP8mTXtni
|
||||
Oo5n5v+/eRoLfHmnsGcaK8EkKsfFHpbqn4gxXGcBuHaTTJ/ZhbW6bi1WWZA9ExaJ
|
||||
IeTDtp5Bks1pJvTjCDacvgwl3rEBM6yaeIvB7575Y/GPMTOZhawhfOxV1smMmTKI
|
||||
JOWYb3+PuN2cvWetkjFgH8re4sRXq22DKBZHJEWYU8sH0sACAePnIr+pkrOtGeJB
|
||||
t1zBqZUnrupH6ptk9n/AjbQ+XSMTEKu55gSjYLAYx1EHApx52QLkdh+ej5xCIVeY
|
||||
6wS1Iipkoc6/r6F7CKctupXurNY2AlD4uQIOfD6kQgkqK4PY3hsRHQA+Zqj6oRfr
|
||||
kxysFJZvhgt26IeBVapFs10WuYt9iHfpbPUBQUIZCLyPAh08UdVW64Uc2DvUPy+I
|
||||
C+3RrmTHQPP/YNKgDQaZ3ySVEDkqjaDPmXr5K0Ibaib2dtPCLcA=
|
||||
=pv03
|
||||
-----END PGP MESSAGE-----
|
||||
--=-=-=--
|
||||
|
||||
111
lib/tests/testdir/new/1220863087.12663_21.mindcrime
Normal file
111
lib/tests/testdir/new/1220863087.12663_21.mindcrime
Normal file
@ -0,0 +1,111 @@
|
||||
Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham
|
||||
version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id 6389969CB2
|
||||
for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:07 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [66.249.91.109]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:07 +0300 (EEST)
|
||||
Received: by 10.142.237.21 with SMTP id k21cs34769wfh; Wed, 6 Aug 2008
|
||||
13:38:53 -0700 (PDT)
|
||||
Received: by 10.100.6.13 with SMTP id 13mr4103508anf.83.1218055131215; Wed, 06
|
||||
Aug 2008 13:38:51 -0700 (PDT)
|
||||
Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com
|
||||
with ESMTP id b32si10199298ana.34.2008.08.06.13.38.49; Wed, 06 Aug 2008
|
||||
13:38:51 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender) client-ip=199.232.76.165;
|
||||
DomainKey-Status: good (test mode)
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender)
|
||||
smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org; domainkeys=pass
|
||||
(test mode) header.From=juanma_bellon@yahoo.es
|
||||
Received: from localhost ([127.0.0.1]:55648 helo=lists.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KQpmT-0005W9-AQ for
|
||||
xxxx.klub@gmail.com; Wed, 06 Aug 2008 16:38:49 -0400
|
||||
Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id
|
||||
1KQplz-0005U5-Pk for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:38:19 -0400
|
||||
Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id
|
||||
1KQplw-0005Nw-OG for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:38:19 -0400
|
||||
Received: from [199.232.76.173] (port=45465 helo=monty-python.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KQplw-0005NX-I6 for
|
||||
help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:38:16 -0400
|
||||
Received: from n74a.bullet.mail.sp1.yahoo.com ([98.136.45.21]:29868) by
|
||||
monty-python.gnu.org with smtp (Exim 4.60) (envelope-from
|
||||
<juanma_bellon@yahoo.es>) id 1KQplw-0007EF-7Z for help-gnu-emacs@gnu.org;
|
||||
Wed, 06 Aug 2008 16:38:16 -0400
|
||||
Received: from [216.252.122.216] by n74.bullet.mail.sp1.yahoo.com with NNFMP;
|
||||
06 Aug 2008 20:38:14 -0000
|
||||
Received: from [68.142.237.89] by t1.bullet.sp1.yahoo.com with NNFMP; 06 Aug
|
||||
2008 20:38:14 -0000
|
||||
Received: from [69.147.75.180] by t5.bullet.re3.yahoo.com with NNFMP; 06 Aug
|
||||
2008 20:38:14 -0000
|
||||
Received: from [127.0.0.1] by omp101.mail.re1.yahoo.com with NNFMP; 06 Aug
|
||||
2008 20:38:14 -0000
|
||||
X-Yahoo-Newman-Id: 778995.62909.bm@omp101.mail.re1.yahoo.com
|
||||
Received: (qmail 43643 invoked from network); 6 Aug 2008 20:38:14 -0000
|
||||
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=s1024; d=yahoo.es;
|
||||
h=Received:X-YMail-OSG:X-Yahoo-Newman-Property:From:To:Subject:Date:User-Agent:References:In-Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-Disposition:Message-Id;
|
||||
b=ThdHlND5CNUsLPGuk+XhCWkdUA9w7lg4hiAgx8F8egsmQteMpwUlV/Y5tfe6K3O2jzHjtsklkzWqm7WY3VAcxxD/QgxLnianK5ZQHoelDAiGaFRqu8Y42XMZso2ccCBFWUQaKo9C+KIfa3e3ci73qehVxTtmr7bxLjurcSYEBPo=
|
||||
;
|
||||
Received: from unknown (HELO 212251170160.customer.cdi.no)
|
||||
(juanma_bellon@212.251.170.160 with plain) by smtp109.plus.mail.re1.yahoo.com
|
||||
with SMTP; 6 Aug 2008 20:38:14 -0000
|
||||
X-YMail-OSG: k86L54kVM1kiZbUlYx7gayoBrCLYMFIRDL.KJLBKetNucAbwU4RjeeE1vhjw33hREaUig0CCjG7BTwIfbeZZpRmUcHbxl6gR0z6Sd3lYqA--
|
||||
X-Yahoo-Newman-Property: ymail-3
|
||||
From: anon@example.com
|
||||
To: help-gnu-emacs@gnu.org
|
||||
Date: Wed, 6 Aug 2008 22:38:15 +0200
|
||||
User-Agent: KMail/1.9.6 (enterprise 0.20070907.709405)
|
||||
References: <mailman.15123.1216681940.18990.help-gnu-emacs@gnu.org>
|
||||
<mailman.15143.1216715014.18990.help-gnu-emacs@gnu.org>
|
||||
<9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com>
|
||||
In-Reply-To: <9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
Content-Disposition: inline
|
||||
Message-Id: <200808062238.15634.juanma_bellon@yahoo.es>
|
||||
X-detected-kernel: by monty-python.gnu.org: FreeBSD 6.x (1)
|
||||
Subject: Re: basic question: going back to dired
|
||||
X-BeenThere: help-gnu-emacs@gnu.org
|
||||
X-Mailman-Version: 2.1.5
|
||||
Precedence: list
|
||||
List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org>
|
||||
List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe>
|
||||
List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs>
|
||||
List-Post: <mailto:help-gnu-emacs@gnu.org>
|
||||
List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help>
|
||||
List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=subscribe>
|
||||
Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Content-Length: 361
|
||||
|
||||
On Thursday 31 July 2008, Xah wrote:
|
||||
> what's the logic of =E2=80=9COK=E2=80=9D?
|
||||
|
||||
=46or all I know, it comes from "0 Knock-outs" (from USA civil war times,
|
||||
IIRC), i.e., all went really well.
|
||||
|
||||
But this is really off-topic.
|
||||
=2D-=20
|
||||
Juanma
|
||||
|
||||
"Having a smoking section in a restaurant is like
|
||||
having a peeing section in a swimming pool."
|
||||
-- Edward Burr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
105
lib/tests/testdir/new/1220863087.12663_23.mindcrime
Normal file
105
lib/tests/testdir/new/1220863087.12663_23.mindcrime
Normal file
@ -0,0 +1,105 @@
|
||||
Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-2.6 required=3.0 tests=BAYES_00 autolearn=ham
|
||||
version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id C3EF069CB3
|
||||
for <xxxx@localhost>; Thu, 7 Aug 2008 08:10:10 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [66.249.91.109]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Thu, 07 Aug 2008 08:10:10 +0300 (EEST)
|
||||
Received: by 10.142.237.21 with SMTP id k21cs35153wfh; Wed, 6 Aug 2008
|
||||
13:58:17 -0700 (PDT)
|
||||
Received: by 10.100.166.10 with SMTP id o10mr4182182ane.0.1218056296101; Wed,
|
||||
06 Aug 2008 13:58:16 -0700 (PDT)
|
||||
Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com
|
||||
with ESMTP id d34si13875743and.3.2008.08.06.13.58.14; Wed, 06 Aug 2008
|
||||
13:58:16 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender) client-ip=199.232.76.165;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender)
|
||||
smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org; dkim=pass (test
|
||||
mode) header.i=@gmail.com
|
||||
Received: from localhost ([127.0.0.1]:33418 helo=lists.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KQq5G-0001aY-Cr for
|
||||
xxxx.klub@gmail.com; Wed, 06 Aug 2008 16:58:14 -0400
|
||||
Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id
|
||||
1KQq4n-0001Z9-06 for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:57:45 -0400
|
||||
Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id
|
||||
1KQq4l-0001V8-6c for help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:57:44 -0400
|
||||
Received: from [199.232.76.173] (port=46438 helo=monty-python.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KQq4k-0001Un-V2 for
|
||||
help-gnu-emacs@gnu.org; Wed, 06 Aug 2008 16:57:42 -0400
|
||||
Received: from ik-out-1112.google.com ([66.249.90.180]:17562) by
|
||||
monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from
|
||||
<lekktu@gmail.com>) id 1KQq4k-0001fk-OW for help-gnu-emacs@gnu.org; Wed, 06
|
||||
Aug 2008 16:57:42 -0400
|
||||
Received: by ik-out-1112.google.com with SMTP id c21so94956ika.2 for
|
||||
<help-gnu-emacs@gnu.org>; Wed, 06 Aug 2008 13:57:41 -0700 (PDT)
|
||||
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma;
|
||||
h=domainkey-signature:received:received:message-id:date:from:to
|
||||
:subject:cc:in-reply-to:mime-version:content-type
|
||||
:content-transfer-encoding:content-disposition:references;
|
||||
bh=TTNY9749hpg1+TXOwdaCr+zbQGhBUt3IvsjLWp+pxp0=;
|
||||
b=BOfudUT/SiW9V4e9+k3dXDzwm+ogdrq4m5OlO+f1H+oE6OAYGIm8dbdqDAOwUewBoS
|
||||
jRpfZo07YamP9rkko79SeFdQnf7UAPFAw9x7DFCm3x6muSlCcJBR7vYs1rgHOSINAn2B
|
||||
vQx2//lKR4fXfKNURNu+B30KrvoEmw6m2C8dI=
|
||||
DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma;
|
||||
h=message-id:date:from:to:subject:cc:in-reply-to:mime-version
|
||||
:content-type:content-transfer-encoding:content-disposition :references;
|
||||
b=UMDBulH/LwxDywEH0pfK3DbJ4u2kIZCVDLIM++PqrdcR82HjcS/O3Jhf5OFrf7Fnyj
|
||||
GH76xmc7zkTG/3aQy2WY6DeWCJaFarEItmhxy3h/xS+kUKeDARzNox0OzK6lIv/u9bdy
|
||||
f2LnFlYRJ7Q5vy3lxpxAWB4v0qCwtF9LjWFg4=
|
||||
Received: by 10.210.47.7 with SMTP id u7mr3100239ebu.30.1218056261587; Wed, 06
|
||||
Aug 2008 13:57:41 -0700 (PDT)
|
||||
Received: by 10.210.71.14 with HTTP; Wed, 6 Aug 2008 13:57:41 -0700 (PDT)
|
||||
Message-ID: <f7ccd24b0808061357t453f5962w8b61f9a453b684d0@mail.gmail.com>
|
||||
Date: Wed, 6 Aug 2008 22:57:41 +0200
|
||||
From: anon@example.com
|
||||
To: Juanma <juanma_bellon@yahoo.es>
|
||||
In-Reply-To: <200808062238.15634.juanma_bellon@yahoo.es>
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: inline
|
||||
References: <mailman.15123.1216681940.18990.help-gnu-emacs@gnu.org>
|
||||
<mailman.15143.1216715014.18990.help-gnu-emacs@gnu.org>
|
||||
<9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com>
|
||||
<200808062238.15634.juanma_bellon@yahoo.es>
|
||||
X-detected-kernel: by monty-python.gnu.org: Linux 2.6 (newer, 2)
|
||||
Cc: help-gnu-emacs@gnu.org
|
||||
Subject: Re: basic question: going back to dired
|
||||
X-BeenThere: help-gnu-emacs@gnu.org
|
||||
X-Mailman-Version: 2.1.5
|
||||
Precedence: list
|
||||
List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org>
|
||||
List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe>
|
||||
List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs>
|
||||
List-Post: <mailto:help-gnu-emacs@gnu.org>
|
||||
List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help>
|
||||
List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=subscribe>
|
||||
Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Content-Length: 309
|
||||
|
||||
On Wed, Aug 6, 2008 at 22:38, Juanma <juanma_bellon@yahoo.es> wrote:
|
||||
|
||||
> For all I know, it comes from "0 Knock-outs" (from USA civil war times,
|
||||
> IIRC), i.e., all went really well.
|
||||
|
||||
See http://en.wikipedia.org/wiki/Okay#Etymology
|
||||
|
||||
"0 knock-outs" is among the "Improbable or refuted etymologies".
|
||||
|
||||
Juanma
|
||||
|
||||
|
||||
98
lib/tests/testdir/new/1220863087.12663_25.mindcrime
Normal file
98
lib/tests/testdir/new/1220863087.12663_25.mindcrime
Normal file
@ -0,0 +1,98 @@
|
||||
Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-3.6 required=3.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW,
|
||||
SPF_PASS autolearn=ham version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id D68E769CB5
|
||||
for <xxxx@localhost>; Fri, 8 Aug 2008 20:56:25 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [72.14.221.111]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Fri, 08 Aug 2008 20:56:25 +0300 (EEST)
|
||||
Received: by 10.142.237.21 with SMTP id k21cs71287wfh; Fri, 8 Aug 2008
|
||||
07:40:46 -0700 (PDT)
|
||||
Received: by 10.100.122.8 with SMTP id u8mr3824321anc.77.1218206446062; Fri,
|
||||
08 Aug 2008 07:40:46 -0700 (PDT)
|
||||
Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com
|
||||
with ESMTP id d35si2718351and.38.2008.08.08.07.40.45; Fri, 08 Aug 2008
|
||||
07:40:46 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender) client-ip=199.232.76.165;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender)
|
||||
smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Received: from localhost ([127.0.0.1]:47349 helo=lists.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KRT93-0006Po-A3 for
|
||||
xxxx.klub@gmail.com; Fri, 08 Aug 2008 10:40:45 -0400
|
||||
Path: news.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!news-out.readnews.com!news-xxxfer.readnews.com!panix!not-for-mail
|
||||
From: anon@example.com
|
||||
Newsgroups: gnu.emacs.help
|
||||
Date: Fri, 08 Aug 2008 10:07:30 -0400
|
||||
Organization: PANIX Public Access Internet and UNIX, NYC
|
||||
Message-ID: <uwsireh25.fsf@one.dot.net>
|
||||
References: <mailman.15123.1216681940.18990.help-gnu-emacs@gnu.org>
|
||||
<mailman.15143.1216715014.18990.help-gnu-emacs@gnu.org>
|
||||
<9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com>
|
||||
<200808062238.15634.juanma_bellon@yahoo.es>
|
||||
<mailman.15958.1218056266.18990.help-gnu-emacs@gnu.org>
|
||||
NNTP-Posting-Host: panix5.panix.com
|
||||
Mime-Version: 1.0
|
||||
Content-Type: text/plain; charset=us-ascii
|
||||
X-Trace: reader1.panix.com 1218204439 22850 166.84.1.5 (8 Aug 2008 14:07:19
|
||||
GMT)
|
||||
X-Complaints-To: abuse@panix.com
|
||||
NNTP-Posting-Date: Fri, 8 Aug 2008 14:07:19 +0000 (UTC)
|
||||
User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.2 (windows-nt)
|
||||
Cancel-Lock: sha1:Ckkp5oJPIMuAVgEHGnS/9MkZsEs=
|
||||
Xref: news.stanford.edu gnu.emacs.help:160963
|
||||
To: help-gnu-emacs@gnu.org
|
||||
Subject: Re: basic question: going back to dired
|
||||
X-BeenThere: help-gnu-emacs@gnu.org
|
||||
X-Mailman-Version: 2.1.5
|
||||
Precedence: list
|
||||
List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org>
|
||||
List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe>
|
||||
List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs>
|
||||
List-Post: <mailto:help-gnu-emacs@gnu.org>
|
||||
List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help>
|
||||
List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=subscribe>
|
||||
Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Content-Length: 710
|
||||
Lines: 27
|
||||
|
||||
I seem to remember from my early school days it was a campaign slogan
|
||||
for someone nick-named Kinderhook that went something like
|
||||
|
||||
Old Kinderhook is OK
|
||||
|
||||
- Chris
|
||||
|
||||
"Juanma Barranquero" <lekktu@gmail.com> writes:
|
||||
|
||||
> On Wed, Aug 6, 2008 at 22:38, Juanma <juanma_bellon@yahoo.es> wrote:
|
||||
>
|
||||
>> For all I know, it comes from "0 Knock-outs" (from USA civil war times,
|
||||
>> IIRC), i.e., all went really well.
|
||||
>
|
||||
> See http://en.wikipedia.org/wiki/Okay#Etymology
|
||||
>
|
||||
> "0 knock-outs" is among the "Improbable or refuted etymologies".
|
||||
>
|
||||
> Juanma
|
||||
>
|
||||
>
|
||||
|
||||
--
|
||||
(. .)
|
||||
=ooO=(_)=Ooo=====================================
|
||||
Chris McMahan | first_initiallastname@one.dot.net
|
||||
=================================================
|
||||
|
||||
209
lib/tests/testdir/new/1220863087.12663_9.mindcrime
Normal file
209
lib/tests/testdir/new/1220863087.12663_9.mindcrime
Normal file
@ -0,0 +1,209 @@
|
||||
Return-Path: <sqlite-dev-bounces@sqlite.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-1.2 required=3.0 tests=BAYES_00,HTML_MESSAGE,
|
||||
MIME_QP_LONG_LINE autolearn=no version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id 4E3CF6963B
|
||||
for <xxxx@localhost>; Mon, 4 Aug 2008 21:49:37 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [72.14.221.111]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Mon, 04 Aug 2008 21:49:37 +0300 (EEST)
|
||||
Received: by 10.142.51.12 with SMTP id y12cs94317wfy; Mon, 4 Aug 2008 05:48:28
|
||||
-0700 (PDT)
|
||||
Received: by 10.150.152.17 with SMTP id z17mr1245909ybd.194.1217854107583;
|
||||
Mon, 04 Aug 2008 05:48:27 -0700 (PDT)
|
||||
Received: from sqlite.org (sqlite.org [67.18.92.124]) by mx.google.com with
|
||||
ESMTP id 9si6334793yws.5.2008.08.04.05.47.57; Mon, 04 Aug 2008 05:48:27 -0700
|
||||
(PDT)
|
||||
Received-SPF: pass (google.com: best guess record for domain of
|
||||
sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as permitted sender)
|
||||
client-ip=67.18.92.124;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: best guess record
|
||||
for domain of sqlite-dev-bounces@sqlite.org designates 67.18.92.124 as
|
||||
permitted sender) smtp.mail=sqlite-dev-bounces@sqlite.org
|
||||
Received: from sqlite.org (localhost [127.0.0.1]) by sqlite.org (Postfix) with
|
||||
ESMTP id 4FBC111C6F; Mon, 4 Aug 2008 08:47:54 -0400 (EDT)
|
||||
X-Original-To: sqlite-dev@sqlite.org
|
||||
Delivered-To: sqlite-dev@sqlite.org
|
||||
Received: from cpsmtpo-eml02.kpnxchange.com (cpsmtpo-eml02.kpnxchange.com
|
||||
[213.75.38.151]) by sqlite.org (Postfix) with ESMTP id AA4F111C10 for
|
||||
<sqlite-dev@sqlite.org>; Mon, 4 Aug 2008 08:47:51 -0400 (EDT)
|
||||
Received: from hpsmtp-eml21.kpnxchange.com ([213.75.38.121]) by
|
||||
cpsmtpo-eml02.kpnxchange.com with Microsoft SMTPSVC(6.0.3790.1830); Mon, 4
|
||||
Aug 2008 14:47:50 +0200
|
||||
Received: from cpbrm-eml13.kpnsp.local ([195.121.247.250]) by
|
||||
hpsmtp-eml21.kpnxchange.com with Microsoft SMTPSVC(6.0.3790.1830); Mon, 4
|
||||
Aug 2008 14:47:50 +0200
|
||||
Received: from hpsmtp-eml30.kpnxchange.com ([10.94.53.250]) by
|
||||
cpbrm-eml13.kpnsp.local with Microsoft SMTPSVC(6.0.3790.1830); Mon, 4 Aug
|
||||
2008 14:47:50 +0200
|
||||
Received: from localhost ([10.94.53.250]) by hpsmtp-eml30.kpnxchange.com with
|
||||
Microsoft SMTPSVC(6.0.3790.1830); Mon, 4 Aug 2008 14:47:49 +0200
|
||||
Content-class: urn:content-classes:message
|
||||
MIME-Version: 1.0
|
||||
X-MimeOLE: Produced By Microsoft Exchange V6.5
|
||||
Date: Mon, 4 Aug 2008 14:46:06 +0200
|
||||
Message-ID: <F687EC042917A94E8BB4B0902946453AE17D6C@CPEXBE-EML18.kpnsp.local>
|
||||
X-MS-Has-Attach:
|
||||
X-MS-TNEF-Correlator:
|
||||
Thread-Topic: [sqlite-dev] VM optimization inside sqlite3VdbeExec
|
||||
Thread-Index: Acj2FjkWvteFtLHTTYeVz4ES7E2ggAAGRxeI
|
||||
References: <83B5AF40-DBFA-4578-A043-04C80276E195@sqlabs.net>
|
||||
From: anon@example.com
|
||||
To: <sqlite-dev@sqlite.org>
|
||||
X-OriginalArrivalTime: 04 Aug 2008 12:47:49.0650 (UTC)
|
||||
FILETIME=[4D577720:01C8F630]
|
||||
Subject: Re: [sqlite-dev] VM optimization inside sqlite3VdbeExec
|
||||
X-BeenThere: sqlite-dev@sqlite.org
|
||||
X-Mailman-Version: 2.1.9
|
||||
Precedence: list
|
||||
Reply-To: sqlite-dev@sqlite.org
|
||||
List-Id: <sqlite-dev.sqlite.org>
|
||||
List-Unsubscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=unsubscribe>
|
||||
List-Archive: <http://sqlite.org:8080/cgi-bin/mailman/private/sqlite-dev>
|
||||
List-Post: <mailto:sqlite-dev@sqlite.org>
|
||||
List-Help: <mailto:sqlite-dev-request@sqlite.org?subject=help>
|
||||
List-Subscribe: <http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>,
|
||||
<mailto:sqlite-dev-request@sqlite.org?subject=subscribe>
|
||||
Content-Type: multipart/mixed; boundary="===============1911358387=="
|
||||
Mime-version: 1.0
|
||||
Sender: sqlite-dev-bounces@sqlite.org
|
||||
Errors-To: sqlite-dev-bounces@sqlite.org
|
||||
Content-Length: 5318
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
--===============1911358387==
|
||||
Content-class: urn:content-classes:message
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----_=_NextPart_001_01C8F630.0FC2EC1E"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
|
||||
------_=_NextPart_001_01C8F630.0FC2EC1E
|
||||
Content-Type: text/plain;
|
||||
charset="iso-8859-1"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Actually, almost every C compiler will already do what you suggest: if =
|
||||
the range of case labels is compact, the switch will be compiled using a =
|
||||
jump table. Only if the range is limited and/or sparse other techniques =
|
||||
will be used, such as linear search and binary search.
|
||||
=20
|
||||
I'm pretty sure, if you perform the tests suggested by Mihai, that you =
|
||||
will find zero performance difference, neither better, nor worse.
|
||||
=20
|
||||
Paul
|
||||
=20
|
||||
________________________________
|
||||
|
||||
From: anon@example.com
|
||||
Sent: Mon 8/4/2008 11:40 AM
|
||||
To: sqlite-dev@sqlite.org
|
||||
Subject: [sqlite-dev] VM optimization inside sqlite3VdbeExec
|
||||
|
||||
|
||||
|
||||
Inside sqlite3VdbeExec there is a very big switch statement.
|
||||
In order to increase performance with few modifications to the=20
|
||||
original code, why not use this technique ?
|
||||
http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html =
|
||||
<http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html>=20
|
||||
|
||||
With a properly defined "instructions" array, instead of the switch=20
|
||||
statement you can use something like:
|
||||
goto * instructions[pOp->opcode];
|
||||
---
|
||||
Marco Bambini
|
||||
http://www.sqlabs.net <http://www.sqlabs.net/>=20
|
||||
http://www.sqlabs.net/blog/ <http://www.sqlabs.net/blog/>=20
|
||||
http://www.sqlabs.net/realsqlserver/ =
|
||||
<http://www.sqlabs.net/realsqlserver/>=20
|
||||
|
||||
|
||||
|
||||
_______________________________________________
|
||||
sqlite-dev mailing list
|
||||
sqlite-dev@sqlite.org
|
||||
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev =
|
||||
<http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev>=20
|
||||
|
||||
|
||||
|
||||
------_=_NextPart_001_01C8F630.0FC2EC1E
|
||||
Content-Type: text/html;
|
||||
charset="iso-8859-1"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<HTML dir=3Dltr><HEAD><TITLE>[sqlite-dev] VM optimization inside =
|
||||
sqlite3VdbeExec</TITLE>=0A=
|
||||
<META http-equiv=3DContent-Type content=3D"text/html; charset=3Dunicode">=0A=
|
||||
<META content=3D"MSHTML 6.00.2715.400" name=3DGENERATOR></HEAD>=0A=
|
||||
<BODY>=0A=
|
||||
<DIV id=3DidOWAReplyText54900 dir=3Dltr>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial color=3D#000000 size=3D2>Actually, =
|
||||
almost every C compiler will already do what you suggest: if the range =
|
||||
of case labels is compact, the switch will be compiled using a jump =
|
||||
table. Only if the range is limited and/or sparse other techniques will =
|
||||
be used, such as linear search and binary search.</FONT></DIV>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial size=3D2></FONT> </DIV>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial size=3D2>I'm pretty sure, if you =
|
||||
perform the tests suggested by Mihai, that you will find zero =
|
||||
performance difference, neither better, nor worse.</FONT></DIV>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial size=3D2></FONT> </DIV>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial size=3D2>Paul</FONT></DIV>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial size=3D2></FONT> </DIV>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial size=3D2>=0A=
|
||||
<HR tabIndex=3D-1>=0A=
|
||||
</FONT></DIV>=0A=
|
||||
<DIV dir=3Dltr><FONT face=3DArial><FONT size=3D2><B>From:</B> =
|
||||
sqlite-dev-bounces@sqlite.org on behalf of Marco Bambini<BR><B>Sent:</B> =
|
||||
Mon 8/4/2008 11:40 AM<BR><B>To:</B> =
|
||||
sqlite-dev@sqlite.org<BR><B>Subject:</B> [sqlite-dev] VM optimization =
|
||||
inside sqlite3VdbeExec<BR><BR></FONT></FONT></DIV></DIV>=0A=
|
||||
<DIV>=0A=
|
||||
<P><FONT face=3DArial size=3D2>Inside sqlite3VdbeExec there is a very =
|
||||
big switch statement.<BR>In order to increase performance with few =
|
||||
modifications to the <BR>original code, why not use this technique =
|
||||
?<BR></FONT><A =
|
||||
href=3D"http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html">=
|
||||
<FONT face=3DArial =
|
||||
size=3D2>http://docs.freebsd.org/info/gcc/gcc.info.Labels_as_Values.html<=
|
||||
/FONT></A><BR><BR><FONT face=3DArial size=3D2>With a properly defined =
|
||||
"instructions" array, instead of the switch <BR>statement you can =
|
||||
use something like:<BR>goto * =
|
||||
instructions[pOp->opcode];<BR>---<BR>Marco Bambini<BR></FONT><A =
|
||||
href=3D"http://www.sqlabs.net/"><FONT face=3DArial =
|
||||
size=3D2>http://www.sqlabs.net</FONT></A><BR><A =
|
||||
href=3D"http://www.sqlabs.net/blog/"><FONT face=3DArial =
|
||||
size=3D2>http://www.sqlabs.net/blog/</FONT></A><BR><A =
|
||||
href=3D"http://www.sqlabs.net/realsqlserver/"><FONT face=3DArial =
|
||||
size=3D2>http://www.sqlabs.net/realsqlserver/</FONT></A><BR><BR><BR><BR><=
|
||||
FONT face=3DArial =
|
||||
size=3D2>_______________________________________________<BR>sqlite-dev =
|
||||
mailing list<BR>sqlite-dev@sqlite.org<BR></FONT><A =
|
||||
href=3D"http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev"><FONT=
|
||||
face=3DArial =
|
||||
size=3D2>http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev</FONT=
|
||||
></A><BR></P></DIV></BODY></HTML>
|
||||
------_=_NextPart_001_01C8F630.0FC2EC1E--
|
||||
|
||||
|
||||
--===============1911358387==
|
||||
Content-Type: text/plain; charset="us-ascii"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: inline
|
||||
|
||||
_______________________________________________
|
||||
sqlite-dev mailing list
|
||||
sqlite-dev@sqlite.org
|
||||
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev
|
||||
|
||||
--===============1911358387==--
|
||||
|
||||
98
lib/tests/testdir/tmp/1220863087.12663.ignore
Normal file
98
lib/tests/testdir/tmp/1220863087.12663.ignore
Normal file
@ -0,0 +1,98 @@
|
||||
Return-Path: <help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org>
|
||||
X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-3.6 required=3.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW,
|
||||
SPF_PASS autolearn=ham version=3.2.5
|
||||
X-Original-To: xxxx@localhost
|
||||
Delivered-To: xxxx@localhost
|
||||
Received: from mindcrime (localhost [127.0.0.1])
|
||||
by mail.xxxxsoftware.nl (Postfix) with ESMTP id D68E769CB5
|
||||
for <xxxx@localhost>; Fri, 8 Aug 2008 20:56:25 +0300 (EEST)
|
||||
Delivered-To: xxxx.klub@gmail.com
|
||||
Received: from gmail-imap.l.google.com [72.14.221.111]
|
||||
by mindcrime with IMAP (fetchmail-6.3.8)
|
||||
for <xxxx@localhost> (single-drop); Fri, 08 Aug 2008 20:56:25 +0300 (EEST)
|
||||
Received: by 10.142.237.21 with SMTP id k21cs71287wfh; Fri, 8 Aug 2008
|
||||
07:40:46 -0700 (PDT)
|
||||
Received: by 10.100.122.8 with SMTP id u8mr3824321anc.77.1218206446062; Fri,
|
||||
08 Aug 2008 07:40:46 -0700 (PDT)
|
||||
Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by mx.google.com
|
||||
with ESMTP id d35si2718351and.38.2008.08.08.07.40.45; Fri, 08 Aug 2008
|
||||
07:40:46 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender) client-ip=199.232.76.165;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: domain of
|
||||
help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org designates 199.232.76.165
|
||||
as permitted sender)
|
||||
smtp.mail=help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Received: from localhost ([127.0.0.1]:47349 helo=lists.gnu.org) by
|
||||
lists.gnu.org with esmtp (Exim 4.43) id 1KRT93-0006Po-A3 for
|
||||
xxxx.klub@gmail.com; Fri, 08 Aug 2008 10:40:45 -0400
|
||||
Path: news.stanford.edu!headwall.stanford.edu!newshub.sdsu.edu!news-out.readnews.com!news-xxxfer.readnews.com!panix!not-for-mail
|
||||
From: anon@example.com
|
||||
Newsgroups: gnu.emacs.help
|
||||
Date: Fri, 08 Aug 2008 10:07:30 -0400
|
||||
Organization: PANIX Public Access Internet and UNIX, NYC
|
||||
Message-ID: <uwsireh25.fsf@one.dot.net>
|
||||
References: <mailman.15123.1216681940.18990.help-gnu-emacs@gnu.org>
|
||||
<mailman.15143.1216715014.18990.help-gnu-emacs@gnu.org>
|
||||
<9bc17528-8ea9-49f7-8e9d-07f5ede91415@p31g2000prf.googlegroups.com>
|
||||
<200808062238.15634.juanma_bellon@yahoo.es>
|
||||
<mailman.15958.1218056266.18990.help-gnu-emacs@gnu.org>
|
||||
NNTP-Posting-Host: panix5.panix.com
|
||||
Mime-Version: 1.0
|
||||
Content-Type: text/plain; charset=us-ascii
|
||||
X-Trace: reader1.panix.com 1218204439 22850 166.84.1.5 (8 Aug 2008 14:07:19
|
||||
GMT)
|
||||
X-Complaints-To: abuse@panix.com
|
||||
NNTP-Posting-Date: Fri, 8 Aug 2008 14:07:19 +0000 (UTC)
|
||||
User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.2 (windows-nt)
|
||||
Cancel-Lock: sha1:Ckkp5oJPIMuAVgEHGnS/9MkZsEs=
|
||||
Xref: news.stanford.edu gnu.emacs.help:160963
|
||||
To: help-gnu-emacs@gnu.org
|
||||
Subject: Re: basic question: going back to dired
|
||||
X-BeenThere: help-gnu-emacs@gnu.org
|
||||
X-Mailman-Version: 2.1.5
|
||||
Precedence: list
|
||||
List-Id: Users list for the GNU Emacs text editor <help-gnu-emacs.gnu.org>
|
||||
List-Unsubscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=unsubscribe>
|
||||
List-Archive: <http://lists.gnu.org/pipermail/help-gnu-emacs>
|
||||
List-Post: <mailto:help-gnu-emacs@gnu.org>
|
||||
List-Help: <mailto:help-gnu-emacs-request@gnu.org?subject=help>
|
||||
List-Subscribe: <http://lists.gnu.org/mailman/listinfo/help-gnu-emacs>,
|
||||
<mailto:help-gnu-emacs-request@gnu.org?subject=subscribe>
|
||||
Sender: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Errors-To: help-gnu-emacs-bounces+xxxx.klub=gmail.com@gnu.org
|
||||
Content-Length: 710
|
||||
Lines: 27
|
||||
|
||||
I seem to remember from my early school days it was a campaign slogan
|
||||
for someone nick-named Kinderhook that went something like
|
||||
|
||||
Old Kinderhook is OK
|
||||
|
||||
- Chris
|
||||
|
||||
"Juanma Barranquero" <lekktu@gmail.com> writes:
|
||||
|
||||
> On Wed, Aug 6, 2008 at 22:38, Juanma <juanma_bellon@yahoo.es> wrote:
|
||||
>
|
||||
>> For all I know, it comes from "0 Knock-outs" (from USA civil war times,
|
||||
>> IIRC), i.e., all went really well.
|
||||
>
|
||||
> See http://en.wikipedia.org/wiki/Okay#Etymology
|
||||
>
|
||||
> "0 knock-outs" is among the "Improbable or refuted etymologies".
|
||||
>
|
||||
> Juanma
|
||||
>
|
||||
>
|
||||
|
||||
--
|
||||
(. .)
|
||||
=ooO=(_)=Ooo=====================================
|
||||
Chris McMahan | first_initiallastname@one.dot.net
|
||||
=================================================
|
||||
|
||||
448
lib/tests/testdir2/Foo/cur/arto.eml
Normal file
448
lib/tests/testdir2/Foo/cur/arto.eml
Normal file
@ -0,0 +1,448 @@
|
||||
Return-Path: <>
|
||||
X-Original-To: f00f@localhost
|
||||
Delivered-To: f00f@localhost
|
||||
Received: from puppet (puppet [127.0.0.1])
|
||||
by f00fmachines.nl (Postfix) with ESMTP id A534D39C7F1
|
||||
for <f00f@localhost>; Mon, 23 May 2011 20:30:05 +0300 (EEST)
|
||||
Delivered-To: diggler@gmail.com
|
||||
Received: from ew-in-f109.1e100.net [174.15.27.101]
|
||||
by puppet with POP3 (fetchmail-6.3.18)
|
||||
for <f00f@localhost> (single-drop); Mon, 23 May 2011 20:30:05 +0300 (EEST)
|
||||
Received: by 10.142.147.13 with SMTP id u13cs87252wfd;
|
||||
Mon, 23 May 2011 01:54:10 -0700 (PDT)
|
||||
Received: by 10.204.7.74 with SMTP id c10mr1984197bkc.104.1306140849326;
|
||||
Mon, 23 May 2011 01:54:09 -0700 (PDT)
|
||||
Received: from MTX4.mbn1.net (mtx4.mbn1.net [213.188.129.252])
|
||||
by mx.google.com with ESMTP id e6si18117551bkw.39.2011.05.23.01.54.07;
|
||||
Mon, 23 May 2011 01:54:08 -0700 (PDT)
|
||||
Received-SPF: pass (google.com: best guess record for domain of MTX4.mbn1.net designates 213.188.129.252 as permitted sender) client-ip=213.188.129.252;
|
||||
Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of MTX4.mbn1.net designates 213.188.129.252 as permitted sender) smtp.mail=
|
||||
Resent-From: <fwdgrp_11163824_f00f@f00fmachines.nl>
|
||||
X-Default-Received-SPF: pass (skip=forwardok (res=PASS)) x-ip-name=192.168.10.123;
|
||||
From: ArtOlive <artolive@mailinglijst.nl>
|
||||
To: "f00f@f00fmachines.nl" <f00f@f00fmachines.nl>
|
||||
Reply-To: <artolive@mailinglijst.nl>
|
||||
Date: Mon, 23 May 2011 10:53:45 +0200
|
||||
Subject: NIEUWSBRIEF ART OLIVE | juni exposite in galerie ArtOlive
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d"
|
||||
X-Mailer: aspNetEmail ver 3.5.2.10
|
||||
X-Sender: 87.93.13.24
|
||||
X-RemoteIP: 87.93.13.24
|
||||
Originating-IP: 87.93.13.24
|
||||
X-MAILINGLIJST-ID: <10374608.109906.11909.2011523105345.MSGID@mailinglijst.nl>
|
||||
Message-ID: <10374608.109906.11909.2011523105345.MSGID@mailinglijst.nl>
|
||||
X-Authenticated-User: guest@mailinglijst.eu
|
||||
X-STA-Metric: 0 (engine=030)
|
||||
X-STA-NotSpam: geinformeerd vormen spec:usig:3.8.2 twee samen
|
||||
X-STA-Spam: 2011 &bull • e-mailing subject:juni
|
||||
X-BTI-AntiSpam: score:0,sta:0/030,dnsbl:passed,sw:passed,bsn:10/passed,spf:off,bsctr:passed/1,dk:off,pbmf:none,ipr:0/3,trusted:no,ts:no,bs:no,ubl:passed
|
||||
X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply
|
||||
Resent-Message-Id: <19740414233016.EB6835A132F5FCF4@MTX4.mbn1.net>
|
||||
Resent-Date: Mon, 23 May 2011 10:54:07 +0200 (CEST)
|
||||
|
||||
--_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d
|
||||
Content-Type: text/plain; charset="iso-8859-15"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
ART-O-NEWS; juni 2011 Westergasfabriekterrein Polonceaukade 17 10=
|
||||
14 DA Amsterdam tel: 020-6758504 info@artolive.nl www.artolive.nlJuni=
|
||||
expositie bij ArtOlive: Peter van den Akker en Marinel Vieleers
|
||||
|
||||
|
||||
Zondag 5 juni
|
||||
Elke maand vindt er in de galerie van ArtOlive een expositie plaats. We =
|
||||
lichten enkele kunstenaars uit (die je misschien al kent van onze website=
|
||||
), waarbij we een spannende mix van materiaal en techniek presenteren. Ti=
|
||||
jdens de expositie staan we klaar om elke vraag over ons kunstaanbod te b=
|
||||
eantwoorden.=20
|
||||
|
||||
De exposities zijn te bezoeken van maandag t/m vrijdag, tussen 10:00 en 1=
|
||||
7:00 uur. De opening is altijd op de eerste zondag van de maand. Dit valt=
|
||||
samen met de Sunday Market die elke maand op het Cultuurpark Westergasfa=
|
||||
briek georganiseerd wordt. De Sunday Market is gratis te bezoeken en staa=
|
||||
t vol met kunst, design, mode en heerlijke hapjes, en er hangt altijd een=
|
||||
vrolijke sfeer. Een ideaal moment dus om in te haken en deze maand twee =
|
||||
kunstenaars te presenteren: Peter van den Akker en Marinel Vieleers.
|
||||
|
||||
We verwelkomen je graag op zondag 5 juni 2011, van 12:00 t/m 17:00 uur op=
|
||||
de Polonceaukade 17 van het Cultuurpark Westergasfabriek in Amsterdam!=20=
|
||||
|
||||
|
||||
|
||||
bekijk meer werk op www.artolive.nl... Peter van den Akker
|
||||
|
||||
|
||||
"In mijn beelden en schilderijen staat het mensbeeld centraal; niet als i=
|
||||
ndividu maar als universele gestalte, waarbij ik op transparante wijze ti=
|
||||
jdsbeelden en gelaagdheid in het menselijke handelen naar voren breng. Ve=
|
||||
rhoudingen tussen mensen, verschuivingen in wereldculturen en verandering=
|
||||
en in techniek, architectuur, natuur en mensbeeld vormen mijn inspiratieb=
|
||||
ronnen. Het zijn allemaal beelden en sferen die naast en met elkaar besta=
|
||||
an. Mijn werkwijze omvat vele technieken in verschillende materialen: sch=
|
||||
ilderijen, gemengde technieken op papier/collages, zeefdrukken, beelden i=
|
||||
n cortenstaal, keramische objecten."
|
||||
|
||||
Peter van den Akker exposeert regelmatig in binnen- en buitenland bij gal=
|
||||
erie=EBn en musea en is in verschillende kunstinstellingen en bedrijfscol=
|
||||
lecties opgenomen.
|
||||
|
||||
|
||||
lees meer over Peter... Marinel Vieleers
|
||||
|
||||
|
||||
Marinel Vieleers probeert het menselijke in de bouwwerken - en ook vaak i=
|
||||
ets van het karakter van de bouwer of bewoner - te laten zien. Het zijn m=
|
||||
aar subtiele details die dat alles weergeven.
|
||||
|
||||
De 'tand des tijds' of invloed van mensen op de gebouwen spelen vaak mee =
|
||||
in het werk. Koper, cement, lood en andere materialen worden in haar nieu=
|
||||
we werk gebruikt. Op deze manier kan ze gemakkelijker improviseren en nog=
|
||||
directer op haar gevoel afgaan.
|
||||
|
||||
Marinel is gefascineerd door de schoonheid van het imperfecte. De gelaagd=
|
||||
heid van ouderdom, het verval. De imperfectie die ontstaat door toevallig=
|
||||
e omstandigheden maakt een huis, een muur, een schutting, hout of steen b=
|
||||
oeiend. Het is doorleefd en het toont een stukje van zijn geschiedenis.
|
||||
|
||||
|
||||
lees meer over Marinel... =20
|
||||
ZONDAG 5 MEI - Juni expositie in de galerie van ArtOlive met Marinel Viel=
|
||||
eers en Peter van den Akker
|
||||
|
||||
Opening op zondag 5 mei, tijdens de Sunday Market op het Cultuurpark West=
|
||||
ergasfabriek in Amsterdam. Je bent van harte uitgenodigd om tussen 12:00 =
|
||||
en 17:00 uur langs te komen!
|
||||
|
||||
Daarna is de expositie te zien op werkdagen (ma - vrij) tussen 10:00 en 1=
|
||||
7:00. De expositie duurt tot 24 juni 2011.
|
||||
wil je niet langer door artolive ge=EFnformeerd worden? Klik dan hier om=
|
||||
je af te melden.=20
|
||||
kreeg je dit mailtje doorgestuurd en wil je voortaan zelf ook graag de n=
|
||||
ieuwsbrief ontvangen?=20
|
||||
klik dan hier om je aan te melden.=20
|
||||
|
||||
Deze e-mailing is verzorgd met MailingLijst
|
||||
|
||||
--_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d
|
||||
Content-Type: text/html; charset="iso-8859-15"
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww=
|
||||
w.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns=3D"http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3D=
|
||||
ISO-8859-15">
|
||||
<title>Artolive</title>
|
||||
</head>
|
||||
<body style=3D"line-height: 13px; font-family: Verdana; color: rgb(11=
|
||||
9, 119, 119); font-size: 10px;" vlink=3D"#666666" alink=3D"#666666" link=3D=
|
||||
"#666666">
|
||||
<style type=3D"text/css">
|
||||
A {
|
||||
COLOR: #666666; TEXT-DECORATION: none
|
||||
}
|
||||
TD {
|
||||
FONT-FAMILY: Verdana; COLOR: #777777; FONT-SIZE: 10px; VERTIC=
|
||||
AL-ALIGN: top
|
||||
}
|
||||
</style>
|
||||
<table style=3D"width: 631px;" width=3D"631" align=3D"center" cel=
|
||||
lpadding=3D"0" cellspacing=3D"10">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href=3D"http://www.mailinglijst.eu/redirect.as=
|
||||
px?l=3D154041&a=3D10374608&t=3DH" target=3D"_blank"><img style=3D"height:=
|
||||
42px; width: 188px;" alt=3D"artolive" src=3D"http://mailinglijst.eu/klan=
|
||||
ten/11909/Sjabloon/logo.jpg" width=3D"188" border=3D"0" height=3D"42"></a=
|
||||
> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"text-align: right; padding-right: 5px; c=
|
||||
olor: rgb(0, 0, 0); font-size: 10px;">ART-O-NEWS • juni 2011 </=
|
||||
td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"border-width: 1px; border-style: solid; =
|
||||
border-color: rgb(167, 169, 172); padding: 5px 10px 5px 15px; color: rgb(=
|
||||
167, 169, 172); font-size: 9px;">Westergasfabriekterrein Polonceau=
|
||||
kade 17 1014 DA Amsterdam tel: 020-6758504 <a style=3D"color=
|
||||
: rgb(167, 169, 172);" href=3D"mailto:info@artolive.nl">info@artolive.nl<=
|
||||
/a> <a style=3D"color: rgb(167, 169, 172);" href=3D"http://www.mail=
|
||||
inglijst.eu/redirect.aspx?l=3D154041&a=3D10374608&t=3DH" target=3D"_blank=
|
||||
">www.artolive.nl</a> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"border-width: 1px; border-style: solid; =
|
||||
border-color: rgb(81, 81, 81);">
|
||||
<table width=3D"100%" cellpadding=3D"10" cellspacing=3D=
|
||||
"0">
|
||||
<tbody style=3D"color: rgb(119, 119, 119); font-s=
|
||||
ize: 10px; vertical-align: top;">
|
||||
<tr>
|
||||
<td style=3D"vertical-align: top;"><img s=
|
||||
tyle=3D"height: 338px; width: 252px;" src=3D"http://mailinglijst.eu/klant=
|
||||
en/11909/juni2011/IMG_1698_%28Medium%29.JPG" width=3D"252" height=3D"338"=
|
||||
> </td>
|
||||
<td style=3D"vertical-align: top;"><span =
|
||||
style=3D"line-height: normal; font-size: 24px; color: rgb(0, 0, 0);">Juni=
|
||||
expositie bij ArtOlive: Peter van den Akker en Marinel Vieleers</span><b=
|
||||
r>
|
||||
<p><strong>Zondag 5 juni</strong><br>
|
||||
Elke maand vindt er in de galerie van Art=
|
||||
Olive een expositie plaats. We lichten enkele kunstenaars uit (die je mis=
|
||||
schien al kent van onze website), waarbij we een spannende mix van materi=
|
||||
aal en techniek presenteren. Tijdens de expositie staan we klaar om elke =
|
||||
vraag over ons kunstaanbod te beantwoorden. </p>
|
||||
<p>De exposities zijn te bezoeken van maa=
|
||||
ndag t/m vrijdag, tussen 10:00 en 17:00 uur. De opening is altijd op de e=
|
||||
erste zondag van de maand. Dit valt samen met de Sunday Market die elke m=
|
||||
aand op het Cultuurpark Westergasfabriek georganiseerd wordt. De Sunday M=
|
||||
arket is gratis te bezoeken en staat vol met kunst, design, mode en heerl=
|
||||
ijke hapjes, en er hangt altijd een vrolijke sfeer. Een ideaal moment dus=
|
||||
om in te haken en deze maand twee kunstenaars te presenteren: Peter van =
|
||||
den Akker en Marinel Vieleers.</p>
|
||||
<p>We verwelkomen je graag op zondag 5 ju=
|
||||
ni 2011, van 12:00 t/m 17:00 uur op de Polonceaukade 17 van het Cultuurpa=
|
||||
rk Westergasfabriek in Amsterdam! </p>
|
||||
<br>
|
||||
<p align=3D"right"><img style=3D"height: =
|
||||
4px; width: 4px;" alt=3D"" src=3D"http://mailinglijst.eu/klanten/11909/Sj=
|
||||
abloon/green_point.jpg" width=3D"4" height=3D"4"> <strong></strong>=
|
||||
<a target=3D"_blank" href=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D=
|
||||
154041&a=3D10374608&t=3DH"></a><a target=3D"_blank" href=3D"http://www.ma=
|
||||
ilinglijst.eu/redirect.aspx?l=3D154041&a=3D10374608&t=3DH"><strong>bekijk=
|
||||
meer werk op www.artolive.nl...</strong></a> </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"border-width: 1px; border-style: solid; =
|
||||
border-color: rgb(167, 169, 172);">
|
||||
<table width=3D"100%" cellpadding=3D"0" cellspacing=3D=
|
||||
"0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img style=3D"display: block; height:=
|
||||
24px; width: 629px;" alt=3D"" src=3D"http://mailinglijst.eu/klanten/1190=
|
||||
9/Sjabloon/header_uitgelicht_fade.jpg" width=3D"629" height=3D"24"> </td>=
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table width=3D"100%" cellpadding=3D"10" =
|
||||
cellspacing=3D"0">
|
||||
<tbody style=3D"color: rgb(119, 119, =
|
||||
119); font-size: 10px; vertical-align: top;">
|
||||
<tr>
|
||||
<td style=3D"width: 150px;"><=
|
||||
a target=3D"_blank" href=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D=
|
||||
154043&a=3D10374608&t=3DH"><img style=3D"height: 214px; width: 156px; bor=
|
||||
der-width: 0px; border-style: solid;" src=3D"http://mailinglijst.eu/klant=
|
||||
en/11909/juni2011/akker-adam-eva.jpg" width=3D"156" height=3D"214"></a> <=
|
||||
/td>
|
||||
<td><span style=3D"color: rgb=
|
||||
(0, 0, 0);">Peter van den Akker</span><br>
|
||||
<p>"In mijn beelden en schild=
|
||||
erijen staat het mensbeeld centraal; niet als individu maar als universel=
|
||||
e gestalte, waarbij ik op transparante wijze tijdsbeelden en gelaagdheid =
|
||||
in het menselijke handelen naar voren breng. Verhoudingen tussen mensen, =
|
||||
verschuivingen in wereldculturen en veranderingen in techniek, architectu=
|
||||
ur, natuur en mensbeeld vormen mijn inspiratiebronnen. Het zijn allemaal =
|
||||
beelden en sferen die naast en met elkaar bestaan. Mijn werkwijze omvat v=
|
||||
ele technieken in verschillende materialen: schilderijen, gemengde techni=
|
||||
eken op papier/collages, zeefdrukken, beelden in cortenstaal, keramische =
|
||||
objecten.”</p>
|
||||
<p>Peter van den Akker expose=
|
||||
ert regelmatig in binnen- en buitenland bij galerieën en musea en is=
|
||||
in verschillende kunstinstellingen en bedrijfscollecties opgenomen.</p>
|
||||
<br>
|
||||
<p align=3D"right"><img style=
|
||||
=3D"height: 4px; width: 4px;" alt=3D"" src=3D"http://mailinglijst.eu/klan=
|
||||
ten/11909/Sjabloon/green_point.jpg" width=3D"4" height=3D"4"> =
|
||||
<a target=3D"_blank" href=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D=
|
||||
154044&a=3D10374608&t=3DH"></a><a target=3D"_blank" href=3D"http://www.ma=
|
||||
ilinglijst.eu/redirect.aspx?l=3D154043&a=3D10374608&t=3DH"></a><a target=3D=
|
||||
"_blank" href=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154044&a=3D=
|
||||
10374608&t=3DH"><strong>lees meer over Peter...</strong></a><strong></str=
|
||||
ong> </p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align=3D"center"><a targe=
|
||||
t=3D"_blank" href=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154045&=
|
||||
a=3D10374608&t=3DH"><img style=3D"border-width: 0px; border-style: solid;=
|
||||
height: 279px; width: 44px;" src=3D"http://mailinglijst.eu/klanten/11909=
|
||||
/juni2011/vieleer-thesky032_bijgesneden.jpg" width=3D"44" height=3D"279">=
|
||||
</a> </td>
|
||||
<td><span style=3D"color: rgb=
|
||||
(0, 0, 0);">Marinel Vieleers</span><br>
|
||||
<p>Marinel Vieleers probeert =
|
||||
het menselijke in de bouwwerken - en ook vaak iets van het karakter van d=
|
||||
e bouwer of bewoner - te laten zien. Het zijn maar subtiele details die d=
|
||||
at alles weergeven.</p>
|
||||
<p>De ‘tand des tijds&r=
|
||||
squo; of invloed van mensen op de gebouwen spelen vaak mee in het werk. K=
|
||||
oper, cement, lood en andere materialen worden in haar nieuwe werk gebrui=
|
||||
kt. Op deze manier kan ze gemakkelijker improviseren en nog directer op h=
|
||||
aar gevoel afgaan.</p>
|
||||
<p>Marinel is gefascineerd do=
|
||||
or de schoonheid van het imperfecte. De gelaagdheid van ouderdom, het ver=
|
||||
val. De imperfectie die ontstaat door toevallige omstandigheden maakt een=
|
||||
huis, een muur, een schutting, hout of steen boeiend. Het is doorleefd e=
|
||||
n het toont een stukje van zijn geschiedenis.</p>
|
||||
<br>
|
||||
<p align=3D"right"><img style=
|
||||
=3D"height: 4px; width: 4px;" alt=3D"" src=3D"http://mailinglijst.eu/klan=
|
||||
ten/11909/Sjabloon/green_point.jpg" width=3D"4" height=3D"4"> =
|
||||
<a target=3D"_blank" href=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D=
|
||||
154045&a=3D10374608&t=3DH"></a><a target=3D"_blank" href=3D"http://www.ma=
|
||||
ilinglijst.eu/redirect.aspx?l=3D154046&a=3D10374608&t=3DH"></a><a target=3D=
|
||||
"_blank" href=3D"http://www.artolive.nl/work/165738"><strong>lees meer ov=
|
||||
er Marinel...</strong></a> </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"border-width: 1px; border-style: solid; =
|
||||
border-color: rgb(167, 169, 172);">
|
||||
<table width=3D"100%" cellpadding=3D"0" cellspacing=3D=
|
||||
"0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img style=3D"display: block; height:=
|
||||
24px; width: 629px;" alt=3D"" src=3D"http://mailinglijst.eu/klanten/1190=
|
||||
9/Sjabloon/header_selection_fade.jpg" width=3D"629" height=3D"24"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"padding: 5px 5px 2px;">
|
||||
<table width=3D"100%" cellpadding=3D"5" c=
|
||||
ellspacing=3D"0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a target=3D"_blank" href=
|
||||
=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154048&a=3D10374608&t=3D=
|
||||
H"><img style=3D"border-width: 0px; border-style: solid; height: 92px; wi=
|
||||
dth: 92px;" src=3D"http://mailinglijst.eu/klanten/11909/juni2011/nw_17372=
|
||||
0.jpg" width=3D"92" height=3D"92"></a> </td>
|
||||
<td><a target=3D"_blank" href=
|
||||
=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154049&a=3D10374608&t=3D=
|
||||
H"><img style=3D"border-width: 0px; border-style: solid; height: 92px; wi=
|
||||
dth: 92px;" src=3D"http://mailinglijst.eu/klanten/11909/juni2011/nw_17386=
|
||||
9.jpg" width=3D"92" height=3D"92"></a> </td>
|
||||
<td><a target=3D"_blank" href=
|
||||
=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154050&a=3D10374608&t=3D=
|
||||
H"><img style=3D"border-width: 0px; border-style: solid; height: 92px; wi=
|
||||
dth: 92px;" src=3D"http://mailinglijst.eu/klanten/11909/juni2011/nw_17398=
|
||||
0.jpg" width=3D"92" height=3D"92"></a> </td>
|
||||
<td><a target=3D"_blank" href=
|
||||
=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154051&a=3D10374608&t=3D=
|
||||
H"><img style=3D"border-width: 0px; border-style: solid; height: 92px; wi=
|
||||
dth: 92px;" src=3D"http://mailinglijst.eu/klanten/11909/juni2011/nw_17390=
|
||||
5.jpg" width=3D"92" height=3D"92"></a> </td>
|
||||
<td><a target=3D"_blank" href=
|
||||
=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154052&a=3D10374608&t=3D=
|
||||
H"><img style=3D"border-width: 0px; border-style: solid; height: 92px; wi=
|
||||
dth: 92px;" src=3D"http://mailinglijst.eu/klanten/11909/juni2011/nw_17390=
|
||||
4.jpg" width=3D"92" height=3D"92"></a> </td>
|
||||
<td><a target=3D"_blank" href=
|
||||
=3D"http://www.mailinglijst.eu/redirect.aspx?l=3D154053&a=3D10374608&t=3D=
|
||||
H"><img style=3D"border-width: 0px; border-style: solid; height: 92px; wi=
|
||||
dth: 92px;" src=3D"http://mailinglijst.eu/klanten/11909/juni2011/nw_17398=
|
||||
4.jpg" width=3D"92" height=3D"92"></a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"border-width: 1px; border-style: solid; =
|
||||
border-color: rgb(167, 169, 172);">
|
||||
<table width=3D"100%" cellpadding=3D"0" cellspacing=3D=
|
||||
"0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img style=3D"display: block; height:=
|
||||
24px; width: 629px;" alt=3D"" src=3D"http://mailinglijst.eu/klanten/1190=
|
||||
9/Sjabloon/header_agenda_fade.jpg" width=3D"629" height=3D"24"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table width=3D"100%" cellpadding=3D"10" =
|
||||
cellspacing=3D"0">
|
||||
<tbody style=3D"color: rgb(119, 119, =
|
||||
119); font-size: 10px; vertical-align: top;" valign=3D"top">
|
||||
<tr>
|
||||
<td valign=3D"top"><br>
|
||||
</td>
|
||||
<td><span style=3D"color: rgb=
|
||||
(0, 0, 0);">ZONDAG 5 MEI - Juni expositie in de galerie van ArtOlive met =
|
||||
Marinel Vieleers en Peter van den Akker</span><br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign=3D"top"><br>
|
||||
</td>
|
||||
<td>Opening op zondag 5 mei, =
|
||||
tijdens de Sunday Market op het Cultuurpark Westergasfabriek in Amsterdam=
|
||||
. Je bent van harte uitgenodigd om tussen 12:00 en 17:00 uur langs te kom=
|
||||
en!<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign=3D"top"><br>
|
||||
</td>
|
||||
<td><span style=3D"color: rgb=
|
||||
(0, 0, 0);"></span>Daarna is de expositie te zien op werkdagen (ma - vrij=
|
||||
) tussen 10:00 en 17:00. De expositie duurt tot 24 juni 2011.<br>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=3D"padding: 30px 15px 15px; text-transform:=
|
||||
uppercase; color: rgb(119, 119, 119); font-size: 8px;"><img style=3D"hei=
|
||||
ght: 58px; width: 59px;" alt=3D"Kunst Koop" src=3D"http://mailinglijst.eu=
|
||||
/klanten/11909/Sjabloon/kunstkoop.jpg" width=3D"59" align=3D"right" heigh=
|
||||
t=3D"58"> wil je niet langer door artolive geïnformeerd worden? Klik=
|
||||
dan <a href=3D'http://www.mailinglijst.eu/nieuwsbrief/edit/?e=3Df00f@djc=
|
||||
bmachines.nl&c=3D9856&l=3D100549'>hier</a> om je af te melden. <br>
|
||||
kreeg je dit mailtje doorgestuurd en wil je voortaan =
|
||||
zelf ook graag de nieuwsbrief ontvangen? <br>
|
||||
klik dan <a href=3D"http://www.mailinglijst.eu/r=
|
||||
edirect.aspx?l=3D154054&a=3D10374608&t=3DH" target=3D"_blank">hier</a> om=
|
||||
je aan te melden. </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- MailingLijst_code --><img src=3D"http://www.mailinglijst.eu/imag=
|
||||
es/10374608.109906.aspx" border=3D0><!-- einde MailingLijst_code --><p><C=
|
||||
ENTER><SPAN STYLE=3D"COLOR:#d3d3d3;FONT-FAMILY:verdana;FONT-SIZE: 10px"><=
|
||||
HR SIZE=3D1 STYLE=3D"COLOR:#d3d3d3" SIZE=3D1>Deze e-mailing is verzorgd m=
|
||||
et <a href=3D"http://www.mailinglijst.com" target=3D_blank class=3D"ml_li=
|
||||
nk">MailingLijst</a></SPAN></CENTER></p></BODY>
|
||||
</html>
|
||||
|
||||
--_=aspNetEmail=_5ed4592191214c7a99bd7f6a3a0f077d--
|
||||
624
lib/tests/testdir2/Foo/cur/mail5
Normal file
624
lib/tests/testdir2/Foo/cur/mail5
Normal file
@ -0,0 +1,624 @@
|
||||
From: Sitting Bull <sb@example.com>
|
||||
To: George Custer <gac@example.com>
|
||||
Subject: pics for you
|
||||
Mail-Reply-To: djcb@djcbsoftware.nl
|
||||
User-Agent: Hunkpapa/2.15.9 (Almost Unreal)
|
||||
Fcc: .sent
|
||||
MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka")
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="Multipart_Sun_Oct_17_10:37:40_2010-1"
|
||||
|
||||
--Multipart_Sun_Oct_17_10:37:40_2010-1
|
||||
Content-Type: text/plain; charset=US-ASCII
|
||||
|
||||
Dude! Here are some pics!
|
||||
|
||||
|
||||
--Multipart_Sun_Oct_17_10:37:40_2010-1
|
||||
Content-Type: image/jpeg
|
||||
Content-Disposition: inline; filename="sittingbull.jpg"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
/9j/4AAQSkZJRgABAQAAAQABAAD/4QvoRXhpZgAASUkqAAgAAAAIABIBCQABAAAAAQAAABoBCQAB
|
||||
AAAASAAAABsBCQABAAAASAAAACgBCQABAAAAAgAAADEBAgAOAAAAbgAAADIBAgAUAAAAfAAAABMC
|
||||
CQABAAAAAQAAAGmHBAABAAAAkAAAAN4AAABndGh1bWIgMi4xMS4zADIwMTA6MTA6MTcgMTA6MzM6
|
||||
MzcABgAAkAcABAAAADAyMjEBkQcABAAAAAECAwAAoAcABAAAADAxMDABoAkAAQAAAAEAAAACoAkA
|
||||
AQAAAMgAAAADoAkAAQAAAGsBAAAAAAAABgADAQMAAQAAAAYAAAAaAQkAAQAAAEgAAAAbAQkAAQAA
|
||||
AEgAAAAoAQkAAQAAAAIAAAABAgQAAQAAACwBAAACAgQAAQAAALMKAAAAAAAA/9j/4AAQSkZJRgAB
|
||||
AQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwc
|
||||
KDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIy
|
||||
MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCACAAEcDASIAAhEBAxEB/8QAHwAA
|
||||
AQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIh
|
||||
MUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpT
|
||||
VFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5
|
||||
usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAA
|
||||
AAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEI
|
||||
FEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm
|
||||
Z2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK
|
||||
0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDq77xdrX/CQ6laRXjRxQTF
|
||||
ECovA/EUg8Sa6W/5CUuP9xP8K5yWQnxjrw9Lwj9BWjkgZHFAG6mu6yV51OXP+4n/AMTUq61rBB/4
|
||||
mU2f9xP/AImsJJTuAJFW0YDnfmgCTUPFGqWFq882p3G1eyqmT/47VfRfGGpawkgGp3CyIeg2cj1+
|
||||
7XK+O7zybCGMNjzHyR6gD/69ZvgG8zqU67vvRZH4EUAesJe6m/XVLv8ANf8A4mpf7Qvl/wCX+6b6
|
||||
uP8ACs+ObKdaeh3Hg9aANTw/4gurjxTLpU7tIv2cTKzHpgkH+n5UVheHGI+KWzJwdNP/AKFRQBzD
|
||||
7f8AhMfEDEHH24j/AMdWrs0oCkDrVKJs+NfEsZ+79u/9kWrd5GqKTmgCstwwkyT0p5uzu61mOzbj
|
||||
zSFn3DmgDB8ePLPe2MEQZyykhRzk9/5Va8D6Vd2Mz3d3CYxJHiPd16+la0hhMybkUvxhj1HWr5uM
|
||||
uB0wMYoA3YJARjvV+DBPasC2lYsOuK3LVunWgCLQRj4sIPXTGP8A4/RSaF/yV2P30tv/AEOigDmY
|
||||
QD408UE9ftw/9AFXpv3iFT9Kgs4t3jXxV6C+H/oAq5cxkMcCgDFltXVyMVVv7iGwtzNcNsQfiT+F
|
||||
a8jbAxdgFAzmuTZZfEV81vG+xTyX/uIPT3P9aAIBr9vdNHcQI/lxk5DDBrfsLuK+jE0MqupPOOx9
|
||||
KzNY8L6fbaaYrdGXb3BOcnuT+FcpodzN4c8RRRyylrW4IDE9MdM/UUAes2wbOAK27PhRms6CJlwc
|
||||
VrWowRkUAV9CP/F3YffSm/8AQ6Kfo+P+FuWp9dLf/wBDooAxrH/kd/Ff/X8P/Ra1evUOcgVW01Qf
|
||||
G/izIz/py/8Aota2LqPK4xQBxniWc2mi3MxBGFA/Mgf1rmtEF/Z6HNqMNuzvPnY+7G1V6Hoe+T0r
|
||||
qfGmnT3Xhm8WNWJVQ+B/skH+lUPBt3d3PhuzXyBM6xBY0YfKDnALewxmgDE1BfEDaPaXNzMRJPIQ
|
||||
+TjCgDHb69u9ZGt2Us2lrdNDtMLAgq27Kng84Fd74qnaMwWB8qWRTnzUcfePGSOx4ziuf1kzT6S9
|
||||
tuRHlVUG5sDJOMA+lAHofh5/tvh3T7k4ZnhXcfcDB/UVuRQEdqzvDelPo/hywsJGDSRRjeR0yeTj
|
||||
2ya3I8/3aAMXSU2/FmzJ/wCgbJ/6FRUunf8AJV7H/sGy/wDoQooAyNJXf448XYPS+X/0Wtb8ynyj
|
||||
0rm/DIll8W+KDKQ0pvF3FehPlr0rvINMzbfN8rsc7upH0oA5ie3mktZSI1ICn5W43e1ec6ZrDwax
|
||||
facIj9liUNtUcgE8j0IzXrHiqS20rQJbiadoyBsWQjc2T2HvXnvhbREuzeXTbvMlfILcsF6D6jFA
|
||||
GJr+pWE1ymFkwFzhlwo+i1xevazLd3Fva2+UiQhh7kdPyr0jVfA8t0BeXNybe35UK2EJAJwST/QG
|
||||
uS1Pw7HYalbKHUIxYxyDd8wHUnNAHsnhXVBrGhWkrBlmEYVww6sAATXQInA5rn/AOZtIa3mQHZI+
|
||||
xwfvAnJ6d8n9a6yazEKhlzgUAc1YAr8WbH302X/0IUU6xBPxYsSe2my/+hUUAV/Bdj5fi3xWJJDJ
|
||||
JHeopY8bj5a5OK9AUArwARXEeFjjxh4xbub5f/RYrsIZgJhGTjcuQMGgDnfHiwnw1KJoVkUuB8yg
|
||||
hfeuZ+HemTLpjx3OCZNzKUbPy54/Sut8Z263OlJE1wYgzkkjvgH86yfBb+XYWuIGiEithWzn9aAN
|
||||
loTcO0ctuGjV9oMg5JGCSOOnp9K8/wDH1qH1iERrukRAqqB3Jzj9BXpsk6F+oyCuRjJ54rhNcg+3
|
||||
Ge5XiUSL5ZGc87sdPagDQ+HlvJHoAdo9h85mUY7dK7WSRCoB6HiuV8IiW10JYs7yszDJ7fN/k1tG
|
||||
Rpb4xj7qpnj3Iwfx5oAwLMgfF+1UHI/suTH/AH3RTLJNnxltx2Olvj/vuigB3hgf8Vp4vH/T8v8A
|
||||
6AK6aRWFk2CA2CPSua8M4/4T3xcp/wCftD/45XR6q32e1JjUySCRdqA4J3HH9aAKHiJTceH4mliK
|
||||
r5e5lDfMpx2Iqp4eQR6Zp75Y4jX7xyfTn8q29djjbS/LMqxYGFdugNZWlskOh2pKgYj2AqO4OB/M
|
||||
0AW7+NLQ3Fwi/O6hsk5yRwOO3WuS1qGJtNuvN3iNJkX5e+EIxn8f1re1e4ubq8jSOMiBArZJ/wBY
|
||||
xOcfQcZ+tVNTsYh4dnjmG9PMJIP8XYUAQ20z2Hg6OeJGTYQzd+N3Le+RzXQ6TGwtjLLkuxAy3XAH
|
||||
f8Saw9Mlt7vwsI4yZI9m07xtyM/y5rqodqxIFAIx1oA5iDj4w2ZHfS3/APQjRSw8/GGzx20x/wD0
|
||||
I0UAee+I/GV/4S+IXiAWlvFKJ7gM28njC+1Ubn4v6xclC1hbAq6vwzdjn+lXviB4X1O88b6lPBYX
|
||||
EkUkgZXWJiDwPQVzH/CH61zjSbwj1EDf4UAbV78YdZvYPJbT7UA+7HP61HbfFXXLW3SFdOtSqZ67
|
||||
v8fesg+Ddbzn+yL3P/XBv8Kcvg3Xc5Oj3x/7YP8A4UAaY+KuvIQP7PtM5JXKt/jUF78Udcu7F7WS
|
||||
ytEVv4grZHPB61VPg/Ws/wDIGvs9v3T/AOFMPg7XcHOk32P+uD/4UAWLb4l6vb2zQJZ2m1gP4WGC
|
||||
FAz19q17f4va0sSobS04GB8rf41z3/CIayOuk3g/7d2/wqRfCWr8f8S27/78P/hQB33w78Q3fib4
|
||||
jR3l3HHG6WTxgR5xjOe/1oq78JvCmo6dq8+qXUBhhETQqJAVYsSDkA8496KAP//ZAP/bAEMABQME
|
||||
BAQDBQQEBAUFBQYHDAgHBwcHDwsLCQwRDxISEQ8RERMWHBcTFBoVEREYIRgaHR0fHx8TFyIkIh4k
|
||||
HB4fHv/bAEMBBQUFBwYHDggIDh4UERQeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e
|
||||
Hh4eHh4eHh4eHh4eHh4eHv/AABEIAWsAyAMBIgACEQEDEQH/xAAdAAABBAMBAQAAAAAAAAAAAAAH
|
||||
AwQFBgACCAEJ/8QAVhAAAQMCBAIHAwYHCwoGAgMAAQIDEQAEBQYSITFBBwgTIlFhcRSBkRUjMqGx
|
||||
0RYXQoKSssElJjNDRFJicpOi8CQ0NmNzo7PC0uEnNVNUVYMJRWR08f/EABQBAQAAAAAAAAAAAAAA
|
||||
AAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDqjNudMEyw6lvE3HEqUnUS
|
||||
kDSmeEkkCTB241Aq6X8mgSL5J/PT99DPrX3S232GwohPbp5/6v8A70PMsrSuwYKgkgtjiONB0Ovp
|
||||
lykFQLhJ8+0Fefjoyh/6/wDfFBctsJWkpt0wRzSK9ft7cwVMNmf6IoDOOmjJ5/lB/SFejpmyeT/n
|
||||
I/TFBdFqwACGGR6IFOWbRhZ3Ya349wUBgT0y5QP8qT+n/wBq3T0x5OPG8A/OFCy3tbZCRpt2j+YK
|
||||
cotLMjvWjM/1BQEs9MWTR/LUn0VWfjiyb/72hki1sw5q9kaHL6ApZDFspQHsze3DuCgI6umLJqUg
|
||||
+2Eg+AJ/ZSR6aMmj+UOfon7qozjTACUhlsCP5opo402AQWkeumgIK+mrKAA0uuK8e6R+ykj03ZU5
|
||||
JePx+6h6gaTGlPHmkUwzDjlrhDKVvtIWXVaUIjj4+6gKKOm7KZUQrtU+YCj/AMteHpuyvvpbeV7l
|
||||
f9NAHEc3Ls8ed9iYZLTZIAI4+vpRAy1i7WKYS3dhtIDghQHIjiKC9npwy3O1rcH3K/6a1V034Ef4
|
||||
OyfPqlf/AE1WG1KKxAAHhFO0LgTCfhQTf47cGPCwuJ/qr/6a9R004as7YXcn0bX/ANNRLbqhsAOF
|
||||
OWnVgEnh4UEm30wWbhhvBL5e3Jpz/ppwOlMLSC3lzElT4suD/kqHN642YT9tIu3T7h2UYHnQWW36
|
||||
S9YleXcQA8Qhf7U05b6SMPXiFpYGwfZfu19myl9XZ6leEkR9dVBy7XEFXLcUN+mPEn7VnCrplZQ6
|
||||
zclaFeCgAQfqoOqbO4L6VBxvsnURrRqConcbisqo9FOYE5mw1zGENdkLllpZQSCUkApO/wCbWUAY
|
||||
63i9N0x3v5SkHy+aqj5QBXhFqRzbFWrrjrWm5a1ER7Unh/saqOSXP3Bst/4pNBbWhLY1nvCt1NyZ
|
||||
mRWtuoKESCOdKjSOB99BiEjYHYU4ZQpKpTwpArTxpVt8g7RQPmlQkpP2UoFE8VkU0buEkK4A+NbN
|
||||
rCzHDagfQCmN69TtsPjTRLoBA1b0oHFaZPwoH6IPODFJPwOG9asklJJ228a8egp9aBspcHhvQf6T
|
||||
b55eZHWVu6m2kgISDsnaT76Kt46hlpx1WyUJKj7q59xzEFXV9dXC1krccK9/M0CybtYUTrMnj50R
|
||||
uiPGLl69OHBTaWG2isgjdRkffQgFyoqO441eOiW4WM1W8D6aVBXpFAfGCVGQmnDZO07jmaYW74G0
|
||||
yKfNuJUBB50D0ISAIAiNyK3UUkQIpDtgUhAAIApNToAKQACedAotaZgCfOvQrUCAIpsVAma31kEE
|
||||
bbUG60Dwn1oW9O6S3h2HqnYvK291E1SlEkzvQr6f1qRg1gVHc3B4+lAaeq2rXkFhcz3NPwcXWU06
|
||||
pzqnchtQ4CkBQjT/AKxVZQDXrpOEXLJO0XYj+xFVDI6lHAbEeLSfsq1dd2UPWyxMG7HHhPZCqtkX
|
||||
/wAhw9Svo9gj7KC4Wx2MCKdt7ncTTaxbGmBz3p4hISNzHhQJrQSYnjXiUkQOYpZzupJJpFK5g7n3
|
||||
0ChQSkbxSluSFDfam5WACokj1NetPAqA29KCXb0HdQ9TSwQlSduVR6XBpJnetkXWgCTQSTQCRpMk
|
||||
Uk8djzpsm9ExNaOP6pOqBQRWcblNtly/eI3SwoTPiIrnbEXAl7j4UaulrEHLbJ9z2ZEuKS2fQneg
|
||||
Fevy62ASQYoHCHkqXsqrl0U3aWs52mpelLgU3v5jah20/C9zO/hVmyc8Bj+HjfvXCB9dB09bq0mD
|
||||
zqQZUkAVDtriOdO7ZzSYO8UEoHBw3r0KB3mm6XUxERWdpO0UDhJBVvSulKh9kU2bUdzvvSzKu9tI
|
||||
mgXShOjfaTQk6xZ/cOw4GLk7/m0XlqlGxEcCDzoP9Y1KhgNhI/lR/VNAWuqGpJyG3pBHdVPn84qs
|
||||
rTqgqJyKyCI+bXHp2qqygH/XoQEW9i5M6r0CJ/1VVnJKP3Aw8QP4BvYf1RVh69K1lm0BMgXyQPL5
|
||||
moPJ50YDY+TCP1RQW+yRoaC44HhSygNXe99NbW7lCUKA48K3cdgbk8KDa5IAMcKj3XiklIMTSrj0
|
||||
zCppi8STwNAo5cEo4RXtq53pn1pke8NIUR5UoydWoA+lBMdrCdjSLr5G8yfCmJdWE6ZpNx2NiZoH
|
||||
4uZjlThKitJ35TUOl4DntUlZOgoiaAe9Nl+tnDbS1QAULdlzxHgftoMLdm4VzAmKKvTsvsrywUqS
|
||||
y8y42seBBBB9xoSWTa7hVwocGmyrh7qDZpUKFXzoptG77NdoFkwiXD7uFUvCMOu7+5SxbNLccO8A
|
||||
TRd6Nsq3eB5mbVezq9l7ThsCTEUBcQvhtuKdWy0qVJ40wS4NqcNOTvA+NBKpMokRSiFKnx8opo07
|
||||
KAAYNOGwrko0DlB/JO005Y0gTNNW9pkzTm3SDJJoHIQhIlKZ50IesgFDLdgYgC74/mmjC03KaEXW
|
||||
WBTlmykH/Ox+qqgJnVAV+8S3hMbOA78fnVVlZ1Oio5BbnkHI/tVVlAOOvSIbtlSN8QSD/Y0yyayh
|
||||
3Ldg4BBNs37+6KedfERaWqo2+UE/8E1FdGdwF5Yw0kgzatmPzRQWdhnRClCTWPAEHeKkENodbSpO
|
||||
xpBy3IJJjegjFwjh6U1eWSoxw50+uGQHPE86j7qEqiY9KBstYSe6B61gfLapB5QaaPOQs77V4twR
|
||||
wNA8TcFyPKsdd25UyS6RBAivXXJSDQOm1jyinlq/CoSY22qDDh1AVI22mUqmNqCpdI+GXOZb9Ng1
|
||||
I9jZL+qNiVSAPiKiMhZQFixcHGGgkvJPaJJ4JBolsKaTdO9zdTcKV4CdqalLSrtevcQEkeVA7ytl
|
||||
/CcOm7sLVKFugb+VSt06k3WgfSSO9HKss30NsagAkAd0GminCXSr8onegfNKSDO/nJp22tCSDUYF
|
||||
xEJpVDkkwDPrQTTCoGxBmnjDp4KG9QVu4rVxjepS3WY1KVPpQSbSiT508tBvBImo9hJICt96f2yS
|
||||
TwIigkWht6UHes8ojK9mJE+2Dj/UVRhaEpn7aDnWhH71LP8A/uj9VVATupmZyCiePzn/ABVVledT
|
||||
OfxfszG6XYj/AGyqygHfX3A+TrPmTiKZ3/1JqvdFX+h+EqPO2TU91+NrK1Mf/sER/YmoLopSpWR8
|
||||
II/9sPtNARbNSdAKdzHCtnY0Eq4zypnbFaEAqImnRUFt6p93OgjbyEEn6qhrxQUo+FTGIp7m23rU
|
||||
HeGKBi6E6orRcRtWyiSoxWp4ERQeoAI3NeLSnnW7Q2341s5skcOMUCSEpMk08to0gA/Gmu4HCvW1
|
||||
qoPMQWtOJIDR/I3HiKyycacfU4o86Y4qpxBcutiUphIpLLy1LGtSN1RsTwoLS0SQpx3uoG4HlSHa
|
||||
gq1JMSa9ullVulJAA5+dN0wYiKCQbcJ+lNLoVzmmjKoEmndudSoVAHpQOrbVq3mIqWtYME7Co9oE
|
||||
xHAeFPWVqCQY250EsyuBsSPWn7C1CI4+tRdq4F7gjzFSduoCBHoKCRYUYAG1CHrQJjKFqqB/nqf1
|
||||
FUWbck96NO3jNCXrPmMm23Ha9R+oqgJPUvM9H6PLtR/vTWVt1MkoT0eMaQAVpdUYPPtSPurKAb9f
|
||||
ZQNkwnf/AMwR/wAA1GdD7QOQMHUeduPtNSfX3H7n2y4mcSSPT5g1G9D69XR9gxCeFvHHzNBczGkR
|
||||
tWqXIBjlW4HdO0U1XqQsGOe9ApcAKTHEGoe7tyJqWWSBv61roQ6nvCgrD7cEyPqpNCYn0qavrMhW
|
||||
w5U1NsYAAoGLAEmaUcAinItSZ8R4VjjJ0Qo+tBHK+NeoI0kVXs3ZptMIS42wk3FwB9EGAPU0KMWz
|
||||
zma6uCtm67BoHZLSQEx6nc0BdzRfBq0UhKu8BwFI4BchttK1EyrlPCqjhmJOXlrbquHUPOrbBJJ5
|
||||
8+FaXC32NTguLi2MmCYU2fLjPxoCel0vDUFFQPCnSAQaH+Vc625fRhuKpbtX+CHAruL+6iC0QtKV
|
||||
JUFA8CKByyFE8KeI2CTzpG2E8h8acpQSRCRH20DxlRMGPqp2zOkJB2prZtlRIg/Gn7bSUwNW9A+t
|
||||
BAO4B8Kk7UeJmo20SUkmeHlUnbK2G1A+aiZjfxoS9Z8k5Ltwrleo4f1VUV0d6IG/OhR1mkg5HZkz
|
||||
/lqP1VUBI6l5H4vmRJOzvHl86aykOpOf3iEb/Sdj+0rKCgdfUj5NtxzGJI9/zBqP6Gkj8XuDDn2H
|
||||
/Malev6lHybakQD8oNg7f6hVMehVIV0dYMqJhj/mNBcm29Q24U3u2gDtUohvS2dtzTO6H+IoI93d
|
||||
PmBTcq0xBmnDoM7b7U1KZUQCBvQLKcQtuFRSCwAmBFIvBxJ2MGaVAUUhQIngaBNUg7kVV81YoUhV
|
||||
rbuweCiOPpUxmC9Rh9it5Su+dkDmTQ9Nz2q1PrO5JCd5JNAxxyztBaOOXyktsNp1rUefqaH7dpc5
|
||||
hvlDCLJabVJgOLG5H2D0q7YhhV5nDFrfBLYrTbA9reOAwAkHYep/YKL+X8qWGHYe1aWjCUBtMDQn
|
||||
c+cmgDtvlq7scEQ2grTdtnVqKdhVXx3EscYTpumoIkdogbKHmDXRWJ2NotlxpbjRUOYVJ9N6G+bc
|
||||
Mt3EdghaFx9IAUAXfxB25bIe0haOHd+qiR0QZ1dRdIwXFXdbayEsOqP0T/NNU/MuXH7Qret0KKUn
|
||||
dIE/CoCxUW3UnfcyCOINB2BaiRI8Nop+0gxP21R+iPHF45gCUPq1XVsezd347bGr8wlQERQLWp0r
|
||||
Gw41IoAKp2k0xQkg/Qnwp9bSYJSZFA7YSfjUhbN6uMAimTBIUCakbZW44UD1hkJAoUdZ9pKMgNqH
|
||||
H25H6qqLTR7oPOhZ1ngD0dSOIvGz9SqC4dSZJGQyeRU7H6YrK36lBH4vk7flvQfzxWUFE6/oBwm1
|
||||
Vz+UWh7+wVTboPSR0cYKkCf8nk/pGpHr/o/e9aLjf5SbH+4XTPoOUk9G2CyCT7PwA/pGgvaR3eAN
|
||||
M71E8qkkJBRsDvTa7QNBFBCuoHGKaqbGuZEU/udIERvTbUmSNO9A0ukjSTG9Nm1BC5PDnUg8pIHC
|
||||
ozEn0tMLcCeCSfqoB9nrElXOLi2bJ0IOkeE86rVzcBDKnAoBLRgCNyfL/HjXmO4koPaoPaOEmf5s
|
||||
86aYHbPY7mmywdk6mmyHbjwKZkz60Bl6NcGRhWX0XDrWm6vPnnIHeAPAe4VZx2iEFSVrKYgpI0/X
|
||||
UJeYuzhrae0Qt9SANmwdIH7ajh0j4WrW204dYEEE8NqCXW6+7dP2yUMNnTKdZJ24cao2ZGCVO9ol
|
||||
tzcmUnh/j7qXTm3tbC8xdcKQnsWRJ7pIUoq+qKqeMZ3YUp9xxxsJcUCQngOJ2+NAmptDxKHEqSCN
|
||||
vGhnnCxOHY8sISEpe76YECecfb76J2GXDV8wLj2a4DDhlDikEA+h8Kr/AEk4St7Chdtd4sHUI4xz
|
||||
Hw391At0JY38mZntmnHdLV38y4nlP5J/x4102ywVAEbiuLMDfcYeauGjpUlQUDHMGu1MpXPyll+x
|
||||
vkbh5hKifOKBZtuCRoiKWbQoGYpx2KkmYO/lSiGjI2EeYoMaQSSDTplJA2rxtJG1OmUgD/tQKM6h
|
||||
50MOsv3ujlwgzF02ftopJMAAxt5ULeslKujq5I/9w2frNBb+pM6Dkfsuep8n9NH31lJ9SUfvOBgb
|
||||
F/cf10VlBVuv7q/B61g7fKTW3/0Lpn0IjT0d4IN/82HPzNO+v8Vfg/apkR8osn/crpr0HEno5wUE
|
||||
cLeJnzNARGSCmCD8ab3IBkAcqctjbhwrV1oqTMAUEBdpABnemRAHvqYu7WASQDNMOw7xAKfHjQMn
|
||||
YjvDaq/m64RaYFevcNLSj9VWp+3IEyKo3S8U2+RcSXIkthI9SQKAKXV4u5ue21BLY3JngImrb0S2
|
||||
TrmCXuYVuKb9pfUkr0nZpA4Ty3maHDSXsQLOHW/8PdOJZbHLvH7AAfjXUOW8rWWGZbssMTZtutMt
|
||||
BGpbQMniVRE7mTQDzMfSJctYXcpwbLq30MoSFP3KinXO3cQBJ9SRQpu/lvE8WS+jCQXXVAyylaUu
|
||||
T68TXQubsNNpZK1YwLVsDV3mSSPSFCoPJeTH3rv5dvO2UiYtu1RpKxH0o4xvzNBHXuX0WnRpcGzQ
|
||||
supWlSyqZMjccfGN6FGG2+Iv4u5cW+ALxNLSoaZUfmwoc1Abq9Jrq3GsPtrbI12gcFNHaI3oJW9q
|
||||
7Z9qbEpSpapg+NA2ezHnJ4rZxLCrZLVshHYWzdutBXPECCoAjzqRtlKxNpxp22U0tSTLS4kCPXen
|
||||
+XLW5xR2EN3iXE/SCmwUj3wD9tTSsHLPdUtK1cYiaDnq1bXa4jcWLuy23FAe7b7q616v1yb/ACFa
|
||||
JMyySg78INc19IViLHNAuG0hPa94jzTxo79Vq/DtjiWHDcJWHk+iuXxFAXrls7xO1etJkCCaevNy
|
||||
NXOvGGxG6QDQaIbM8AactohMRWyWyD605aSInwoGwTIgCD40MOsa3/4cXh8Hm/1qLSkhMqNDHrHN
|
||||
T0Y38Dg43+sKCd6k4H4EHxDj36yKyvOpTIyZpJPF79dFZQVLr/T+Dtt4DEmf+Cuk+g1uejfAzEk2
|
||||
wn4ml+v8EjK9sYknE2Y8vmXKT6BQ4OjTAyT/ACfh+caAiBoDYCaxxOlAGnjSralQJg7V4+sdmBtQ
|
||||
Rd2gaZM7nwqLdShCzE1NXS2wmJmot5CSdSQregY3KthE+kUNOnjtF5Hf7PVHatlfoFCim+hOkwDI
|
||||
86p3SRhyr/Kt/apaKipklIHGRuKDmjKV81ZZuwi6cWSlNzpE8BtE/EiuqLbMPtFi2WVDVA2rjXEy
|
||||
5bX1skApW2AQPA6ia6awJQNvavpSZWgKI5EEUBLwmwbxFSHMQYZd098FSQQD761GMWuK45eYPhYT
|
||||
cKsUJNw4ncIKphI84BJqs5hzKcPy4+q0lVypISNIkk8gPEkmKnsuZCvMHyEi3sMQFnmC6V7Re3Rb
|
||||
16nFcUnyTwHp50Epmxm3ssnrYeUe0uEHSIoIYg0cKS9cOW5Uw1ClqHEJ5n1q59JWKYhh7tthN0+u
|
||||
7et2NetKY1pHExyoft4lfYnir1u6sPWL+mUqT9HxE+HxoLhaXS2sND9k5rbdROpJ2INa212OzK1L
|
||||
nn3qhMtMPYU67hThKmEElon+YeHw4e6nFxPblQBKPyY/x5UAt6VLwPZoba3DYbn3knf6qLXVGbcV
|
||||
e4u8QezS02kEjmSTFDHNuXcRxnNDKrG3U4SnRJMSZ/711B0I5OcyhktuxvA37a4suPqRuJ5CecCg
|
||||
ujoEQfCtmOI2rZ0d2vEpEbcKBy2AVcvfS6UgbDnTNJPnSrRK5UmRQKqC9ESDvyoY9Y4q/FlfiQfn
|
||||
G/1hRPWogaYihh1joPRhfEj+Mb/WFBMdSon8DgDyL8emtFZWvUnCRlFYSRIL0+utP7IrKCr9f3/R
|
||||
q2PH90mR/uV170EKKejLAxAP+TbfpGvOv+CMsWpPPEmY/sV1r0Bk/izwPcx7Od/zjQEhlRUNk+6l
|
||||
XEymY5UmyDp4x50uggpHemgj3mZSSRA8aadmEqAVFS9yfmzHhUU8UzvyNB4tpBG4NMLy2YeZWFN8
|
||||
QeNPA4QdMkT9VIvOHSUhQ3oORulXJl7a5tvbq2tyq3U4FIQBvBAO310bMBsQcKw+4ZIIS2kKSR5V
|
||||
KZ9sPaWm1WqW37i2lSxH0gBBFUTLeamLW+Tg97cBt9StSEcAB/N9aAws4LgLCbTEbrSi3aWLo61d
|
||||
1Kk7ifQ7+6vXeke2uLdCsvYLf46tThSk2zKuzBH85wjSBVeTeu3rCMPNom/tnFauyUdKfzvETyqb
|
||||
fubpFs2wthtCUCENsSAPACKCk50xPMvt72L4nkt4Xa2SygtuIUkIIjeFHkfKh5huMWdk+W73Bb2y
|
||||
UDu6WSpER4iYq45rwS9duV3Nz7Ssap+ceJ5cAJquM9o2QEIJ7wkKPCgnsHxCzxa1F0w6haSkplJ5
|
||||
UjdH5tdwe4gCRJj/ABzpo0lq3X2rbKGVLBBKe7J9OdUnpQzULWx+R7R3/KHk6VkH6CefvNBMdEmc
|
||||
13PSMm1u3W12C3VBgFIlJ8Z84+uuvLIzaoKtyRXzpy7fXFjcpWypTa5ACk7Eedd99HmJt4zlHDbx
|
||||
D6XlKYSFrTwKgN/roJ1xHdFY2J3mt3EmIrUCO7FAqhAVsRtThIAiCD5UigGCZO1bpSqNiaBR3SpH
|
||||
CCKFvWNI/Fne7TLjY/vUTl6tJoXdY0KPRnfGP41sf3qCb6lP+h7h0gDW8J/PTWVp1Ilzk59E8HXf
|
||||
tRWUFX6/5P4NWoKhHyizt/8AS5SnV+bDnRpgaU7xb8PzjSH/AOQNUZfsk+OINH/dOUr1eFhHRlgg
|
||||
3nsJHl3lUBONvGxTBitUthPGZpVLx3kztz51p2xJ2igTuWu6ajLhvjvUs8XltEttqWI3IrZGFJWl
|
||||
K7l4hSk6uz578KCtLQrXEz4RUknAHT2a1uaFkayI29KnWcIsrZ/tN1rSJgqmo1u5vLpt1p1pTUPE
|
||||
srk7jjvQBvH04hhuarxzELlaGn0Q2hHBJ3gTXOefrwt5oadUl1p9h4HQvYxqkGuoOmF+1U0bJ25Z
|
||||
aeWsKMqEq8QJrm7F8PcxfPTLKy12LSzD6mtaFJBkBQHHwoDBaYld4c004SpbRAKVDiAeVWFrPNi1
|
||||
aAugrlOgEHcbc6jsv263sBZtbhse0sICXE6eGw5elRSsJtA4/rag8Y+6gaZqzheXlyvsSWrdHdSh
|
||||
KREcKgm8bt0IlxYTGxkQaZZvtW14u0D80yGwVRtJBqCxRy2ClOaU7bJkzvQSmI5mff2YgNjYeJNC
|
||||
/MTy38auHnFd7VzNWW9uDDVtbgLed2QmOE8SagMesfZr0NKJU5pBVvxJoNMLt3HvyoI70zw3/wC9
|
||||
dP8AQRnVzD7K3wm5uGlWSTpSeaT4ek0CMm4GL68asw65LpCNCB4kcffRz6JMAbX0iXOEBxsJsWUO
|
||||
IhI0qBH+PfQdDsrQ4RpcSraYml9AUkq2EVDW2GO2OKe0hsr1JIUUq2I9KmEPs6ezSpJWRtNAow2C
|
||||
jma2TqA7vCvGFOCQpPpW5PmRHGg8UCUgn30LusgI6Mr8CZ7Rs/3hRWOyBznwoXdZRMdGV/wMuNfr
|
||||
UDvqQz+CNwTw7Z0Ae9FZXvUiH70LiOAedjb/AGdZQC/riY9cZgybaXdx2QWnEGUKDaFIAUGV6tlC
|
||||
eYq1dX1BPRrgRHO3/wCY1WuubhNnhOUrC1sX3LhtF61LjiYUo9k5JMVaur4kDoxwLf8Ak5P95VAT
|
||||
A0rmB8a2w+1L9ylomATvHhSrTetMJBJPKp7B8P7BsFMdqrdRPKg8xK0Qzh6bdlYbkiSBvE71AnCc
|
||||
TxHGRcqAtrdteyie84ANtuQq6KZSmCoaz4mtVpII22oIe2wq2t2tMOOL3lajJ3qDzc37Jhb2lSkF
|
||||
YgL/AJm3GrhpUkn3VSOljMWDZawBy5xd4gOgpbQlGpSj5Cg59zRaP67w35beeZAUwp8gl1JO59QK
|
||||
p/RVgS2XL/GL5am+3LyWg2jVI4QBB47/AAqbxvNb2a1vMYQ+i11KSEQ0QVeEq4/DzqwdGjLlvlkL
|
||||
unHEPquH9bkAqRGqVAcyDvHlQXHEMETbu219aJKA+yhtxB5EDYn7KrGYLY21yEOoLayCFBSYNF+8
|
||||
w4sXFu+EqubNTRVp4qVpbBA8NzJk1TukZeMu4fdN4ZgaMTcaLIDIZDim9X0zMgwPf6UHP2e03Tt/
|
||||
bN2rS3HFpACW06lKPkBxppYdGmasQR7RiCRhtvpK4cV84QP6PL311ecm2jFs0xgybSxu1sIcDxZk
|
||||
lJ4jYgmD9tVvFsoWiQ45mDHVvW8QvfsEQDChtufpJI35Gg57Tl/C7ZDjdtcsWz7SE9s65K3EhXPh
|
||||
z4eFVvNtrg15b9ph63W32nEMtoKCS8Oa1K4DyFdE57s8k4060zqQbi2SG0sNgw547jYgcffQr6RL
|
||||
Sywu6ZFtbN2zCXO0LaR/CECdz/jlQVnLWH3RxRoWoLEaVLUtUwf5225H30XeifGlYJnO0tn0ds3f
|
||||
/MG6UgJgkyD6HhVUyW3b3l+wEpSxcusjSDJC522+HD0qy4Thq8VzrY2QcKmRqaeCO4oECAoBQmAR
|
||||
QdTWTYCAVLCieFe3WFWV4pKnWh2iFSlSTBFQfR85eHD3MOxLvXlkoNqWf4xP5KvfVwZbIIOxmgi1
|
||||
4elgSkrIPDypqtlQE1ZnW0qbg7yKjLxlTKVAJGmOJoItepKR3gDFC/rJKJ6Mr2AdJcb3P9aig4VE
|
||||
bDj4UMOsekp6ML2SILrUfpUEj1JmyjJrytiFOOnj5oH7KyvepPIyfcTzccP95IrKAb9dN9p7J1gb
|
||||
a+urtkXjPefIKpDTgIMbE93iKtHV1LKujjL5fC+z9mIMeSlVS+tZh2L2HRrZt47Zi1vTiTQKO11w
|
||||
OzcgzJnh4mip1YcNb/FTgDrqQrVbE7jh31UBOsLcdhrbZ0JiEDnUvbM9miIk86SsdK3VqaVqbTtE
|
||||
bTzp4J5UGq0kxArRxsngDTkkgbcBWizwmRQNXGjvFBHrLZYxXHUYabJhbzDetC0gx3jEb8uHOjq4
|
||||
ElMzNUHpcfScEFmq5RbofVC1qXpgDfY+M0HI/YYtl1a3bmyt7C3LnZ26zHarIMaoEmOfhRKyM8nE
|
||||
7Fm4tihLaFKWHNMBKtCiVkb89yKHXSo7a2+IlouqKWkKTDjsjURsB5wZnzq+9ByG2cCtmlKI1rB4
|
||||
mSSOBI5GfhQG51SMLwhd9d3wWtNqhaU6YTAG+mPEmn2AN2tvZm9cCW37sh1ZPiYAHwgU6Yw9rEcv
|
||||
27ToAOgBJT4D9hpZGHNuhDK4T2YiOQoEsVtnlLtvZ7QOuIUdLmqNCSNxHP0oZ490dXl5jryPlJ9F
|
||||
m6kOOslZWFKPE7kxwG1EZnGGnMSvLNKoatEEvOzEE8BNKNOYa7iSk2zrbtwWwpxSSVGOUnnQU+wy
|
||||
Rg+HIQpNqhSkc1JGxoE9OeErv82Iw9huO9Ko2CUwkn7a6rv7VTiDpMeBP21zn0j26lZuvLy3e1Ia
|
||||
CVSonhI24eCaCjWtk0xcW5xC8RbtWoCmm0nvEJMgGOG9WHo4dxRzOthdXGHutoQ+oF7V3QF8IJ3P
|
||||
ED31EvM/InbEqZccvHNRURqCWwIjf3mpfJ79xdZywtCriXC+lxSNR7yAdyYMAbbCg6WbZXa4na4m
|
||||
2SEqhm4HLSfon3GPjVub2G1QVtbC6wxbau6FoInwqTwl0uWqAsy433HPUUEnJg7CKbvoC0qB4U4k
|
||||
cfqpFwlSjHKgg7pstrIKNPhAoSdZhSm+jW4kDvXLSfrP3Uar5jtGpmKB3WkXp6O3GuZu2p+ugn+p
|
||||
aQrJTp0gaXHUkxx7yT+2sr3qWQMjL8S69z/pJrKCqdfhR/B21bIgG8ZIMcfm3Pvq79WMA9DeXdWw
|
||||
9nV8A4qqf19Qn8F7NYBKxetD3FDn3Vaeq68X+iDA0L/ikKSNuI1E/toDCNKW+7tXjbknTMKB3pNR
|
||||
BRusSRwpG1bQlZdTqle5M0D8pIRIMzWi0k7kVs26VARuOHpWy9kydtqBvyAoR9PDdy6W0NBOlLJV
|
||||
KuAMnf3UXlQTx40JOmrHLe1vfYLllLluGPnSeRPn8KAA4sxhGY0IwXEbdAcaKSxfJRpVpJAjVzTx
|
||||
MHwFGPLGXLXA+yw6zh1lgobSswZKYG9B3GUWzCEXdmXHGFPoShKR/Bq8FetHnJTbjlip1/6Yuz9L
|
||||
YjvJP7aC9ZYYPyUyoOiQkApSQUp8hG23Davc2JxC3wi7fwlkvXvZHs0AgFR8idp8JpbKaAjCGwmS
|
||||
EqKQeaoP0jtxPH31LPJ7VlSRsY29aClZTwFdnkq3axBhab19IuL8FYUpxziQo8Dvy4VL4Szh7XaN
|
||||
WduGlIjVCCBvy4fZUi2T2imjsJmDSVuy6h59bjoUCrupH5I8KBtjTybTCLl8qCSlohJP84jb6656
|
||||
zk0FX+IS0pbhW2lKUnc7EzRw6QHpwL2clZ7d1KShIOpQG8SOA2Ek+nOg3iuFrxBV+Ge0S4HHXAAd
|
||||
+4Dtz5GgodlhTuLOXV9blt+3t1qTcIKtK2FA8DOxnl76neizALh3NTd4ppRJCVIIJ+jJ+HCvcq4h
|
||||
8nZdRamwLpecU4tLg1BaCd9W3Ab8TPCp/oVxC3s80Iw95d2lVwSlhLoEJgcoHODQdA2Dei1QmDJG
|
||||
9LJZ7N4vN7H8pP8AOrGNSedOAQU7+NB6l1KyIO8SKb3L4QstEwTBn1P214NFvreIJK+fgPCoyzWi
|
||||
/eXfPhaQh5SG0qVACdoMefH30Ek+4lCi2dwQTx50Detesfi6J4TeND6lUYbgqf7VsK0KSe799BDr
|
||||
VOLPR8pCjwvWgY9FUFr6k8fgSuP/AFHj/eR91ZXvUm/0GV/Xe/XTWUFc695P4NtJO6ResHj/AKty
|
||||
rb1YRo6IMvAAd5lZ9/aKqqdexKjgCIE/5Vbn+45Vs6ti9PRLl0Dj7Or/AIi6AqO6QtC1qjkBHE1o
|
||||
HCEKCdlQSJ4DwmtL3UGwtKgkpM1s22OzCn1pJMcOFBtgq1rtRJKlT3jBAJ8p4ipIokAz7qq+FYnj
|
||||
D+OutrtbVOFto2eQ4dQVJATEQdoPvqypdA3kEcaDCxJ5jehL0k4JbX+aF2D4Kn7tslsaZ2jaD4yf
|
||||
qovJeBA9aGGbcYUxmS4XcLW2GwopMDToHL12nagDeMWrOQ8Ut38Te+bfeShTDYSrWUwZUOW870UM
|
||||
MxBD2EX17bgFKrp4twdjCEkfZQn6VMHxDMuZrB/CXbd3tnA0u2dX88gkzqG26efjtRiwWy7DLhY0
|
||||
gr9ofQTp3nSdx+jQXTLLxUxdJVGpL6jtMQrcRPkR75qQQ+kKUkq3mq1ktSoeSlBQkttqEj6RKASf
|
||||
MST7wamXwWX+2EQdlDwoFApS7wmBoB2ptbG6TdvOOvsG3OzTSEFOnfz4+tN7fEW3seXZNmS0NawP
|
||||
yR51o9d3Vzj6mBZhq3aSfnCoFThkbwOAoIHON2HsXtbALWgBQVISd1KJgTwiEmR6VV8rgqexG9AB
|
||||
0WjzgJG0mpa5Wm4zViN8hQcRaoXJ0gAdmgAAEHeFFcnjO3KkujlTSbK+uX/4MMLCpH5Ox4e80Alv
|
||||
7O89rtLK0tlXK1EgISqEK27yp2HAbDyqU6MsLXhucEqWyHHGnNTbhSJIVPCCY9Kl8CtsYvbxzFuz
|
||||
bu7dLmi3t1PFGpO41pjhyEceNPMpl+zzkwzeKLr2IK+ilOlCAlJIgevPnQGFNwrsCofSArXA7x29
|
||||
Sokd1KiJ9K2QzpSNUwRxpLB22MPs3xqVHaqWeZMkmBQa4u+3fONWdqVrLdylL+gwWiBqTPiOHuNM
|
||||
ct3D14L9zswi1RcuIb1cVadp9Nqe4241h6HMQbMLQytao4KhOxPjwpnktl1rKNqHge2cRrcP9JW5
|
||||
+2gmrRsItgTBJ8KA3WzRoyUFJ4KvG5+CqP6GyGUgAcKBnWzQT0epIH8ub+xVBYepbIyQ2AQR8/Pl
|
||||
84Kyk+pPq/A10HcBbo9O8jasoILr1ScBRBgh63j4O1YerOo/ilwHURs0sbf7VdV/r0j9wkqn8u3+
|
||||
1yrB1Z5X0TYFwjslgf2q6Aq3X+bSPCeNIYchNywQ5JTBETtTi4SVWy4HLamuEhSElJM78qB1CGG2
|
||||
7VpCUpnvnkkePrUQ/cXjOYmbFAUbd4FSf6McR9dSmINLUJCCptB1KTzX4D0mqhmPH3MvZgZvbxtR
|
||||
sy+1bKITMLcCt/iAKAjhsJQIiYoW9Ilp+6V44yg3DhQIQ2qTJ2I08440TWHw/bJdSkiUzvQ2xxdy
|
||||
xib7rDalvF8nYctXCaAMYfjWJp6Q8Js8SY1D2xBCwkoIIOwE8eKZo34OV+zW0pK+1vS4QFcAttdD
|
||||
XGMYwu66SLG4xKzadvWLhDbLikFKgeB9QJPwotYez2YwxI4FDK9/zx+2gTynCb20WVhRfsQAmBKA
|
||||
hRmY33Kvqqw4mlCLRxxwEpSJgbk1XsHhl7D5b0Bu5fZU6Y4EylBnfcmdvDzq4ONpXG21AN8i4ZmH
|
||||
D845gfxLsBZ3LiV2znaa3FbniD9EAbRw22qw2bTFniV865dXLj4BUpK1Hs+E90UviDqm8UJgCFAH
|
||||
zBH3iorPVwm1wdy5ceLZdSGUKClJAUo7GR/jhQVxntG8v4ze3LTSHnWtB0DYFaiYnme9x58aQwNt
|
||||
bWT8XdSrQPZwkq8CZn6op1i/zOT20JVPbuBSTxkAEj7BS+VOweyhiDlwvTbuFxKlEcEgQf20AJuF
|
||||
Y1eW9vb2jtxpYK3FqZdG0ERPDxiiV0WYWu8xNGP3ilpdb7gSsGCqIkVBXgwDCMFQ40t5Srx5QZd1
|
||||
hSVRAkDkB4bVa+iS+YcuHMMR2kBHtACySdzvx5cKAj3t8pi1WUtqWoJkAVC/L9naWKbh1Sbi8WUM
|
||||
9i2QopWQYkDcDjvUvi94zZYY/dOphttsqJAnYChDlN22ds14xiS/ZXrm+cuWme00rUClPZpUBvJA
|
||||
n86gvrz1zieBui4T2NzeOIY7IHZvUdwPQA1dLVkW7DbKYAQkCqlhlstzMOCWOgDskru3gTMHTpAJ
|
||||
9VH4VeFp+cMAbeNBouNEBRG24oJdbFH/AIcpM8b5refJVG90d0bbmgp1sEk9Hze8RfN7fmqoJTqU
|
||||
6fwKfE97tndp80VlJdSrbLFyJnvu/aisoIrrzpP4OoVy7S3+1ypjqvuT0SYIkk7B0f71dRnXlSDl
|
||||
hKo4Lt/f3nKfdV0a+inByPyQ6D69qugM6BLShHEUyw7a4dQDMHantuklBHKmjbfY3ijBAO9BviT7
|
||||
bPdeMJcQQY/Z9dQV6S4n2e+CXgp5tbIUmSUpgyfMGneeGFuYCt5DikKZUlyQJ2Bk+u01st1tWYbS
|
||||
zCNZuGlKkD+DSIG/hMigstuEhlO0gjaqRmppJvXvY3GW7pSiYdMJkDjPjV8Q2EtgDkIqi52tmlG4
|
||||
L3dE90zxJHDyoAnjDdwzm21uMX7uKXVwA2yIKUJTxWFDZRO3186NzjqWbXCVE8EIT8Ck/fQlunm3
|
||||
834azcWVtqt3VJQtLkqEjf7d/Si9i7KU4HaPJ4MupmPAgj7qBtdtqRbX51hPs16h8o2GuTATJ9QR
|
||||
5gVamHSS2VRCk+FRl9bj2m7HY9qLi2JCAY1KA2G+wM86c2F12+CW7y161pQNStpkcZjnQNcetwhZ
|
||||
dA7y1JHHjVTz5cLdcw2wYQHpd1OTuECISvbeZkDx34xVhz1iAscKNyYKUd4jVHI0PsOujfZgaxC5
|
||||
uVIbZSFFtPNOkOBRj1IA8vSgks4OMpNtZtAFthWohJHArA4egVTjCbFf4txag6XHWCtfA7rOoj6z
|
||||
UFfF66XcXUavablQSTP0Q2qOJ/pD3ir4y2G8Hbti1rBbCVDgIAoOfsaxFOG481hC3LZ21QrtWm1t
|
||||
hayrgrSeRIMRwgUR+ibCWkdpj3cC32g2hCTPZpHI+e31VRs2ZYtHLxePKJUwpxTSWUcdYPDUOCSI
|
||||
38iKu/RG4ub+xDehhhSVISeI1DfhtxFARnG27izW28gKQRBBHEUEsFwkXnTJcYeo6rSwWl9CI4bC
|
||||
J8d/so3gAtQKpOG4a3adIuL4i0nvOWaJ24qkxQWzKzYdxbEsVIOnULdonwTx+s/VU8rvKK5nwApD
|
||||
CrX2TDmrfYECVkCJJ3P106aSJ8qBEqIAEDhQZ61xB6PmduF83+qujY62nwoJ9a8j8XzQif8ALkfq
|
||||
roH3UsP72LoT/GO7e9FZWvUtIGWn0wZK3if0kVlAy68IJyxO8Tb/AK6/vpfqquBPRfhQJjvuiDz+
|
||||
cVSXXg3yuE+HYH++uvOqopA6M7DWB3XHY/TNAemBKCBSdy2lDqSY350lZ3KdxIn7a0xJ0ltCkmCn
|
||||
eg0x1CXMJfbIiUECee1UToGzLe461jFtibRVcWF4WUPkfwiOQnxEVaMRxGbRYWZEGo7oktLS0yym
|
||||
5YA1XTzjyz4kqNBeytU+NVvMFlbYip5t5TqVEBIUhUaTyPhU4t4p4c6qGbHL9DinLI/OIlQSTHLj
|
||||
QDfOeCsYNmBDlpZEvKR2i7kypSjqg78Ad55UUFLL2WlIUkmWwRtPDehPjeM3t04W7nuBtxsFA2JK
|
||||
l/ZANF7C1JVh7bSu8kpjc0Gl1iLbVrh12tQRKkoJJ2hXdH1kVtgbgFvdWyne1LbihqkHUPHbn4+d
|
||||
QePYbcYng7Fqw5p7J4FSvNCpA+IFLNXTeFIxS+dSUKTal5QJ2UUpMke/9njQD7NmNX+dM7KyvhyU
|
||||
2+HWw7R+4c37VKFaVCBwEggTE0/dbfQWbewabsy6pWs9nJDadjII2Jnlt4VZzY2eH4cvFbawtkYl
|
||||
fNN+0PBAG8TJ99Nra0Q7jTSyhLa3GktrK3ZEDcpTzJkn4cqDW8swi5wu23AbaU85pHFS1AD6pq2L
|
||||
HzACRAAqKdQh7FiscilA9E//AO1KuL1IMcAKATZrFthgdwnEb1NjhhJdU+133DqV3U6dJj135VNZ
|
||||
CxrK7PZ4Xg945erMBb3YmSTJ75gR68KpvSFdtXV3ibz60e0W1ou5LSRsEAqS2k+oMnwIqMyLauYM
|
||||
3heI3lk9bKfulWt20pWkgLIgn3EEUHQEd07kCq1lwPO5qxRTgGhCkBJ8QBP2mpe2eXZdnaP6ltaQ
|
||||
lp8qmT/NV4Hz5+tQVveLtMcu9AkuOoB9KAgNqUobEA1oHFa9JFJ27mod0+tK7SFzvzoN1lWmZ4UE
|
||||
+taf3gsTzvkAforo0uujSQN6B/WqcKskWyRzvk/qroJfqYT+DTuw2U9+sisrfqZD97D3kp2fXUis
|
||||
oGHXc3y3Eb6WD/vF1TOr7mvCMJyFbWt3i1nbPJdc1IceCSJVI2NXTrrtzgIUebbH1OL++uNnEkHa
|
||||
RvQd42PSFlkAFWP4bI//AJKPvp09n/La298cw0+l0j764C7yTxPrXhKualUHcGN51wJVo6pvGcOJ
|
||||
0HhcI++k+jDOGCYZlJi1uscw8LC1qANygEBSiY4+dcSHUocTvXg17gK4UH0Jts/ZXUolzMOGJA8b
|
||||
pG/11Vcx59y6rGJbx2xcbKIMXSAkHj41xBqXEaj8a1UTO5NB1FjucsuqddGH3tj3HWe0V24AUe/M
|
||||
c1RIPGN6I+HdImVm7VtCswYYCEj+Up++uFIJ8ayFfzj4bUHdFp0j5TQ++hWYcNSgr1g+0p58ai84
|
||||
dIeUVWi1s47h90lbKmXbcXAhaVbevjw8fSuLAFR9I153juSZoO2nek3J5Nuk5isAlpIIT2oIGxHv
|
||||
2Net9KOUWFBacdwwlcqWQ6JnhwiuJAFExJisg8N6Dta16UMn9sFuZhsATqO7nifup4elTJn5OZLD
|
||||
+1rhzSrzrIJTtsaDoPpMz1gb+NG7sL21uBd4Y7au6FA7laon0mansdz9lrEsh2S14tZ+2pQytbYW
|
||||
NWtIAPv2rl7SRsTNbBJ8aDtW36UMn3Fg2HcespU2nUC5wMVBPdIWWk4s1+7to4nWklwrHAHnXJAC
|
||||
toJrfccKDuWz6UsohO+YLEf/AGU5b6UMnkE/hDYE/wC0rhi1WG7hC3UFxCFAlMxqHhNSPttq46Sq
|
||||
1S2kuqXAPAEyEjyHCg7Vd6T8omNOYLDf/WULOsDmzBMeyqzbYdidtdOi7SvQ2qSBpUJ+uufFLK3V
|
||||
rQAlKjIA5DwpdqeZJoOu+pjtlh8Rtqd/WTWUr1N2+zyo54rLiuP9NI/ZWUDLrop1YAgaTu21B5fw
|
||||
iv8AtXHrzMmNJ25iuzuuIytzLgUB3UtN/Hta5DW1J+jQRBZAMwTWBid441KlqTFZ2IkkpE0EZ7OJ
|
||||
3FeKY7xhMVK9l4CvOy24cPKgiCx5D41qWoMQRUuWSREDjNeFkapIoEEi0Fro1Q4EJAIRzBnf69/S
|
||||
lXrlpSrwtdnp1ksgtjgVSeXhtXpYT/NE16GeGw28qDxm4bLlmXez0hep8dn4K25eG1aBVsm00KIL
|
||||
obUmQjiZOx90QeVO2WmFFKFMjUTx1QPftTj2FggkBgDURBe3+ygi7z2Zxp/sVJSlTiS03o3QnfaY
|
||||
9PXjSeG+zNsuJfSky82RIJOkTq5c5G3OpP2JorCAlkd0GS5sd/t+6tnbW3aX2iW0KSFboS7Mj4UE
|
||||
Y8m2DKezUndrTu3BCtczw8K2U1a+03L6XG9Dpc7NGg93+by293hSzrLanCW29KTwSTMV4liOVBHP
|
||||
WiUL0ocS5wkpBiffSaWNztvUr2G86a9DMDYR40EX2BE6RNbJYPGNvGpMMA8q9LJ8B8KBghnfhSoa
|
||||
QdwmDTzsiTukVuGjEQAaBu22EwIpw0gTsK3S0rbu07tmVTPCg606nrShk5bihA1OJHn3xWVKdUtC
|
||||
R0bJUEgEPLB24nWr9kfCsoNOtVaOXeUEttNqWsplISkknStBP1Sa5NXhS9KdVncSAdXcO/hX0PxG
|
||||
wssRt/Z7+0ZuWpnS6gKE+O9RRyblgmfka2HpI/bQcDDDGAynVZ3Xac1Rtz/7Ui9hqVbtMOpE7yJr
|
||||
6ADKGWxwwlj4q++tGcmZYZWtbWEMoKzKgFqgnxiYoPn/APJrv/oufCvBhywf4Je3ka+hP4L4BEfJ
|
||||
jUep++vBlfAAZGGNT6q++g+e3yavj2S/gaw4a4Y+Zc/RNfQo5XwAmThjU+p++vPwWy/M/JjXxV99
|
||||
B89Pkp4meycj+qa2GEvT/Aun8019ChlfAASRhrUnzV99bHLeBnjhrJ+P30Hz2ThL+/zDv6Ne/JFw
|
||||
Zlh39E19Ck5dwVPDDmR8aw5dwX/49r6/voPnl8kvD+Idn+qa1+S3Z/gnI80mvoactYESScOaJPmf
|
||||
vrwZZwEGRhjIPv8AvoPnmMLdH8W5+ia9+TF7y0ufSvoYct4EeOGMH1BNe/g7gn/xzP1/fQfPA4cs
|
||||
H6Cx5RXhw9QH0VV9DXcsYAv6WGMn4/fSasqZdJk4Wz8VffQfPgWPPQfSsNlvOk719Al5Qy2RvhLP
|
||||
xV99aLydlk8cIY+KvvoOChZ4fKtSbgbbcONYLO030h7yMV3j+BuWNR/ce34+f316jKGWwSBhLIB2
|
||||
O6vvoOE7e0swiXRcSBtoAiYP7Yq69FHRxdZ9xt2zs1qtLW3QHLi5WmQkEwEj+keXoTXXyMpZcSNK
|
||||
cJYA4wCfvp5Z4DhNqoqtrQNEiDoWoftoGeRMrYdlDBk4PhfaezoMhThBUSSSSSAPGsqfSAlISOAr
|
||||
KD//2Q==
|
||||
|
||||
|
||||
|
||||
--Multipart_Sun_Oct_17_10:37:40_2010-1
|
||||
Content-Type: image/jpeg
|
||||
Content-Disposition: inline; filename="custer.jpg"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
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
|
||||
I0duqkQhurk8Z+mM0V6BI+FCqAFHAA6CiobA/9kA/+EMRWh0dHA6Ly9ucy5hZG9iZS5jb20veGFw
|
||||
LzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQi
|
||||
Pz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUg
|
||||
NC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5
|
||||
LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgog
|
||||
ICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6dGlm
|
||||
Zj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczpleGlmPSJodHRwOi8v
|
||||
bnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUu
|
||||
Y29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50
|
||||
cy8xLjEvIgogICB4bXA6Q3JlYXRlRGF0ZT0iMjAwNS0wMS0xMFQwMDowNzoyNyswMTowMCIKICAg
|
||||
eG1wOk1vZGlmeURhdGU9IjIwMDUtMDEtMTBUMDA6NTc6MDMrMDE6MDAiCiAgIHhtcDpNZXRhZGF0
|
||||
YURhdGU9IjIwMDUtMDEtMTBUMDA6NTc6MDMrMDE6MDAiCiAgIHhtcDpDcmVhdG9yVG9vbD0iQWRv
|
||||
YmUgUGhvdG9zaG9wIENTIFdpbmRvd3MiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHRpZmY6
|
||||
WFJlc29sdXRpb249IjIwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIyMDAvMSIKICAgdGlmZjpS
|
||||
ZXNvbHV0aW9uVW5pdD0iMiIKICAgZXhpZjpDb2xvclNwYWNlPSI0Mjk0OTY3Mjk1IgogICBleGlm
|
||||
OlBpeGVsWERpbWVuc2lvbj0iNzU1IgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iOTMwIgogICB4
|
||||
bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6Zjg2ZTcwZTQtNjI5OC0xMWQ5
|
||||
LTllM2YtZDQyZjM0NjM5ZGJiIgogICB4bXBNTTpJbnN0YW5jZUlEPSJ1dWlkOmY4NmU3MGU1LTYy
|
||||
OTgtMTFkOS05ZTNmLWQ0MmYzNDYzOWRiYiIKICAgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIi8+CiA8
|
||||
L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg
|
||||
ICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7/2wBDAAUDBAQEAwUEBAQFBQUG
|
||||
BwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUF
|
||||
BQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e
|
||||
Hh4eHh7/wAARCAD2AMgDASIAAhEBAxEB/8QAHQAAAAcBAQEAAAAAAAAAAAAAAAIDBAUGBwEICf/E
|
||||
AEIQAAIBAwIEBAQDBgUBCAMBAAECAwAEEQUhBhIxQQcTUWEicYGRFDKhI0JSscHRCBVicoLwFiQl
|
||||
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
|
||||
|
||||
Cheerio!
|
||||
|
||||
--Multipart_Sun_Oct_17_10:37:40_2010-1--
|
||||
0
lib/tests/testdir2/Foo/new/.noindex
Normal file
0
lib/tests/testdir2/Foo/new/.noindex
Normal file
0
lib/tests/testdir2/Foo/tmp/.noindex
Normal file
0
lib/tests/testdir2/Foo/tmp/.noindex
Normal file
42
lib/tests/testdir2/bar/cur/181736.eml
Normal file
42
lib/tests/testdir2/bar/cur/181736.eml
Normal file
@ -0,0 +1,42 @@
|
||||
Path: uutiset.elisa.fi!feeder2.news.elisa.fi!feeder.erje.net!newsfeed.kamp.net!newsfeed0.kamp.net!nx02.iad01.newshosting.com!newshosting.com!post01.iad!not-for-mail
|
||||
X-newsreader: xrn 9.03-beta-14-64bit
|
||||
Sender: jimbo@lews (Jimbo Foobarcuux)
|
||||
From: jimbo@slp53.sl.home (Jimbo Foobarcuux)
|
||||
Reply-To: slp53@pacbell.net
|
||||
Subject: Re: Are writes "atomic" to readers of the file?
|
||||
Newsgroups: comp.unix.programmer
|
||||
References: <e9065dac-13c1-4103-9e31-6974ca232a89@t15g2000prt.googlegroups.com> <87hbblwelr.fsf@sapphire.mobileactivedefense.com> <pql248-4va.ln1@wilbur.25thandClement.com> <ikns6r$li3$1@Iltempo.Update.UU.SE> <8762s0jreh.fsf@sapphire.mobileactivedefense.com> <ikqqp1$jv0$1@Iltempo.Update.UU.SE> <87hbbjc5jt.fsf@sapphire.mobileactivedefense.com> <ikr0na$lru$1@Iltempo.Update.UU.SE> <tO8cp.1228$GE6.370@news.usenetserver.com> <ikr6ks$nlf$1@Iltempo.Update.UU.SE> <8ioh48-8mu.ln1@leafnode-msgid.gclare.org.uk>
|
||||
Organization: UseNetServer - www.usenetserver.com
|
||||
X-Complaints-To: abuse@usenetserver.com
|
||||
Message-ID: <oktdp.42997$Te.22361@news.usenetserver.com>
|
||||
Date: 08 Mar 2011 17:04:20 GMT
|
||||
Lines: 27
|
||||
Xref: uutiset.elisa.fi comp.unix.programmer:181736
|
||||
|
||||
John Denver <jd@clare.See-My-Signature.invalid> writes:
|
||||
>Eric the Red wrote:
|
||||
>
|
||||
>>> There _IS_ a requirement that all reads and writes to regular files
|
||||
>>> be atomic. There is also an ordering guarantee. Any implementation
|
||||
>>> that doesn't provide both atomicity and ordering guarantees is broken.
|
||||
>>
|
||||
>> But where is it specified?
|
||||
>
|
||||
>The place where it is stated most explicitly is in XSH7 2.9.7
|
||||
>Thread Interactions with Regular File Operations:
|
||||
>
|
||||
> All of the following functions shall be atomic with respect to each
|
||||
> other in the effects specified in POSIX.1-2008 when they operate on
|
||||
> regular files or symbolic links:
|
||||
>
|
||||
> [List of functions that includes read() and write()]
|
||||
>
|
||||
> If two threads each call one of these functions, each call shall
|
||||
> either see all of the specified effects of the other call, or none
|
||||
> of them.
|
||||
>
|
||||
|
||||
And, for the purposes of this paragraph, the two threads need not be
|
||||
part of the same process.
|
||||
|
||||
jimbo
|
||||
37
lib/tests/testdir2/bar/cur/mail1
Normal file
37
lib/tests/testdir2/bar/cur/mail1
Normal file
@ -0,0 +1,37 @@
|
||||
Date: Thu, 31 Jul 2008 14:57:25 -0400
|
||||
From: "John Milton" <jm@example.com>
|
||||
Subject: Fere libenter homines id quod volunt credunt
|
||||
To: "Julius Caesar" <jc@example.com>
|
||||
Message-id: <3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com>
|
||||
MIME-version: 1.0
|
||||
x-label: Paradise, losT
|
||||
Content-type: text/plain; charset=us-ascii
|
||||
Content-transfer-encoding: 7BIT
|
||||
Precedence: high
|
||||
|
||||
OF Mans First Disobedience, and the Fruit
|
||||
Of that Forbidden Tree, whose mortal tast
|
||||
Brought Death into the World, and all our woe,
|
||||
With loss of Eden, till one greater Man
|
||||
Restore us, and regain the blissful Seat, [ 5 ]
|
||||
Sing Heav'nly Muse,that on the secret top
|
||||
Of Oreb, or of Sinai, didst inspire
|
||||
That Shepherd, who first taught the chosen Seed,
|
||||
In the Beginning how the Heav'ns and Earth
|
||||
Rose out of Chaos: Or if Sion Hill [ 10 ]
|
||||
Delight thee more, and Siloa's Brook that flow'd
|
||||
Fast by the Oracle of God; I thence
|
||||
Invoke thy aid to my adventrous Song,
|
||||
That with no middle flight intends to soar
|
||||
Above th' Aonian Mount, while it pursues [ 15 ]
|
||||
Things unattempted yet in Prose or Rhime.
|
||||
And chiefly Thou O Spirit, that dost prefer
|
||||
Before all Temples th' upright heart and pure,
|
||||
Instruct me, for Thou know'st; Thou from the first
|
||||
Wast present, and with mighty wings outspread [ 20 ]
|
||||
Dove-like satst brooding on the vast Abyss
|
||||
And mad'st it pregnant: What in me is dark
|
||||
Illumin, what is low raise and support;
|
||||
That to the highth of this great Argument
|
||||
I may assert Eternal Providence, [ 25 ]
|
||||
And justifie the wayes of God to men.
|
||||
13
lib/tests/testdir2/bar/cur/mail2
Normal file
13
lib/tests/testdir2/bar/cur/mail2
Normal file
@ -0,0 +1,13 @@
|
||||
Date: Thu, 31 Jul 2008 14:57:25 -0400
|
||||
From: "Socrates" <soc@example.com>
|
||||
Subject: cool stuff
|
||||
To: "Alcibiades" <alki@example.com>
|
||||
Message-id: <3BE9E6535E0D852173@emss35m06.us.lmco.com>
|
||||
MIME-version: 1.0
|
||||
Content-type: text/plain; charset=us-ascii
|
||||
Content-transfer-encoding: 7BIT
|
||||
Precedence: high
|
||||
|
||||
The hour of departure has arrived, and we go our ways—I to die, and you to
|
||||
live. Which is better God only knows.
|
||||
|
||||
34
lib/tests/testdir2/bar/cur/mail3
Normal file
34
lib/tests/testdir2/bar/cur/mail3
Normal file
@ -0,0 +1,34 @@
|
||||
From: Napoleon Bonaparte <nb@example.com>
|
||||
To: Edmond =?UTF-8?B?RGFudMOocw==?= <ed@example.com>
|
||||
Subject: rock on dude
|
||||
User-Agent: Wanderlust/2.15.9 (Almost Unreal) Emacs/24.0 Mule/6.0 (HANACHIRUSATO)
|
||||
Fcc: .sent
|
||||
MIME-Version: 1.0 (generated by SEMI 1.14.6 - "Maruoka")
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Le 24 février 1815, la vigie de Notre-Dame de la Garde signala le trois-mâts
|
||||
le Pharaon, venant de Smyrne, Trieste et Naples.
|
||||
|
||||
Comme d'habitude, un pilote côtier partit aussitôt du port, rasa le château
|
||||
d'If, et alla aborder le navire entre le cap de Morgion et l'île de Rion.
|
||||
|
||||
Aussitôt, comme d'habitude encore, la plate-forme du fort Saint-Jean s'était
|
||||
couverte de curieux; car c'est toujours une grande affaire à Marseille que
|
||||
l'arrivée d'un bâtiment, surtout quand ce bâtiment, comme le Pharaon, a été
|
||||
construit, gréé, arrimé sur les chantiers de la vieille Phocée, et appartient
|
||||
à un armateur de la ville.
|
||||
|
||||
Cependant ce bâtiment s'avançait; il avait heureusement franchi le détroit que
|
||||
quelque secousse volcanique a creusé entre l'île de Calasareigne et l'île de
|
||||
Jaros; il avait doublé Pomègue, et il s'avançait sous ses trois huniers, son
|
||||
grand foc et sa brigantine, mais si lentement et d'une allure si triste, que
|
||||
les curieux, avec cet instinct qui pressent un malheur, se demandaient quel
|
||||
accident pouvait être arrivé à bord. Néanmoins les experts en navigation
|
||||
reconnaissaient que si un accident était arrivé, ce ne pouvait être au
|
||||
bâtiment lui-même; car il s'avançait dans toutes les conditions d'un navire
|
||||
parfaitement gouverné: son ancre était en mouillage, ses haubans de beaupré
|
||||
décrochés; et près du pilote, qui s'apprêtait à diriger le Pharaon par
|
||||
l'étroite entrée du port de Marseille, était un jeune homme au geste rapide et
|
||||
à l'œil actif, qui surveillait chaque mouvement du navire et répétait chaque
|
||||
ordre du pilote.
|
||||
28
lib/tests/testdir2/bar/cur/mail4
Normal file
28
lib/tests/testdir2/bar/cur/mail4
Normal file
@ -0,0 +1,28 @@
|
||||
Return-Path: <foo@example.com>
|
||||
Delivered-To: foo@example.com
|
||||
Received: from [128.88.204.56] by freemailng0304.web.de with HTTP;
|
||||
Mon, 07 May 2005 00:27:52 +0200
|
||||
Date: Mon, 07 May 2005 00:27:52 +0200
|
||||
Message-Id: <293847329847@web.de>
|
||||
MIME-Version: 1.0
|
||||
From: =?iso-8859-1?Q? "=F6tzi" ?= <oetzi@web.de>
|
||||
To: foo@example.com
|
||||
Subject: =?iso-8859-1?Q?Re:=20der=20b=E4r=20und=20das=20m=E4dchen?=
|
||||
Precedence: fm-user
|
||||
Organization: http://freemail.web.de/
|
||||
Content-Type: text/plain; charset="iso-8859-1"
|
||||
Content-Transfer-Encoding: 8bit
|
||||
X-MIME-Autoconverted: from quoted-printable to 8bit by mailhost6.ladot.com id j48MScQ30791
|
||||
X-UIDL: 93h!!\i<!!L)l!!%_I!!
|
||||
X-Spam-Checker-Version: SpamAssassin 3.0.2 (2004-11-16) on mindcrime
|
||||
X-Spam-Level:
|
||||
X-Spam-Status: No, score=-2.3 required=3.0 tests=AWL,BAYES_00 autolearn=ham
|
||||
version=3.0.2
|
||||
|
||||
Viele liebe Gruesse aus der Stadt der St<53>dte..
|
||||
|
||||
__________________________________________________________
|
||||
Mit WEB.DE FreePhone mit hoechster Qualitaet ab 0 Ct./Min.
|
||||
weltweit telefonieren! http://freephone.web.de/?mc=021201
|
||||
|
||||
|
||||
7
lib/tests/testdir2/bar/cur/mail5
Normal file
7
lib/tests/testdir2/bar/cur/mail5
Normal file
@ -0,0 +1,7 @@
|
||||
Date: Mon, 13 Jun 2011 14:57:25 -0400
|
||||
From: xyz@123.xx
|
||||
Subject: abc
|
||||
To: foo@bar.cx
|
||||
Message-id: <abc@def>
|
||||
|
||||
123
|
||||
18
lib/tests/testdir2/bar/cur/mail6
Normal file
18
lib/tests/testdir2/bar/cur/mail6
Normal file
@ -0,0 +1,18 @@
|
||||
Date: Thu, 31 Jul 2008 14:57:25 -0400
|
||||
From: "Geoff Tate" <jeff@example.com>
|
||||
Subject: eyes of a stranger
|
||||
To: "Enrico Fermi" <enrico@example.com>
|
||||
Message-id: <3BE9E6535E302944823E7A1A20D852173@msg.id>
|
||||
MIME-version: 1.0
|
||||
X-label: @NextActions, operation:mindcrime, Queensrÿche
|
||||
Content-type: text/plain; charset=us-ascii
|
||||
Content-transfer-encoding: 7BIT
|
||||
Precedence: high
|
||||
|
||||
And I raise my head and stare
|
||||
Into the eyes of a stranger
|
||||
I've always known that the mirror never lies
|
||||
People always turn away
|
||||
From the eyes of a stranger
|
||||
Afraid to know what
|
||||
Lies behind the stare
|
||||
0
lib/tests/testdir2/bar/new/.noindex
Normal file
0
lib/tests/testdir2/bar/new/.noindex
Normal file
0
lib/tests/testdir2/bar/tmp/.noindex
Normal file
0
lib/tests/testdir2/bar/tmp/.noindex
Normal file
20
lib/tests/testdir2/wom_bat/cur/atomic
Normal file
20
lib/tests/testdir2/wom_bat/cur/atomic
Normal file
@ -0,0 +1,20 @@
|
||||
Date: Sat, 12 Nov 2011 12:06:23 -0400
|
||||
From: "Richard P. Feynman" <rpf@example.com>
|
||||
Subject: atoms
|
||||
To: "Democritus" <demo@example.com>
|
||||
Message-id: <3BE9E6535E302944823E7A1A20D852173@msg.id>
|
||||
MIME-version: 1.0
|
||||
Content-type: text/plain; charset=us-ascii
|
||||
Content-transfer-encoding: 7BIT
|
||||
Precedence: high
|
||||
|
||||
If, in some cataclysm, all scientific knowledge were to be destroyed,
|
||||
and only one sentence passed on to the next generation of creatures,
|
||||
what statement would contain the most information in the fewest words?
|
||||
I believe it is the atomic hypothesis (or atomic fact, or whatever you
|
||||
wish to call it) that all things are made of atoms — little particles
|
||||
that move around in perpetual motion, attracting each other when they
|
||||
are a little distance apart, but repelling upon being squeezed into
|
||||
one another. In that one sentence you will see an enormous amount of
|
||||
information about the world, if just a little imagination and thinking
|
||||
are applied.
|
||||
44
lib/tests/testdir2/wom_bat/cur/rfc822.1
Normal file
44
lib/tests/testdir2/wom_bat/cur/rfc822.1
Normal file
@ -0,0 +1,44 @@
|
||||
Return-Path: <foo@example.com>
|
||||
Subject: Fwd: rfc822
|
||||
From: foobar <foo@example.com>
|
||||
To: martin
|
||||
Content-Type: multipart/mixed; boundary="=-XHhVx/BCC6tJB87HLPqF"
|
||||
Message-Id: <1077300332.871.27.camel@example.com>
|
||||
Mime-Version: 1.0
|
||||
X-Mailer: Ximian Evolution 1.4.5
|
||||
Date: Fri, 20 Feb 2004 19:05:33 +0100
|
||||
|
||||
--=-XHhVx/BCC6tJB87HLPqF
|
||||
Content-Type: text/plain
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Hello world, forwarding some RFC822 message
|
||||
|
||||
--=-XHhVx/BCC6tJB87HLPqF
|
||||
Content-Disposition: inline
|
||||
Content-Type: message/rfc822
|
||||
|
||||
Return-Path: <cuux@example.com>
|
||||
Message-ID: <9A01B19D0D605D478E8B72E1367C66340141B9C5@example.com>
|
||||
From: frob@example.com
|
||||
To: foo@example.com
|
||||
Subject: hopjesvla
|
||||
Date: Sat, 13 Dec 2003 19:35:56 +0100
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=iso-8859-1
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
The ship drew on and had safely passed the strait, which some volcanic shock
|
||||
has made between the Calasareigne and Jaros islands; had doubled Pomegue, and
|
||||
approached the harbor under topsails, jib, and spanker, but so slowly and
|
||||
sedately that the idlers, with that instinct which is the forerunner of evil,
|
||||
asked one another what misfortune could have happened on board. However, those
|
||||
experienced in navigation saw plainly that if any accident had occurred, it was
|
||||
not to the vessel herself, for she bore down with all the evidence of being
|
||||
skilfully handled, the anchor a-cockbill, the jib-boom guys already eased off,
|
||||
and standing by the side of the pilot, who was steering the Pharaon towards the
|
||||
narrow entrance of the inner port, was a young man, who, with activity and
|
||||
vigilant eye, watched every motion of the ship, and repeated each direction of
|
||||
the pilot.
|
||||
|
||||
--=-XHhVx/BCC6tJB87HLPqF--
|
||||
44
lib/tests/testdir2/wom_bat/cur/rfc822.2
Normal file
44
lib/tests/testdir2/wom_bat/cur/rfc822.2
Normal file
@ -0,0 +1,44 @@
|
||||
From: dwarf@siblings.net
|
||||
To: root@eruditorum.org
|
||||
Subject: Fwd: test abc
|
||||
References: <8639ddr9wu.fsf@cthulhu.djcbsoftware>
|
||||
User-agent: mu 0.98pre; emacs 24.0.91.9
|
||||
Date: Thu, 24 Nov 2011 14:24:00 +0200
|
||||
Message-ID: <861usxr9nj.fsf@cthulhu.djcbsoftware>
|
||||
Content-Type: multipart/mixed; boundary="=-=-="
|
||||
MIME-Version: 1.0
|
||||
|
||||
--=-=-=
|
||||
Content-Type: text/plain
|
||||
|
||||
Saw the website. Am willing to stipulate that you are not RIST 9E03. Suspect
|
||||
that you are the Dentist, who yearns for honest exchange of views. Anonymous,
|
||||
digitally signed e-mail is the only safe vehicle for same.
|
||||
|
||||
If you want me to believe you are not the Dentist, provide plausible
|
||||
explanation for your question regarding why we are building the Crypt.
|
||||
|
||||
Yours truly,
|
||||
|
||||
--=-=-=
|
||||
Content-Type: message/rfc822
|
||||
Content-Disposition: inline; filename=
|
||||
"1322137188_3.11919.foo:2,S"
|
||||
Content-Description: rfc822
|
||||
|
||||
From: dwarf@siblings.net
|
||||
To: root@eruditorum.org
|
||||
Subject: test abc
|
||||
User-agent: mu 0.98pre; emacs 24.0.91.9
|
||||
Date: Thu, 24 Nov 2011 14:18:25 +0200
|
||||
Message-ID: <8639ddr9wu.fsf@cthulhu.djcbsoftware>
|
||||
Content-Type: text/plain
|
||||
MIME-Version: 1.0
|
||||
|
||||
As I stepped on this unknown middle-aged Filipina's feet during an ill-advised
|
||||
ballroom dancing foray, she leaned close to me and uttered some latitude and
|
||||
longitude figures with a conspicuously large number of significant digits of
|
||||
precision, implying a maximum positional error on the order of the size of a
|
||||
dinner plate. Gosh, was I ever curious!
|
||||
|
||||
--=-=-=--
|
||||
7
lib/tests/testdir3/cycle/cur/cycle0
Normal file
7
lib/tests/testdir3/cycle/cur/cycle0
Normal file
@ -0,0 +1,7 @@
|
||||
From: foo@example.com
|
||||
To: bar@example.com
|
||||
Subject: cycle0
|
||||
Message-Id: <cycle0@msg.id>
|
||||
Date: Tue, 21 Jun 2011 11:00 +0000
|
||||
|
||||
def
|
||||
8
lib/tests/testdir3/cycle/cur/cycle0.0
Normal file
8
lib/tests/testdir3/cycle/cur/cycle0.0
Normal file
@ -0,0 +1,8 @@
|
||||
From: foo@example.com
|
||||
To: bar@example.com
|
||||
Subject: cycle0.0
|
||||
Message-Id: <cycle0.0@msg.id>
|
||||
References: <cycle0@msg.id>
|
||||
Date: Tue, 21 Jun 2011 12:00 +0000
|
||||
|
||||
def
|
||||
8
lib/tests/testdir3/cycle/cur/cycle0.0.0
Normal file
8
lib/tests/testdir3/cycle/cur/cycle0.0.0
Normal file
@ -0,0 +1,8 @@
|
||||
From: foo@example.com
|
||||
To: bar@example.com
|
||||
Subject: cycle0.0.0
|
||||
Message-Id: <cycle0.0.0@msg.id>
|
||||
References: <cycle0@msg.id> <cycle0.0@msg.id>
|
||||
Date: Tue, 21 Jun 2011 13:00 +0000
|
||||
|
||||
def
|
||||
8
lib/tests/testdir3/cycle/cur/rogue0
Normal file
8
lib/tests/testdir3/cycle/cur/rogue0
Normal file
@ -0,0 +1,8 @@
|
||||
From: foo@example.com
|
||||
To: bar@example.com
|
||||
Subject: rogue0
|
||||
Message-Id: <rogue0@msg.id>
|
||||
References: <cycle0.0@msg.id> <cycle0@msg.id>
|
||||
Date: Tue, 21 Jun 2011 15:00 +0000
|
||||
|
||||
def
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user