* refactoring: split src/ into mu/ and lib/

This commit is contained in:
djcb
2012-05-20 17:41:18 +03:00
parent 605657d4de
commit 46f10cfde9
143 changed files with 160 additions and 279 deletions

95
lib/Makefile.am Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

View 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 */
}

View 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
View 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
View 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
View 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 ();
}

View 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
View 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
View 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
View 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
View 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 ();
}

View 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

View 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 &quot;BEGIN IMMEDIATE&quot; 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">&lt;<a href="mailto:grant.gatchel@gmail.com">grant.gatchel@gmail.com</a>&gt;</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">&lt;<a href="http://nadav.gr" target="_blank">nadav.gr</a>@<a href="http://gmail.com" target="_blank">gmail.com</a>&gt;</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>&quot;BEGIN IMMEDIATE&quot;</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==--

View 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-----
--=-=-=--

View 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.

View 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

View 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--

View 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.

View 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.

View 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.

View 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-----
--=-=-=--

View 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-----
--=-=-=--

View 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-----
--=-=-=--

View 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

View 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

View 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
=================================================

View 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>&nbsp;</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>&nbsp;</DIV>=0A=
<DIV dir=3Dltr><FONT face=3DArial size=3D2>Paul</FONT></DIV>=0A=
<DIV dir=3Dltr><FONT face=3DArial size=3D2></FONT>&nbsp;</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&nbsp;<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&nbsp;<BR>statement you can =
use something like:<BR>goto * =
instructions[pOp-&gt;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==--

View 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
=================================================

View 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 &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&nbsp;&bull; 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 &nbsp; Polonceau=
kade 17 1014 DA Amsterdam &nbsp; tel: 020-6758504&nbsp; <a style=3D"color=
: rgb(167, 169, 172);" href=3D"mailto:info@artolive.nl">info@artolive.nl<=
/a>&nbsp; <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">&nbsp; <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> &nbsp;&nbsp; </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.&rdquo;</p>
<p>Peter van den Akker expose=
ert regelmatig in binnen- en buitenland bij galerie&euml;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">&nbsp;&nbsp;=
<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>&nbsp;&nbsp;&nbsp; </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 &lsquo;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">&nbsp;&nbsp;=
<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>&nbsp;&nbsp;&nbsp; </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&iuml;nformeerd worden? Klik=
dan <a href=3D'http://www.mailinglijst.eu/nieuwsbrief/edit/?e=3Df00f@djc=
bmachines.nl&c=3D9856&l=3D100549'>hier</a>&nbsp;om je af te melden. <br>
kreeg je dit mailtje doorgestuurd en wil je voortaan =
zelf ook graag de nieuwsbrief ontvangen? <br>
klik dan&nbsp;<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--

View 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
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
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
/9j/4AAQSkZJRgABAQAAAQABAAD/4Q1kRXhpZgAASUkqAAgAAAAIABIBCQABAAAAAQAAABoBCQAB
AAAAyAAAABsBBQABAAAAbgAAACgBAwABAAAAAgAAADEBAgAOAAAAdgAAADIBAgAUAAAAhAAAABMC
CQABAAAAAQAAAGmHBAABAAAAmAAAAOYAAADIAAAAAQAAAGd0aHVtYiAyLjExLjMAMjAwNTowMTox
MCAwMDo1NzowMwAGAACQBwAEAAAAMDIyMQGRBwAEAAAAAQIDAACgBwAEAAAAMDEwMAGgAwABAAAA
//8AAAKgCQABAAAAyAAAAAOgCQABAAAA9gAAAAAAAAAGAAMBAwABAAAABgAAABoBCQABAAAASAAA
ABsBCQABAAAASAAAACgBCQABAAAAAgAAAAECBAABAAAANAEAAAICBAABAAAAJwwAAAAAAAD/2P/g
ABBKRklGAAEBAAABAAEAAP/bAEMACAYGBwYFCAcHBwkJCAoMFA0MCwsMGRITDxQdGh8eHRocHCAk
LicgIiwjHBwoNyksMDE0NDQfJzk9ODI8LjM0Mv/bAEMBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIy
MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAIAAaAMBIgACEQED
EQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0B
AgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpD
REVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmq
srO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEB
AQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFR
B2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVW
V1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrC
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
pAznLa7juUCSEJMvr3qZwydRx6isi4ty3zJw1TWksjrs3kOOoJpiL0twwXlCwHoKxri6WFj8pG/k
A1fe6vIODtKmu1+Gc9vcazNHeRxyZh+RXjVuc8nJ5oA4zwxp91rmpSW1nGGnZMZbgAZ5JP5V6Cvw
213Jb7TYgbcKPMbr/wB816pGsESARpGi+iqAKT7VCPlMi5+tAHisPwc8SLJk39hH/tpI+R/47XWe
FvBeqeGtHnhlENxO8hlYRSH5vQDIHP1xXoSzow4ZT9DSvIO1AHiepeMNUWaWCBPsgDFWDrl8jsc9
PpXMajqdxe3BuLt90zAAsABkDp0rq/ixpTWeuwanbDC3kZDr/trwT+IIrziS6uUcJcqGWgDpPDOo
pb+ILctJxITGQffp+uKKyNFtjc67YxqdyNMrfgDk/wAqKljR64vB5rK8Q6DaapCtxI0qXC7I0Kth
eXAGR3+8fzrTDYNQavKRol465DpEZFI6gryP1FJMbPMEcqxRhytNkwrLInDDriqST/ay3z4lB3A+
tSrIWQq3DjqDVEl9h9oiDIC2e1V4H1KxvEuIGe2KHPmZxtqkbuazYtC5FVJ767vG/eyEr6dKAPQr
P4k6tZRHcy3KKeGlXJI98VuQ+OdM1EB3nubN3IASWM4Y+gIzXksN4YcB49yjsank1L7Q4ZvlVfuj
3oC57To2vQX8jJBdJujO0hnCnP0ODXWxC6dBuLL6ZFeP2/izwp4hsIodejmtNREflvdRICjnszY5
OepGOtcvc6vqeh3Ij0jXbnyifl8mZth+maLDPU/i9Cf+EOt53YB4rtcHvypBx+leILdgDbICwrqL
7TvHPiVo7fUTdTxp8y+dIAg9+uM/rXS6D8PbDTQs+pst5cjkJ/yzU/T+L8fyp3AyfAmiXHn/ANrz
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
M5KissJDU9Lh/8QAGQEBAQEBAQEAAAAAAAAAAAAAAAECAwQF/8QAGhEBAQEBAQEBAAAAAAAAAAAA
AAERAjEhEv/aAAwDAQACEQMRAD8Am8Hl60dEGN+tAbnalEBIr5+PUIoGcmllXPSgkW9LxxntVBET
2pVY/Y0tFEe+KcpFRDYRDHSjrD/pp4sdHWIUwMHhwOlJcpXr0qVeEEd6aXCxxRtLK6xou7MxwB8z
TDSSpjelAtQFxxvwjaSmGXW7dnH/AOtWkH3UEUeLjjhGXATV0z7wyD/61cNTTDbG1AKcdKjBxXww
RznWbUA9Mkj+lSem3+n6lE0mnXtvdopwzQyBgp9DjpTDXGQ0mY6fFD6Vzy8DpTDTMR7UOT3p0U9q
KF3oaasm1F5PSnTr3pMAb1FJrGwGRRuX1o64xvRtqYG5X2oyp7UpgV1agCKAQSKFKZHLQozUEoNL
Rj3oBd+lLxJsKrTsaEmnccW+d65Ehz0pzGp9KuI5HHv0pdU7Yrsa+1LqmSBitSAsabdKrXiLxfb8
IabG6xR3V/O4EVsXweXuxx2/maX4w410PhmJknlWe7A2gRhkf7j2/nWCaxxK9/qs+ohea6mbJuJd
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
Cheerio!
--Multipart_Sun_Oct_17_10:37:40_2010-1--

View File

View File

View 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

View 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.

View 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.

View 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.

View 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

View 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

View 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

View File

View File

View 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.

View 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--

View 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!
--=-=-=--

View 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

View 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

View 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

View 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