* 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

244
mu/mu-cmd-cfind.c Normal file
View File

@ -0,0 +1,244 @@
/*
** 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.
**
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif /*HAVE_CONFIG_H*/
#include <stdlib.h>
#include <stdio.h>
#include "mu-cmd.h"
#include "mu-util.h"
#include "mu-str.h"
#include "mu-date.h"
#include "mu-contacts.h"
#include "mu-runtime.h"
static void
print_header (MuConfigFormat format)
{
switch (format) {
case MU_CONFIG_FORMAT_BBDB:
g_print (";; -*-coding: utf-8-emacs;-*-\n"
";;; file-version: 6\n");
break;
case MU_CONFIG_FORMAT_MUTT_AB:
g_print ("Matching addresses in the mu database:\n");
break;
default:
break;
}
}
static void
each_contact_bbdb (const char *email, const char *name, time_t tstamp)
{
char *fname, *lname, *now, *timestamp;
fname = mu_str_guess_first_name (name);
lname = mu_str_guess_last_name (name);
now = mu_date_str ("%Y-%m-%d", time(NULL));
timestamp = mu_date_str ("%Y-%m-%d", tstamp);
g_print ("[\"%s\" \"%s\" nil nil nil nil (\"%s\") "
"((creation-date . \"%s\") (time-stamp . \"%s\")) nil]\n",
fname, lname, email, now, timestamp);
g_free (now);
g_free (timestamp);
g_free (fname);
g_free (lname);
}
static void
each_contact_mutt_alias (const char *email, const char *name)
{
gchar *nick;
if (!name)
return;
nick = mu_str_guess_nick (name);
mu_util_print_encoded ("alias %s %s <%s>\n",
nick, name, email);
g_free (nick);
}
static void
each_contact_wl (const char *email, const char *name)
{
gchar *nick;
if (!name)
return;
nick = mu_str_guess_nick (name);
mu_util_print_encoded ("%s \"%s\" \"%s\"\n",
email, nick, name);
g_free (nick);
}
static void
each_contact_org_contact (const char *email, const char *name)
{
if (name)
mu_util_print_encoded (
"* %s\n:PROPERTIES:\n:EMAIL: %s\n:END:\n\n",
name, email);
}
static void
print_plain (const char *email, const char *name, gboolean color)
{
if (name) {
if (color) fputs (MU_COLOR_MAGENTA, stdout);
mu_util_fputs_encoded (name, stdout);
fputs (" ", stdout);
}
if (color)
fputs (MU_COLOR_GREEN, stdout);
mu_util_fputs_encoded (email, stdout);
if (color)
fputs (MU_COLOR_DEFAULT, stdout);
fputs ("\n", stdout);
}
struct _ECData {
MuConfigFormat format;
gboolean color;
};
typedef struct _ECData ECData;
static void
each_contact (const char *email, const char *name, time_t tstamp,
ECData *ecdata)
{
switch (ecdata->format) {
case MU_CONFIG_FORMAT_MUTT_ALIAS:
each_contact_mutt_alias (email, name);
break;
case MU_CONFIG_FORMAT_MUTT_AB:
mu_util_print_encoded ("%s\t%s\t\n",
email, name ? name : "");
break;
case MU_CONFIG_FORMAT_WL:
each_contact_wl (email, name);
break;
case MU_CONFIG_FORMAT_ORG_CONTACT:
each_contact_org_contact (email, name);
break;
case MU_CONFIG_FORMAT_BBDB:
each_contact_bbdb (email, name, tstamp);
break;
case MU_CONFIG_FORMAT_CSV:
mu_util_print_encoded ("%s,%s\n", name ? name : "", email);
break;
default:
print_plain (email, name, ecdata->color);
}
}
static MuError
run_cmd_cfind (const char* pattern, MuConfigFormat format,
gboolean color, GError **err)
{
gboolean rv;
MuContacts *contacts;
size_t num;
ECData ecdata;
ecdata.format = format;
ecdata.color = color;
contacts = mu_contacts_new (mu_runtime_path(MU_RUNTIME_PATH_CONTACTS));
if (!contacts) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_CONTACTS_CANNOT_RETRIEVE,
"could not retrieve contacts");
return MU_ERROR_CONTACTS_CANNOT_RETRIEVE;
}
print_header (format);
rv = mu_contacts_foreach (contacts,
(MuContactsForeachFunc)each_contact,
&ecdata, pattern, &num);
mu_contacts_destroy (contacts);
if (num == 0) {
g_warning ("no matching contacts found");
return MU_ERROR_NO_MATCHES;
}
return rv ? MU_OK : MU_ERROR_CONTACTS;
}
static gboolean
cfind_params_valid (MuConfig *opts)
{
switch (opts->format) {
case MU_CONFIG_FORMAT_PLAIN:
case MU_CONFIG_FORMAT_MUTT_ALIAS:
case MU_CONFIG_FORMAT_MUTT_AB:
case MU_CONFIG_FORMAT_WL:
case MU_CONFIG_FORMAT_BBDB:
case MU_CONFIG_FORMAT_CSV:
case MU_CONFIG_FORMAT_ORG_CONTACT:
break;
default:
g_warning ("invalid output format %s",
opts->formatstr ? opts->formatstr : "<none>");
return FALSE;
}
/* only one pattern allowed */
if (opts->params[1] && opts->params[2]) {
g_warning ("usage: mu cfind [options] [<ptrn>]");
return FALSE;
}
return TRUE;
}
MuError
mu_cmd_cfind (MuConfig *opts, GError **err)
{
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_CFIND,
MU_ERROR_INTERNAL);
if (!cfind_params_valid (opts)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"invalid parameters");
return MU_ERROR_IN_PARAMETERS;
}
return run_cmd_cfind (opts->params[1], opts->format,
!opts->nocolor, err);
}

417
mu/mu-cmd-extract.c Normal file
View File

@ -0,0 +1,417 @@
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
/*
** 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, 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 <stdlib.h>
#include <string.h>
#include "mu-msg.h"
#include "mu-msg-part.h"
#include "mu-cmd.h"
#include "mu-util.h"
#include "mu-str.h"
static gboolean
save_part (MuMsg *msg, const char *targetdir, guint partidx, gboolean overwrite,
gboolean play)
{
GError *err;
gchar *filepath;
gboolean rv;
err = NULL;
rv = FALSE;
filepath = mu_msg_part_filepath (msg, targetdir, partidx, &err);
if (!filepath)
goto exit;
if (!mu_msg_part_save (msg, filepath, partidx, overwrite, FALSE, &err))
goto exit;
if (play)
rv = mu_util_play (filepath, TRUE, FALSE, &err);
else
rv = TRUE;
exit:
if (err)
g_warning ("error with MIME-part: %s",
err->message);
g_clear_error (&err);
g_free (filepath);
return rv;
}
static gboolean
save_numbered_parts (MuMsg *msg, MuConfig *opts)
{
gboolean rv;
char **parts, **cur;
parts = g_strsplit (opts->parts, ",", 0);
for (rv = TRUE, cur = parts; cur && *cur; ++cur) {
unsigned idx;
int i;
char *endptr;
idx = (unsigned)(i = strtol (*cur, &endptr, 10));
if (i < 0 || *cur == endptr) {
g_warning ("invalid MIME-part index '%s'", *cur);
rv = FALSE;
break;
}
if (!save_part (msg, opts->targetdir, idx, opts->overwrite,
opts->play)) {
g_warning ("failed to save MIME-part %d", idx);
rv = FALSE;
break;
}
}
g_strfreev (parts);
return rv;
}
static GRegex*
anchored_regex (const char* pattern)
{
GRegex *rx;
GError *err;
gchar *anchored;
anchored = g_strdup_printf
("%s%s%s",
pattern[0] == '^' ? "" : "^",
pattern,
pattern[strlen(pattern)-1] == '$' ? "" : "$");
err = NULL;
rx = g_regex_new (anchored, G_REGEX_CASELESS|G_REGEX_OPTIMIZE, 0,
&err);
g_free (anchored);
if (!rx) {
g_warning ("error in regular expression '%s': %s",
pattern, err->message ? err->message : "error");
g_error_free (err);
return NULL;
}
return rx;
}
static gboolean
save_part_with_filename (MuMsg *msg, const char *pattern, MuConfig *opts)
{
GSList *lst, *cur;
GRegex *rx;
gboolean rv;
/* 'anchor' the pattern with '^...$' if not already */
rx = anchored_regex (pattern);
if (!rx)
return FALSE;
lst = mu_msg_part_find_files (msg, rx);
g_regex_unref (rx);
if (!lst) {
g_warning ("no matching attachments found");
return FALSE;
}
for (cur = lst, rv = TRUE; cur; cur = g_slist_next (cur))
rv &= save_part (msg, opts->targetdir,
GPOINTER_TO_UINT(cur->data),
opts->overwrite, opts->play);
g_slist_free (lst);
return rv;
}
struct _SaveData {
gboolean attachments_only;
gboolean result;
guint saved_num;
const gchar* targetdir;
gboolean overwrite;
gboolean play;
};
typedef struct _SaveData SaveData;
static gboolean
ignore_part (MuMsg *msg, MuMsgPart *part, SaveData *sd)
{
/* something went wrong somewhere; stop */
if (!sd->result)
return TRUE;
/* filter out non-attachments if only want attachments */
if (sd->attachments_only &&
!mu_msg_part_looks_like_attachment (part, TRUE))
return TRUE;
/* ignore multiparts */
if (part->type &&
g_ascii_strcasecmp (part->type, "multipart") == 0)
return TRUE;
return FALSE;
}
static void
save_part_if (MuMsg *msg, MuMsgPart *part, SaveData *sd)
{
gchar *filepath;
gboolean rv;
GError *err;
if (ignore_part (msg, part, sd))
return;
rv = FALSE;
filepath = NULL;
err = NULL;
filepath = mu_msg_part_filepath (msg, sd->targetdir, part->index, &err);
if (!filepath)
goto exit;
if (!mu_msg_part_save (msg, filepath, part->index,
sd->overwrite, FALSE, &err))
goto exit;
if (sd->play)
rv = mu_util_play (filepath, TRUE, FALSE, &err);
else
rv = TRUE;
++sd->saved_num;
exit:
if (err)
g_warning ("error saving MIME part: %s", err->message);
g_free (filepath);
g_clear_error (&err);
sd->result = rv;
}
static gboolean
save_certain_parts (MuMsg *msg, gboolean attachments_only,
const gchar *targetdir, gboolean overwrite, gboolean play)
{
SaveData sd;
sd.result = TRUE;
sd.saved_num = 0;
sd.attachments_only = attachments_only;
sd.overwrite = overwrite;
sd.targetdir = targetdir;
sd.play = play;
mu_msg_part_foreach (msg, FALSE,
(MuMsgPartForeachFunc)save_part_if,
&sd);
if (sd.saved_num == 0) {
g_warning ("no %s extracted from this message",
attachments_only ? "attachments" : "parts");
sd.result = FALSE;
}
return sd.result;
}
static gboolean
save_parts (const char *path, const char *filename, MuConfig *opts)
{
MuMsg* msg;
gboolean rv;
GError *err;
err = NULL;
msg = mu_msg_new_from_file (path, NULL, &err);
if (!msg) {
if (err) {
g_warning ("error: %s", err->message);
g_error_free (err);
}
return FALSE;
}
/* note, mu_cmd_extract already checks whether what's in opts
* is somewhat, so no need for extensive checking here */
/* should we save some explicit parts? */
if (opts->parts)
rv = save_numbered_parts (msg, opts);
else if (filename)
rv = save_part_with_filename (msg, filename, opts);
else if (opts->save_attachments) /* all attachments */
rv = save_certain_parts (msg, TRUE,
opts->targetdir, opts->overwrite,
opts->play);
else if (opts->save_all) /* all parts */
rv = save_certain_parts (msg, FALSE,
opts->targetdir, opts->overwrite,
opts->play);
else
g_assert_not_reached ();
mu_msg_unref (msg);
return rv;
}
#define color_maybe(C) do{ if (color) fputs ((C),stdout);}while(0)
static void
each_part_show (MuMsg *msg, MuMsgPart *part, gboolean color)
{
/* index */
g_print (" %u ", part->index);
/* filename */
color_maybe (MU_COLOR_GREEN);
mu_util_fputs_encoded (part->file_name ? part->file_name : "<none>",
stdout);
/* content-type */
color_maybe (MU_COLOR_BLUE);
mu_util_print_encoded (
" %s/%s ",
part->type ? part->type : "<none>",
part->subtype ? part->subtype : "<none>");
/* disposition */
color_maybe (MU_COLOR_MAGENTA);
mu_util_print_encoded (
"[%s]", part->disposition ? part->disposition : "<none>");
/* size */
if (part->size > 0) {
color_maybe (MU_COLOR_CYAN);
g_print (" (%s)", mu_str_size_s (part->size));
}
color_maybe (MU_COLOR_DEFAULT);
fputs ("\n", stdout);
}
static gboolean
show_parts (const char* path, MuConfig *opts, GError **err)
{
MuMsg* msg;
msg = mu_msg_new_from_file (path, NULL, err);
if (!msg)
return FALSE;
g_print ("MIME-parts in this message:\n");
mu_msg_part_foreach
(msg, FALSE, (MuMsgPartForeachFunc)each_part_show,
GUINT_TO_POINTER(!opts->nocolor));
mu_msg_unref (msg);
return TRUE;
}
static gboolean
check_params (MuConfig *opts)
{
size_t param_num;
param_num = mu_config_param_num (opts);
if (param_num < 2) {
g_warning ("usage: mu extract [options] <file> [<pattern>]");
return FALSE;
}
if (opts->save_attachments || opts->save_all)
if (opts->parts || param_num == 3) {
g_warning ("--save-attachments --save-all don't accept "
"a filename pattern or --parts");
return FALSE;
}
if (opts->save_attachments && opts->save_all) {
g_warning ("only one of --save-attachments and"
" --save-all is allowed");
return FALSE;
}
return TRUE;
}
MuError
mu_cmd_extract (MuConfig *opts, GError **err)
{
int rv;
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_EXTRACT,
MU_ERROR_INTERNAL);
if (!check_params (opts)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"error in parameters");
return MU_ERROR_IN_PARAMETERS;
}
if (!opts->params[2] && !opts->parts &&
!opts->save_attachments && !opts->save_all)
rv = show_parts (opts->params[1], opts, err); /* show, don't save */
else {
rv = mu_util_check_dir(opts->targetdir, FALSE, TRUE);
if (!rv)
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_WRITE,
"target '%s' is not a writable directory",
opts->targetdir);
else
rv = save_parts (opts->params[1],
opts->params[2],
opts); /* save */
}
return rv ? MU_OK : MU_ERROR;
}

881
mu/mu-cmd-find.c Normal file
View File

@ -0,0 +1,881 @@
/* -*-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, 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 <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include "mu-msg.h"
#include "mu-str.h"
#include "mu-date.h"
#include "mu-maildir.h"
#include "mu-index.h"
#include "mu-query.h"
#include "mu-msg-iter.h"
#include "mu-bookmarks.h"
#include "mu-runtime.h"
#include "mu-util.h"
#include "mu-cmd.h"
#include "mu-threader.h"
static gboolean output_links (MuMsgIter *iter, const char* linksdir,
gboolean clearlinks, GError **err);
static gboolean output_sexp (MuMsgIter *iter, gboolean threads,
gboolean include_unreadable, GError **err);
static gboolean output_xml (MuMsgIter *iter,gboolean include_unreadable,
GError **err);
static gboolean output_plain (MuMsgIter *iter, const char *fields,
int summary_len, gboolean threads,
gboolean color, gboolean include_unreadable,
GError **err);
static gboolean
print_xapian_query (MuQuery *xapian, const gchar *query, GError **err)
{
char *querystr;
querystr = mu_query_as_string (xapian, query, err);
if (!querystr)
return FALSE;
g_print ("%s\n", querystr);
g_free (querystr);
return TRUE;
}
/* returns MU_MSG_FIELD_ID_NONE if there is an error */
static MuMsgFieldId
sort_field_from_string (const char* fieldstr, GError **err)
{
MuMsgFieldId mfid;
mfid = mu_msg_field_id_from_name (fieldstr, FALSE);
/* not found? try a shortcut */
if (mfid == MU_MSG_FIELD_ID_NONE &&
strlen(fieldstr) == 1)
mfid = mu_msg_field_id_from_shortcut(fieldstr[0],
FALSE);
if (mfid == MU_MSG_FIELD_ID_NONE)
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"not a valid sort field: '%s'\n", fieldstr);
return mfid;
}
static gboolean
output_query_results (MuMsgIter *iter, MuConfig *opts, GError **err)
{
switch (opts->format) {
case MU_CONFIG_FORMAT_LINKS:
return output_links (iter, opts->linksdir, opts->clearlinks, err);
case MU_CONFIG_FORMAT_PLAIN:
return output_plain (iter, opts->fields,
opts->summary ? opts->summary_len : 0,
opts->threads, !opts->nocolor,
opts->include_unreadable, err);
case MU_CONFIG_FORMAT_XML:
return output_xml (iter, opts->include_unreadable, err);
case MU_CONFIG_FORMAT_SEXP:
return output_sexp (iter, opts->threads,
opts->include_unreadable, err);
default:
g_assert_not_reached ();
return FALSE;
}
}
static MuMsgIter*
run_query (MuQuery *xapian, const gchar *query, MuConfig *opts,
GError **err)
{
MuMsgIter *iter;
MuMsgFieldId sortid;
sortid = MU_MSG_FIELD_ID_NONE;
if (opts->sortfield) {
sortid = sort_field_from_string (opts->sortfield, err);
if (sortid == MU_MSG_FIELD_ID_NONE) /* error occured? */
return FALSE;
}
iter = mu_query_run (xapian, query, opts->threads, sortid,
opts->reverse, -1, err);
return iter;
}
static gboolean
process_query (MuQuery *xapian, const gchar *query, MuConfig *opts, GError **err)
{
MuMsgIter *iter;
gboolean rv;
iter = run_query (xapian, query, opts, err);
if (!iter)
return FALSE;
rv = output_query_results (iter, opts, err);
mu_msg_iter_destroy (iter);
return rv;
}
static gboolean
exec_cmd (const char *path, const char *cmd, GError **err)
{
gint status;
char *cmdline, *escpath;
gboolean rv;
if (access (path, R_OK) != 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_READ,
"cannot read %s: %s", path, strerror(errno));
return FALSE;
}
escpath = g_strescape (path, NULL);
cmdline = g_strdup_printf ("%s %s", cmd, escpath);
err = NULL;
rv = g_spawn_command_line_sync (cmdline, NULL, NULL,
&status, err);
g_free (cmdline);
g_free (escpath);
return rv;
}
static gboolean
exec_cmd_on_query (MuQuery *xapian, const gchar *query, MuConfig *opts,
GError **err)
{
MuMsgIter *iter;
gboolean rv;
size_t count;
if (!(iter = run_query (xapian, query, opts, err)))
return FALSE;
for (rv = TRUE, count = 0; !mu_msg_iter_is_done (iter);
mu_msg_iter_next(iter)) {
const char *path;
MuMsg *msg;
msg = mu_msg_iter_get_msg_floating (iter);
if (!msg)
continue;
path = mu_msg_get_path (msg);
if (!msg) {
g_warning ("cannot get path for msg");
continue;
}
rv = exec_cmd (path, opts->exec, err);
if (rv)
++count;
}
if (count == 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES,
"no matches for search expression");
return FALSE;
}
mu_msg_iter_destroy (iter);
return rv;
}
static gboolean
format_params_valid (MuConfig *opts, GError **err)
{
switch (opts->format) {
case MU_CONFIG_FORMAT_PLAIN:
case MU_CONFIG_FORMAT_SEXP:
case MU_CONFIG_FORMAT_LINKS:
case MU_CONFIG_FORMAT_XML:
case MU_CONFIG_FORMAT_XQUERY:
break;
default:
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"invalid output format %s",
opts->formatstr ? opts->formatstr : "<none>");
return FALSE;
}
if (opts->format == MU_CONFIG_FORMAT_LINKS && !opts->linksdir) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"missing --linksdir argument");
return FALSE;
}
if (opts->linksdir && opts->format != MU_CONFIG_FORMAT_LINKS) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"--linksdir is only valid with --format=links");
return FALSE;
}
return TRUE;
}
static gboolean
query_params_valid (MuConfig *opts, GError **err)
{
const gchar *xpath;
xpath = mu_runtime_path (MU_RUNTIME_PATH_XAPIANDB);
if (mu_util_check_dir (xpath, TRUE, FALSE))
return TRUE;
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_READ,
"'%s' is not a readable Xapian directory", xpath);
return FALSE;
}
static gchar*
resolve_bookmark (MuConfig *opts, GError **err)
{
MuBookmarks *bm;
char* val;
const gchar *bmfile;
bmfile = mu_runtime_path (MU_RUNTIME_PATH_BOOKMARKS);
bm = mu_bookmarks_new (bmfile);
if (!bm) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_OPEN,
"failed to open bookmarks file '%s'", bmfile);
return FALSE;
}
val = (gchar*)mu_bookmarks_lookup (bm, opts->bookmark);
if (!val)
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES,
"bookmark '%s' not found", opts->bookmark);
else
val = g_strdup (val);
mu_bookmarks_destroy (bm);
return val;
}
gchar*
str_quoted_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_c (str, '"');
g_string_append (str, params[i]);
g_string_append_c (str, '"');
}
return g_string_free (str, FALSE);
}
static gchar*
get_query (MuConfig *opts, GError **err)
{
gchar *query, *bookmarkval;
/* params[0] is 'find', actual search params start with [1] */
if (!opts->bookmark && !opts->params[1]) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"error in parameters");
return NULL;
}
bookmarkval = NULL;
if (opts->bookmark) {
bookmarkval = resolve_bookmark (opts, err);
if (!bookmarkval)
return NULL;
}
query = str_quoted_from_strv ((const gchar**)&opts->params[1]);
if (bookmarkval) {
gchar *tmp;
tmp = g_strdup_printf ("%s %s", bookmarkval, query);
g_free (query);
query = tmp;
}
g_free (bookmarkval);
return query;
}
static MuQuery*
get_query_obj (MuStore *store, GError **err)
{
MuQuery *mquery;
unsigned count;
count = mu_store_count (store, err);
if (count == (unsigned)-1)
return NULL;
if (count == 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_IS_EMPTY,
"the database is empty");
return NULL;
}
if (mu_store_needs_upgrade (store)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_NOT_UP_TO_DATE,
"the database is not up-to-date");
return NULL;
}
mquery = mu_query_new (store, err);
if (!mquery)
return NULL;
return mquery;
}
/* create a linksdir if it not exist yet; if it already existed,
* remove old links if opts->clearlinks was specified */
static gboolean
create_linksdir_maybe (const char *linksdir, gboolean clearlinks)
{
GError *err;
err = NULL;
/* note, mu_maildir_mkdir simply ignores whatever part of the
* mail dir already exists */
if (!mu_maildir_mkdir (linksdir, 0700, TRUE, &err))
goto fail;
if (clearlinks && !mu_maildir_clear_links (linksdir, &err))
goto fail;
return TRUE;
fail:
g_warning ("%s", err->message ? err->message : "unknown error");
g_clear_error (&err);
return FALSE;
}
static gboolean
link_message (const char *src, const char *destdir)
{
GError *err;
if (access (src, R_OK) != 0) {
if (errno == ENOENT)
g_warning ("cannot find source message %s", src);
else
g_warning ("cannot read source message %s: %s", src,
strerror (errno));
return FALSE;
}
err = NULL;
if (!mu_maildir_link (src, destdir, &err)) {
g_warning ("%s", err->message ? err->message :
"unknown error");
g_clear_error (&err);
return FALSE;
}
return TRUE;
}
static gboolean
output_links (MuMsgIter *iter, const char* linksdir, gboolean clearlinks,
GError **err)
{
size_t count, errcount;
MuMsgIter *myiter;
g_return_val_if_fail (iter, FALSE);
g_return_val_if_fail (linksdir, FALSE);
/* note: we create the linksdir even if there are no search results */
if (!create_linksdir_maybe (linksdir, clearlinks))
return FALSE;
for (myiter = iter, count = errcount = 0; !mu_msg_iter_is_done (myiter);
mu_msg_iter_next (myiter)) {
const char* path;
MuMsg *msg;
msg = mu_msg_iter_get_msg_floating (iter);
if (!msg)
continue;
path = mu_msg_get_path (msg);
if (access (path, R_OK) == 0) /* only link to readable */
link_message (path, linksdir) ? ++count : ++errcount;
}
if (errcount > 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_LINK,
"error linking %u message(s)", (unsigned)errcount);
return FALSE;
}
if (count == 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES,
"no matches for search expression");
return FALSE;
}
return TRUE;
}
static void
ansi_color_maybe (MuMsgFieldId mfid, gboolean color)
{
const char* ansi;
if (!color)
return; /* nothing to do */
switch (mfid) {
case MU_MSG_FIELD_ID_FROM:
ansi = MU_COLOR_CYAN; break;
case MU_MSG_FIELD_ID_TO:
case MU_MSG_FIELD_ID_CC:
case MU_MSG_FIELD_ID_BCC:
ansi = MU_COLOR_BLUE; break;
case MU_MSG_FIELD_ID_SUBJECT:
ansi = MU_COLOR_GREEN; break;
case MU_MSG_FIELD_ID_DATE:
ansi = MU_COLOR_MAGENTA; break;
default:
if (mu_msg_field_type(mfid) == MU_MSG_FIELD_TYPE_STRING)
ansi = MU_COLOR_YELLOW;
else
ansi = MU_COLOR_RED;
}
fputs (ansi, stdout);
}
static void
ansi_reset_maybe (MuMsgFieldId mfid, gboolean color)
{
if (!color)
return; /* nothing to do */
fputs (MU_COLOR_DEFAULT, stdout);
}
static const char*
display_field (MuMsg *msg, MuMsgFieldId mfid)
{
gint64 val;
switch (mu_msg_field_type(mfid)) {
case MU_MSG_FIELD_TYPE_STRING: {
const gchar *str;
str = mu_msg_get_field_string (msg, mfid);
return str ? str : "";
}
case MU_MSG_FIELD_TYPE_INT:
if (mfid == MU_MSG_FIELD_ID_PRIO) {
val = mu_msg_get_field_numeric (msg, mfid);
return mu_msg_prio_name ((MuMsgPrio)val);
} else if (mfid == MU_MSG_FIELD_ID_FLAGS) {
val = mu_msg_get_field_numeric (msg, mfid);
return mu_str_flags_s ((MuFlags)val);
} else /* as string */
return mu_msg_get_field_string (msg, mfid);
case MU_MSG_FIELD_TYPE_TIME_T:
val = mu_msg_get_field_numeric (msg, mfid);
return mu_date_str_s ("%c", (time_t)val);
case MU_MSG_FIELD_TYPE_BYTESIZE:
val = mu_msg_get_field_numeric (msg, mfid);
return mu_str_size_s ((unsigned)val);
default:
g_return_val_if_reached (NULL);
}
}
static void
print_summary (MuMsg *msg, int summary_len)
{
const char* body;
char *summ;
body = mu_msg_get_body_text(msg);
summ = body ? mu_str_summarize (body, (unsigned)summary_len) : NULL;
g_print ("Summary: ");
mu_util_fputs_encoded (summ ? summ : "<none>", stdout);
g_print ("\n");
g_free (summ);
}
static void
thread_indent (MuMsgIter *iter)
{
const MuMsgIterThreadInfo *ti;
const char* threadpath;
int i;
gboolean is_root, first_child, empty_parent, is_dup;
ti = mu_msg_iter_get_thread_info (iter);
if (!ti) {
g_warning ("cannot get thread-info for message %u",
mu_msg_iter_get_docid (iter));
return;
}
threadpath = ti->threadpath;
/* fputs (threadpath, stdout); */
/* fputs (" ", stdout); */
is_root = ti->prop & MU_MSG_ITER_THREAD_PROP_ROOT;
first_child = ti->prop & MU_MSG_ITER_THREAD_PROP_FIRST_CHILD;
empty_parent = ti->prop & MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT;
is_dup = ti->prop & MU_MSG_ITER_THREAD_PROP_DUP;
/* FIXME: count the colons... */
for (i = 0; *threadpath; ++threadpath)
i += (*threadpath == ':') ? 1 : 0;
/* indent */
while (i --> 0)
fputs (" ", stdout);
if (!is_root) {
fputs (first_child ? "`" : "|", stdout);
fputs (empty_parent ? "*> " : is_dup ? "=> " : "-> ", stdout);
}
}
static void
output_plain_fields (MuMsg *msg, const char *fields,
gboolean color, gboolean threads)
{
const char* myfields;
int nonempty;
for (myfields = fields, nonempty = 0; *myfields; ++myfields) {
MuMsgFieldId mfid;
mfid = mu_msg_field_id_from_shortcut (*myfields, FALSE);
if (mfid == MU_MSG_FIELD_ID_NONE ||
(!mu_msg_field_xapian_value (mfid) &&
!mu_msg_field_xapian_contact (mfid)))
nonempty += printf ("%c", *myfields);
else {
ansi_color_maybe (mfid, color);
nonempty += mu_util_fputs_encoded
(display_field (msg, mfid), stdout);
ansi_reset_maybe (mfid, color);
}
}
if (nonempty)
fputs ("\n", stdout);
}
static gboolean
output_plain (MuMsgIter *iter, const char *fields, int summary_len,
gboolean threads, gboolean color, gboolean include_unreadable, GError **err)
{
MuMsgIter *myiter;
size_t count;
g_return_val_if_fail (iter, FALSE);
g_return_val_if_fail (fields, FALSE);
for (myiter = iter, count = 0; !mu_msg_iter_is_done (myiter);
mu_msg_iter_next (myiter)) {
MuMsg *msg;
msg = mu_msg_iter_get_msg_floating (iter); /* don't unref */
if (!msg)
continue;
/* only return messages if they're actually
* readable (ie, live also outside the database) */
if (!include_unreadable && !mu_msg_is_readable (msg))
continue;
/* we reuse the color (whatever that may be)
* for message-priority for threads, too */
ansi_color_maybe (MU_MSG_FIELD_ID_PRIO, color);
if (threads)
thread_indent (iter);
output_plain_fields (msg, fields, color, threads);
if (summary_len > 0)
print_summary (msg, summary_len);
++count;
}
if (count == 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES,
"no existing matches for search expression");
return FALSE;
}
return TRUE;
}
static void
print_attr_xml (const char* elm, const char *str)
{
gchar *esc;
if (mu_str_is_empty(str))
return; /* empty: don't include */
esc = g_markup_escape_text (str, -1);
g_print ("\t\t<%s>%s</%s>\n", elm, esc, elm);
g_free (esc);
}
static gboolean
output_sexp (MuMsgIter *iter, gboolean threads,
gboolean include_unreadable, GError **err)
{
MuMsgIter *myiter;
size_t count;
g_return_val_if_fail (iter, FALSE);
for (myiter = iter, count = 0; !mu_msg_iter_is_done (myiter);
mu_msg_iter_next (myiter)) {
MuMsg *msg;
char *sexp;
const MuMsgIterThreadInfo *ti;
msg = mu_msg_iter_get_msg_floating (iter);
if (!msg)
return FALSE;
/* only return messages if they're actually
* readable (ie, live also outside the database) */
if (!include_unreadable && !mu_msg_is_readable (msg))
continue;
ti = threads ? mu_msg_iter_get_thread_info (iter) : NULL;
sexp = mu_msg_to_sexp (msg,
mu_msg_iter_get_docid (iter),
ti, TRUE, FALSE);
fputs (sexp, stdout);
g_free (sexp);
++count;
}
if (count == 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES,
"no existing matches for search expression");
return FALSE;
}
return TRUE;
}
static void
output_xml_msg (MuMsg *msg)
{
g_print ("\t<message>\n");
print_attr_xml ("from", mu_msg_get_from (msg));
print_attr_xml ("to", mu_msg_get_to (msg));
print_attr_xml ("cc", mu_msg_get_cc (msg));
print_attr_xml ("subject", mu_msg_get_subject (msg));
g_print ("\t\t<date>%u</date>\n",
(unsigned)mu_msg_get_date (msg));
g_print ("\t\t<size>%u</size>\n", (unsigned)mu_msg_get_size (msg));
print_attr_xml ("msgid", mu_msg_get_msgid (msg));
print_attr_xml ("path", mu_msg_get_path (msg));
print_attr_xml ("maildir", mu_msg_get_maildir (msg));
g_print ("\t</message>\n");
}
static gboolean
output_xml (MuMsgIter *iter, gboolean include_unreadable, GError **err)
{
MuMsgIter *myiter;
size_t count;
g_return_val_if_fail (iter, FALSE);
g_print ("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
g_print ("<messages>\n");
for (myiter = iter, count = 0; !mu_msg_iter_is_done (myiter);
mu_msg_iter_next (myiter)) {
MuMsg *msg;
msg = mu_msg_iter_get_msg_floating (iter); /* don't unref */
if (!msg)
return FALSE;
/* only return messages if they're actually
* readable (ie, live also outside the database) */
if (!include_unreadable && !mu_msg_is_readable (msg))
continue;
output_xml_msg (msg);
++count;
}
g_print ("</messages>\n");
if (count == 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES,
"no existing matches for search expression");
return FALSE;
}
return TRUE;
}
static gboolean
execute_find (MuStore *store, MuConfig *opts, GError **err)
{
char *query_str;
MuQuery *oracle;
gboolean rv;
oracle = get_query_obj(store, err);
if (!oracle)
return FALSE;
query_str = get_query (opts, err);
if (!query_str) {
mu_query_destroy (oracle);
return FALSE;
}
if (opts->format == MU_CONFIG_FORMAT_XQUERY)
rv = print_xapian_query (oracle, query_str, err);
else if (opts->exec)
rv = exec_cmd_on_query (oracle, query_str, opts, err);
else
rv = process_query (oracle, query_str, opts, err);
mu_query_destroy (oracle);
g_free (query_str);
return rv;
}
static void
show_usage (void)
{
const char *usage_str =
"usage: mu find [options] <search expression>\n";
g_message ("%s", usage_str);
}
MuError
mu_cmd_find (MuStore *store, MuConfig *opts, GError **err)
{
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_FIND,
MU_ERROR_INTERNAL);
if (!query_params_valid (opts, err) || !format_params_valid(opts, err)) {
if (MU_G_ERROR_CODE(err) == MU_ERROR_IN_PARAMETERS)
show_usage ();
return MU_G_ERROR_CODE (err);
}
if (!execute_find (store, opts, err))
return MU_G_ERROR_CODE(err);
else
return MU_OK;
}

410
mu/mu-cmd-index.c Normal file
View File

@ -0,0 +1,410 @@
/* -*-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, 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-cmd.h"
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include "mu-util.h"
#include "mu-msg.h"
#include "mu-index.h"
#include "mu-store.h"
#include "mu-runtime.h"
static gboolean MU_CAUGHT_SIGNAL;
static void
sig_handler (int sig)
{
if (!MU_CAUGHT_SIGNAL && sig == SIGINT) { /* Ctrl-C */
g_print ("\n");
g_warning ("shutting down gracefully, "
"press again to kill immediately");
}
MU_CAUGHT_SIGNAL = TRUE;
}
static void
install_sig_handler (void)
{
struct sigaction action;
int i, sigs[] = { SIGINT, SIGHUP, SIGTERM };
MU_CAUGHT_SIGNAL = FALSE;
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESETHAND;
for (i = 0; i != G_N_ELEMENTS(sigs); ++i)
if (sigaction (sigs[i], &action, NULL) != 0)
g_critical ("set sigaction for %d failed: %s",
sigs[i], strerror (errno));;
}
static gboolean
check_params (MuConfig *opts, GError **err)
{
/* param[0] == 'index' there should be no param[1] */
if (opts->params[1]) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"unexpected parameter");
return FALSE;
}
if (opts->xbatchsize < 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"the batch size must be non-negative");
return FALSE;
}
if (opts->max_msg_size < 0) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"the maximum message size must be non-negative");
return FALSE;
}
return TRUE;
}
static gboolean
check_maildir (const char *maildir, GError **err)
{
if (!maildir) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"no maildir to work on; use --maildir=");
return FALSE;
}
if (!g_path_is_absolute (maildir)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"maildir path '%s' is not absolute",
maildir);
return FALSE;
}
if (!mu_util_check_dir (maildir, TRUE, FALSE)) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"not a valid Maildir: %s", maildir);
return FALSE;
}
return TRUE;
}
static MuError
index_msg_silent_cb (MuIndexStats* stats, void *user_data)
{
return MU_CAUGHT_SIGNAL ? MU_STOP: MU_OK;
}
static void
backspace (unsigned u)
{
static gboolean init = FALSE;
static char backspace[80];
if (G_UNLIKELY(!init)) {
/* fill with backspaces */
int i;
for (i = 0; i != sizeof(backspace); ++i)
backspace[i] = '\b';
init = TRUE;
}
backspace[MIN(u,sizeof(backspace))] = '\0';
fputs (backspace, stdout);
backspace[u] = '\b';
}
static void
print_stats (MuIndexStats* stats, gboolean clear, gboolean color)
{
const char *kars="-\\|/";
char output[120];
static unsigned i = 0, len = 0;
if (clear)
backspace (len);
if (color)
len = (unsigned)snprintf
(output, sizeof(output),
MU_COLOR_YELLOW "%c " MU_COLOR_DEFAULT
"processing mail; "
"processed: " MU_COLOR_GREEN "%u; " MU_COLOR_DEFAULT
"updated/new: " MU_COLOR_GREEN "%u" MU_COLOR_DEFAULT
", cleaned-up: " MU_COLOR_GREEN "%u" MU_COLOR_DEFAULT,
(unsigned)kars[++i % 4],
(unsigned)stats->_processed,
(unsigned)stats->_updated,
(unsigned)stats->_cleaned_up);
else
len = (unsigned)snprintf
(output, sizeof(output),
"%c processing mail; processed: %u; "
"updated/new: %u, cleaned-up: %u",
(unsigned)kars[++i % 4],
(unsigned)stats->_processed,
(unsigned)stats->_updated,
(unsigned)stats->_cleaned_up);
fputs (output, stdout);
fflush (stdout);
}
struct _IndexData {
gboolean color;
};
typedef struct _IndexData IndexData;
static MuError
index_msg_cb (MuIndexStats* stats, IndexData *idata)
{
if (stats->_processed % 25)
return MU_OK;
print_stats (stats, TRUE, idata->color);
return MU_CAUGHT_SIGNAL ? MU_STOP: MU_OK;
}
static gboolean
database_version_check_and_update (MuStore *store, MuConfig *opts,
GError **err)
{
if (mu_store_count (store, err) == 0)
return TRUE;
/* when rebuilding, we empty the database before doing
* anything */
if (opts->rebuild) {
opts->reindex = TRUE;
g_debug ("clearing database");
g_debug ("clearing contacts-cache");
return mu_store_clear (store, err);
}
if (!mu_store_needs_upgrade (store))
return TRUE; /* ok, nothing to do */
/* ok, database is not up to date */
if (opts->autoupgrade) {
opts->reindex = TRUE;
g_debug ("auto-upgrade: clearing old database and cache");
return mu_store_clear (store, err);
}
return FALSE;
}
static void
show_time (unsigned t, unsigned processed, gboolean color)
{
if (color) {
if (t)
g_message ("elapsed: "
MU_COLOR_GREEN "%u" MU_COLOR_DEFAULT
" second(s), ~ "
MU_COLOR_GREEN "%u" MU_COLOR_DEFAULT
" msg/s",
t, processed/t);
else
g_message ("elapsed: "
MU_COLOR_GREEN "%u" MU_COLOR_DEFAULT
" second(s)", t);
} else {
if (t)
g_message ("elapsed: %u second(s), ~ %u msg/s",
t, processed/t);
else
g_message ("elapsed: %u second(s)", t);
}
}
static MuError
cleanup_missing (MuIndex *midx, MuConfig *opts, MuIndexStats *stats,
gboolean show_progress, GError **err)
{
MuError rv;
time_t t;
IndexData idata;
g_message ("cleaning up messages [%s]",
mu_runtime_path (MU_RUNTIME_PATH_XAPIANDB));
mu_index_stats_clear (stats);
t = time (NULL);
idata.color = !opts->nocolor;
rv = mu_index_cleanup
(midx, stats,
show_progress ?
(MuIndexCleanupDeleteCallback)index_msg_cb :
(MuIndexCleanupDeleteCallback)index_msg_silent_cb,
&idata, err);
if (!opts->quiet) {
print_stats (stats, TRUE, !opts->nocolor);
g_print ("\n");
show_time ((unsigned)(time(NULL)-t),stats->_processed,
!opts->nocolor);
}
return (rv == MU_OK || rv == MU_STOP) ? MU_OK: MU_G_ERROR_CODE(err);
}
static void
index_title (const char* maildir, const char* xapiandir, gboolean color)
{
if (color)
g_message ("indexing messages under "
MU_COLOR_BLUE "%s" MU_COLOR_DEFAULT
" ["
MU_COLOR_BLUE "%s" MU_COLOR_DEFAULT
"]", maildir, xapiandir);
else
g_message ("indexing messages under %s [%s]",
maildir, xapiandir);
}
static MuError
cmd_index (MuIndex *midx, MuConfig *opts, MuIndexStats *stats,
gboolean show_progress, GError **err)
{
IndexData idata;
MuError rv;
time_t t;
t = time (NULL);
index_title (opts->maildir, mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB),
!opts->nocolor);
idata.color = !opts->nocolor;
rv = mu_index_run (midx, opts->maildir, opts->reindex, stats,
show_progress ?
(MuIndexMsgCallback)index_msg_cb :
(MuIndexMsgCallback)index_msg_silent_cb,
NULL, &idata);
if (!opts->quiet) {
print_stats (stats, TRUE, !opts->nocolor);
g_print ("\n");
show_time ((unsigned)(time(NULL)-t),
stats->_processed, !opts->nocolor);
}
if (rv == MU_OK || rv == MU_STOP) {
MU_WRITE_LOG ("index: processed: %u; updated/new: %u",
stats->_processed, stats->_updated);
if (rv == MU_OK && !opts->nocleanup)
rv = cleanup_missing (midx, opts, stats, show_progress, err);
if (rv == MU_STOP)
rv = MU_OK;
} else
g_set_error (err, MU_ERROR_DOMAIN, rv, "error while indexing");
return rv;
}
static MuIndex*
init_mu_index (MuStore *store, MuConfig *opts, GError **err)
{
MuIndex *midx;
if (!check_params (opts, err))
return NULL;
if (!database_version_check_and_update(store, opts, err))
return NULL;
if (!check_maildir (opts->maildir, err))
return NULL;
midx = mu_index_new (store, err);
if (!midx)
return NULL;
mu_index_set_max_msg_size (midx, opts->max_msg_size);
mu_index_set_xbatch_size (midx, opts->xbatchsize);
return midx;
}
MuError
mu_cmd_index (MuStore *store, MuConfig *opts, GError **err)
{
MuIndex *midx;
MuIndexStats stats;
gboolean rv, show_progress;
g_return_val_if_fail (opts, FALSE);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_INDEX,
FALSE);
/* create, and do error handling if needed */
midx = init_mu_index (store, opts, err);
if (!midx)
return MU_G_ERROR_CODE(err);
/* note, 'opts->quiet' already cause g_message output not to
* be shown; here, we make sure we only print progress info if
* opts->quiet is false case and when stdout is a tty */
show_progress = !opts->quiet && isatty(fileno(stdout));
mu_index_stats_clear (&stats);
install_sig_handler ();
rv = cmd_index (midx, opts, &stats, show_progress, err);
mu_index_destroy (midx);
return rv;
}

1254
mu/mu-cmd-server.c Normal file

File diff suppressed because it is too large Load Diff

487
mu/mu-cmd.c Normal file
View File

@ -0,0 +1,487 @@
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
/*
** 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, 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "mu-msg.h"
#include "mu-msg-part.h"
#include "mu-cmd.h"
#include "mu-util.h"
#include "mu-str.h"
#include "mu-date.h"
#include "mu-maildir.h"
#include "mu-contacts.h"
#include "mu-runtime.h"
#include "mu-flags.h"
#include "mu-store.h"
#define VIEW_TERMINATOR '\f' /* form-feed */
static gboolean
view_msg_sexp (MuMsg *msg)
{
char *sexp;
sexp = mu_msg_to_sexp (msg, 0, NULL, FALSE, FALSE);
fputs (sexp, stdout);
g_free (sexp);
return TRUE;
}
static void
each_part (MuMsg *msg, MuMsgPart *part, gchar **attach)
{
if (mu_msg_part_looks_like_attachment (part, TRUE) &&
(part->file_name)) {
char *tmp = *attach;
*attach = g_strdup_printf ("%s%s'%s'",
*attach ? *attach : "",
*attach ? ", " : "",
part->file_name);
g_free (tmp);
}
}
/* return comma-sep'd list of attachments */
static gchar *
get_attach_str (MuMsg *msg)
{
gchar *attach;
attach = NULL;
mu_msg_part_foreach (msg, FALSE,
(MuMsgPartForeachFunc)each_part, &attach);
return attach;
}
#define color_maybe(C) do{ if (color) fputs ((C),stdout);}while(0)
static void
print_field (const char* field, const char *val, gboolean color)
{
if (!val)
return;
color_maybe (MU_COLOR_MAGENTA);
mu_util_fputs_encoded (field, stdout);
color_maybe (MU_COLOR_DEFAULT);
fputs (": ", stdout);
if (val) {
color_maybe (MU_COLOR_GREEN);
mu_util_fputs_encoded (val, stdout);
}
color_maybe (MU_COLOR_DEFAULT);
fputs ("\n", stdout);
}
/* a summary_len of 0 mean 'don't show summary, show body */
static void
body_or_summary (MuMsg *msg, unsigned summary_len, gboolean color)
{
const char* field;
field = mu_msg_get_body_text (msg);
if (!field)
return; /* no body -- nothing more to do */
if (summary_len != 0) {
gchar *summ;
summ = mu_str_summarize (field, summary_len);
print_field ("Summary", summ, color);
g_free (summ);
} else {
color_maybe (MU_COLOR_YELLOW);
mu_util_print_encoded ("\n%s\n", field);
color_maybe (MU_COLOR_DEFAULT);
}
}
/* we ignore fields for now */
/* summary_len == 0 means "no summary */
static gboolean
view_msg_plain (MuMsg *msg, const gchar *fields, unsigned summary_len,
gboolean color)
{
gchar *attachs;
time_t date;
const GSList *lst;
print_field ("From", mu_msg_get_from (msg), color);
print_field ("To", mu_msg_get_to (msg), color);
print_field ("Cc", mu_msg_get_cc (msg), color);
print_field ("Bcc", mu_msg_get_bcc (msg), color);
print_field ("Subject", mu_msg_get_subject (msg), color);
if ((date = mu_msg_get_date (msg)))
print_field ("Date", mu_date_str_s ("%c", date),
color);
if ((lst = mu_msg_get_tags (msg))) {
gchar *tags;
tags = mu_str_from_list (lst,',');
print_field ("Tags", tags, color);
g_free (tags);
}
if ((attachs = get_attach_str (msg))) {
print_field ("Attachments", attachs, color);
g_free (attachs);
}
body_or_summary (msg, summary_len, color);
return TRUE;
}
static gboolean
handle_msg (const char *fname, MuConfig *opts, GError **err)
{
MuMsg *msg;
gboolean rv;
err = NULL;
msg = mu_msg_new_from_file (fname, NULL, err);
if (!msg)
return FALSE;
switch (opts->format) {
case MU_CONFIG_FORMAT_PLAIN:
rv = view_msg_plain
(msg, NULL,
opts->summary ? opts->summary_len : 0,
!opts->nocolor);
break;
case MU_CONFIG_FORMAT_SEXP:
rv = view_msg_sexp (msg);
break;
default:
g_critical ("bug: should not be reached");
rv = FALSE;
}
mu_msg_unref (msg);
return rv;
}
static gboolean
view_params_valid (MuConfig *opts, GError **err)
{
/* note: params[0] will be 'view' */
if (!opts->params[0] || !opts->params[1]) {
mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS,
"error in parameters");
return FALSE;
}
switch (opts->format) {
case MU_CONFIG_FORMAT_PLAIN:
case MU_CONFIG_FORMAT_SEXP:
break;
default:
mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS,
"invalid output format");
return FALSE;
}
return TRUE;
}
MuError
mu_cmd_view (MuConfig *opts, GError **err)
{
int i;
gboolean rv;
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_VIEW,
MU_ERROR_INTERNAL);
rv = view_params_valid(opts, err);
if (!rv)
goto leave;
for (i = 1; opts->params[i]; ++i) {
rv = handle_msg (opts->params[i], opts, err);
if (!rv)
break;
/* add a separator between two messages? */
if (opts->terminator)
g_print ("%c", VIEW_TERMINATOR);
}
leave:
if (!rv)
return err && *err ? (*err)->code : MU_ERROR;
return MU_OK;
}
MuError
mu_cmd_mkdir (MuConfig *opts, GError **err)
{
int i;
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_MKDIR,
MU_ERROR_INTERNAL);
if (!opts->params[1]) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"missing directory parameter");
return MU_ERROR_IN_PARAMETERS;
}
for (i = 1; opts->params[i]; ++i)
if (!mu_maildir_mkdir (opts->params[i], opts->dirmode,
FALSE, err))
return err && *err ? (*err)->code :
MU_ERROR_FILE_CANNOT_MKDIR;
return MU_OK;
}
static gboolean
check_file_okay (const char *path, gboolean cmd_add)
{
if (!g_path_is_absolute (path)) {
g_warning ("path is not absolute: %s", path);
return FALSE;
}
if (cmd_add && access(path, R_OK) != 0) {
g_warning ("path is not readable: %s: %s",
path, strerror (errno));
return FALSE;
}
return TRUE;
}
gboolean
check_add_params (MuConfig *opts, GError **err)
{
return TRUE;
}
MuError
mu_cmd_add (MuStore *store, MuConfig *opts, GError **err)
{
gboolean allok;
int i;
g_return_val_if_fail (store, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_ADD,
MU_ERROR_INTERNAL);
/* note: params[0] will be 'add' */
if (!opts->params[0] || !opts->params[1]) {
g_message ("usage: mu add <file> [<files>]");
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"missing source and/or target");
return MU_ERROR_IN_PARAMETERS;
}
for (i = 1, allok = TRUE; opts->params[i]; ++i) {
const char* src;
src = opts->params[i];
if (!check_file_okay (src, TRUE) ||
mu_store_add_path (store, src, NULL, err) ==
MU_STORE_INVALID_DOCID) {
MU_WRITE_LOG ("failed to add %s", src);
allok = FALSE;
}
}
if (!allok) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_STORE_FAILED,
"store failed for some message(s)");
return MU_ERROR_XAPIAN_STORE_FAILED;
}
return MU_OK;
}
MuError
mu_cmd_remove (MuStore *store, MuConfig *opts, GError **err)
{
gboolean allok;
int i;
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
g_return_val_if_fail (opts->cmd == MU_CONFIG_CMD_REMOVE,
MU_ERROR_INTERNAL);
/* note: params[0] will be 'add' */
if (!opts->params[0] || !opts->params[1]) {
g_warning ("usage: mu remove <file> [<files>]");
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"missing source and/or target");
return MU_ERROR_IN_PARAMETERS;
}
for (i = 1, allok = TRUE; opts->params[i]; ++i) {
const char* src;
src = opts->params[i];
if (!check_file_okay (src, FALSE) ||
!mu_store_remove_path (store, src)) {
allok = FALSE;
MU_WRITE_LOG ("failed to remove %s", src);
}
}
if (!allok) {
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_STORE_FAILED,
"remove failed for some message(s)");
return MU_ERROR_XAPIAN_REMOVE_FAILED;
}
return MU_OK;
}
static void
show_usage (void)
{
g_message ("usage: mu command [options] [parameters]");
g_message ("where command is one of index, find, cfind, view, mkdir, "
"extract, add, remove or server");
g_message ("see the mu, mu-<command> or mu-easy manpages for "
"more information");
}
static void
show_version (void)
{
g_print ("mu (mail indexer/searcher) version " VERSION "\n"
"Copyright (C) 2008-2011 Dirk-Jan C. Binnema (GPLv3+)\n");
}
typedef MuError (*store_func) (MuStore *, MuConfig *, GError **err);
MuError
with_store (store_func func, MuConfig *opts, gboolean read_only,
GError **err)
{
MuStore *store;
if (read_only)
store = mu_store_new_read_only
(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB),
err);
else
store = mu_store_new_writable
(mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB),
mu_runtime_path(MU_RUNTIME_PATH_CONTACTS),
opts->rebuild, err);
if (!store)
return MU_G_ERROR_CODE(err);
else {
MuError merr;
merr = func (store, opts, err);
mu_store_unref (store);
return merr;
}
}
gboolean
check_params (MuConfig *opts, GError **err)
{
if (!opts->params||!opts->params[0]) {/* no command? */
show_usage ();
mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS,
"error in parameters");
return FALSE;
}
return TRUE;
}
MuError
mu_cmd_execute (MuConfig *opts, GError **err)
{
g_return_val_if_fail (opts, MU_ERROR_INTERNAL);
if (opts->version) {
show_version ();
return MU_OK;
}
if (!check_params(opts, err))
return MU_G_ERROR_CODE(err);
switch (opts->cmd) {
case MU_CONFIG_CMD_CFIND: return mu_cmd_cfind (opts, err);
case MU_CONFIG_CMD_MKDIR: return mu_cmd_mkdir (opts, err);
case MU_CONFIG_CMD_VIEW: return mu_cmd_view (opts, err);
case MU_CONFIG_CMD_EXTRACT: return mu_cmd_extract (opts, err);
case MU_CONFIG_CMD_FIND:
return with_store (mu_cmd_find, opts, TRUE, err);
case MU_CONFIG_CMD_INDEX:
return with_store (mu_cmd_index, opts, FALSE, err);
case MU_CONFIG_CMD_ADD:
return with_store (mu_cmd_add, opts, FALSE, err);
case MU_CONFIG_CMD_REMOVE:
return with_store (mu_cmd_remove, opts, FALSE, err);
case MU_CONFIG_CMD_SERVER:
return with_store (mu_cmd_server, opts, FALSE, err);
default:
show_usage ();
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
"unknown command '%s'", opts->cmdstr);
return MU_ERROR_IN_PARAMETERS;
}
}

165
mu/mu-cmd.h Normal file
View File

@ -0,0 +1,165 @@
/* -*-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.
**
*/
#ifndef __MU_CMD_H__
#define __MU_CMD_H__
#include <glib.h>
#include <mu-config.h>
#include <mu-store.h>
G_BEGIN_DECLS
/**
* execute the 'mkdir' command
*
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeded,
* some error code otherwise
*/
MuError mu_cmd_mkdir (MuConfig *opts, GError **err);
/**
* execute the 'view' command
*
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeded,
* some error code otherwise
*/
MuError mu_cmd_view (MuConfig *opts, GError **err);
/**
* execute the 'index' command
*
* @param store store object to use
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeded,
* some error code otherwise
*/
MuError mu_cmd_index (MuStore *store, MuConfig *opt, GError **err);
/**
* execute the 'find' command
*
* @param store store object to use
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeds and
* >MU_OK (0) results, MU_EXITCODE_NO_MATCHES if the command
* succeeds but there no matches, some error code for all other errors
*/
MuError mu_cmd_find (MuStore *store, MuConfig *opts, GError **err);
/**
* execute the 'extract' command
*
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeds,
* some error code otherwise
*/
MuError mu_cmd_extract (MuConfig *opts, GError **err);
/**
* execute the 'mv' command
*
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeds,
* some error code otherwise
*/
MuError mu_cmd_mv (MuConfig *opts, GError **err);
/**
* execute the cfind command
*
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeds,
* some error code otherwise
*/
MuError mu_cmd_cfind (MuConfig *opts, GError **err);
/**
* execute the add command
*
* @param store store object to use
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeds,
* some error code otherwise
*/
MuError mu_cmd_add (MuStore *store, MuConfig *opts, GError **err);
/**
* execute the remove command
*
* @param store store object to use
* @param opts configuration options
* @param err receives error information, or NULL
*
* @return MU_OK (0) if the command succeeds,
* some error code otherwise
*/
MuError mu_cmd_remove (MuStore *store, MuConfig *opts, GError **err);
/**
* execute the server command
* @param store store object to use
* @param opts configuration options
*
* @return MU_OK (0) if the command succeeds,
* some error code otherwise
*/
MuError mu_cmd_server (MuStore *store, MuConfig *opts,GError**/*unused*/);
/**
* execute some mu command, based on 'opts'
*
* @param opts configuration option
* @param err receives error information, or NULL
*
* @return MU_OK if all went wall, some error code otherwise
*/
MuError mu_cmd_execute (MuConfig *opts, GError **err);
G_END_DECLS
#endif /*__MU_CMD_H__*/

536
mu/mu-config.c Normal file
View File

@ -0,0 +1,536 @@
/* -*-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, 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> /* memset */
#include <unistd.h>
#include <stdio.h>
#include "mu-util.h"
#include "mu-config.h"
#include "mu-cmd.h"
static MuConfig MU_CONFIG;
#define DEFAULT_SUMMARY_LEN 5
static MuConfigFormat
get_output_format (const char *formatstr)
{
int i;
struct {
const char* name;
MuConfigFormat format;
} formats [] = {
{"mutt-alias", MU_CONFIG_FORMAT_MUTT_ALIAS},
{"mutt-ab", MU_CONFIG_FORMAT_MUTT_AB},
{"wl", MU_CONFIG_FORMAT_WL},
{"csv", MU_CONFIG_FORMAT_CSV},
{"org-contact", MU_CONFIG_FORMAT_ORG_CONTACT},
{"bbdb", MU_CONFIG_FORMAT_BBDB},
{"links", MU_CONFIG_FORMAT_LINKS},
{"plain", MU_CONFIG_FORMAT_PLAIN},
{"sexp", MU_CONFIG_FORMAT_SEXP},
{"xml", MU_CONFIG_FORMAT_XML},
{"xquery", MU_CONFIG_FORMAT_XQUERY}
};
for (i = 0; i != G_N_ELEMENTS(formats); i++)
if (strcmp (formats[i].name, formatstr) == 0)
return formats[i].format;
return MU_CONFIG_FORMAT_UNKNOWN;
}
static void
set_group_mu_defaults (void)
{
gchar *exp;
if (!MU_CONFIG.muhome)
MU_CONFIG.muhome = mu_util_guess_mu_homedir();
exp = mu_util_dir_expand(MU_CONFIG.muhome);
if (exp) {
g_free(MU_CONFIG.muhome);
MU_CONFIG.muhome = exp;
}
/* check for the MU_NOCOLOR env var; but in any case don't
* use colors unless we're writing to a tty */
if (g_getenv (MU_NOCOLOR) != NULL)
MU_CONFIG.nocolor = TRUE;
if (!isatty(fileno(stdout)))
MU_CONFIG.nocolor = TRUE;
}
static GOptionGroup*
config_options_group_mu (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"debug", 'd', 0, G_OPTION_ARG_NONE, &MU_CONFIG.debug,
"print debug output to standard error (false)", NULL},
{"quiet", 'q', 0, G_OPTION_ARG_NONE, &MU_CONFIG.quiet,
"don't give any progress information (false)", NULL},
{"version", 'v', 0, G_OPTION_ARG_NONE, &MU_CONFIG.version,
"display version and copyright information (false)", NULL},
{"muhome", 0, 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.muhome,
"specify an alternative mu directory", NULL},
{"log-stderr", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.log_stderr,
"log to standard error (false)", NULL},
{"nocolor", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.nocolor,
"don't use ANSI-colors in some output (false)", NULL},
{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY,
&MU_CONFIG.params, "parameters", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
og = g_option_group_new("mu", "general mu options", "", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static void
set_group_index_defaults (void)
{
char *exp;
if (!MU_CONFIG.maildir)
MU_CONFIG.maildir = mu_util_guess_maildir ();
if (MU_CONFIG.maildir) {
exp = mu_util_dir_expand(MU_CONFIG.maildir);
if (exp) {
g_free(MU_CONFIG.maildir);
MU_CONFIG.maildir = exp;
}
}
}
static GOptionGroup*
config_options_group_index (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"maildir", 'm', 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.maildir,
"top of the maildir", NULL},
{"reindex", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.reindex,
"index even already indexed messages (false)", NULL},
{"rebuild", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.rebuild,
"rebuild the database from scratch (false)", NULL},
{"autoupgrade", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.autoupgrade,
"auto-upgrade the database with new mu versions (false)",
NULL},
{"nocleanup", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.nocleanup,
"don't clean up the database after indexing (false)", NULL},
{"xbatchsize", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.xbatchsize,
"set transaction batchsize for xapian commits (0)", NULL},
{"max-msg-size", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.max_msg_size,
"set the maximum size for message files", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
og = g_option_group_new("index",
"options for the 'index' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static void
set_group_find_defaults (void)
{
/* note, when no fields are specified, we use
* date-from-subject, and sort descending by date. If fields
* *are* specified, we sort in ascending order. */
if (!MU_CONFIG.fields) {
MU_CONFIG.fields = "d f s";
if (!MU_CONFIG.sortfield)
MU_CONFIG.sortfield = "d";
}
if (!MU_CONFIG.formatstr) /* by default, use plain output */
MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN;
else
MU_CONFIG.format =
get_output_format (MU_CONFIG.formatstr);
if (MU_CONFIG.linksdir) {
gchar *old = MU_CONFIG.linksdir;
MU_CONFIG.linksdir = mu_util_dir_expand(MU_CONFIG.linksdir);
if (!MU_CONFIG.linksdir) /* we'll check the dir later */
MU_CONFIG.linksdir = old;
else
g_free(old);
}
if ((MU_CONFIG.summary && !MU_CONFIG.summary_len)||
(MU_CONFIG.summary_len < 1))
MU_CONFIG.summary_len = DEFAULT_SUMMARY_LEN;
}
static GOptionGroup*
config_options_group_find (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"fields", 'f', 0, G_OPTION_ARG_STRING, &MU_CONFIG.fields,
"fields to display in the output", NULL},
{"sortfield", 's', 0, G_OPTION_ARG_STRING, &MU_CONFIG.sortfield,
"field to sort on", NULL},
{"threads", 't', 0, G_OPTION_ARG_NONE, &MU_CONFIG.threads,
"show message threads", NULL},
{"bookmark", 'b', 0, G_OPTION_ARG_STRING, &MU_CONFIG.bookmark,
"use a bookmarked query", NULL},
{"reverse", 'z', 0, G_OPTION_ARG_NONE, &MU_CONFIG.reverse,
"sort in reverse (descending) order (z -> a)", NULL},
{"summary", 'k', 0, G_OPTION_ARG_NONE, &MU_CONFIG.summary,
"include a short summary of the message (false)", NULL},
{"summary-len", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.summary_len,
"use up to <n> lines for the summary (5)", NULL},
{"linksdir", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.linksdir,
"output as symbolic links to a target maildir", NULL},
{"clearlinks", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.clearlinks,
"clear old links before filling a linksdir (false)", NULL},
{"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr,
"output format ('plain'(*), 'links', 'xml',"
"'sexp', 'xquery')", NULL},
{"exec", 'e', 0, G_OPTION_ARG_STRING, &MU_CONFIG.exec,
"execute command on each match message", NULL},
{"include-unreable", 0, 0, G_OPTION_ARG_NONE,
&MU_CONFIG.include_unreadable,
"don't ignore messages without a disk file (false)", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
og = g_option_group_new("find",
"options for the 'find' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static GOptionGroup *
config_options_group_mkdir (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"mode", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.dirmode,
"set the mode (as in chmod), in octal notation", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
/* set dirmode before, because '0000' is a valid mode */
MU_CONFIG.dirmode = 0755;
og = g_option_group_new("mkdir", "options for the 'mkdir' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static void
set_group_cfind_defaults (void)
{
if (!MU_CONFIG.formatstr) /* by default, use plain output */
MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN;
else
MU_CONFIG.format = get_output_format (MU_CONFIG.formatstr);
}
static GOptionGroup *
config_options_group_cfind (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr,
"output format ('plain'(*), 'mutt', 'wanderlust',"
"'org-contact', 'csv')", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
og = g_option_group_new("cfind", "options for the 'cfind' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static void
set_group_view_defaults (void)
{
if (!MU_CONFIG.formatstr) /* by default, use plain output */
MU_CONFIG.format = MU_CONFIG_FORMAT_PLAIN;
else
MU_CONFIG.format = get_output_format (MU_CONFIG.formatstr);
if ((MU_CONFIG.summary && !MU_CONFIG.summary_len)||
(MU_CONFIG.summary_len < 1))
MU_CONFIG.summary_len = DEFAULT_SUMMARY_LEN;
}
static GOptionGroup *
config_options_group_view (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"summary", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.summary,
"only show a short summary of the message (false)", NULL},
{"summary-len", 0, 0, G_OPTION_ARG_INT, &MU_CONFIG.summary_len,
"use up to <n> lines for the summary (5)", NULL},
{"terminate", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.terminator,
"terminate messages with ascii-0x07 (\\f, form-feed)", NULL},
{"format", 'o', 0, G_OPTION_ARG_STRING, &MU_CONFIG.formatstr,
"output format ('plain'(*), 'sexp')", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
og = g_option_group_new("view", "options for the 'view' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static GOptionGroup*
config_options_group_extract (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"save-attachments", 'a', 0, G_OPTION_ARG_NONE,
&MU_CONFIG.save_attachments,
"save all attachments (false)", NULL},
{"save-all", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.save_all,
"save all parts (incl. non-attachments) (false)", NULL},
{"parts", 0, 0, G_OPTION_ARG_STRING, &MU_CONFIG.parts,
"save specific parts (comma-separated list)", NULL},
{"target-dir", 0, 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.targetdir,
"target directory for saving", NULL},
{"overwrite", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.overwrite,
"overwrite existing files (false)", NULL},
{"play", 0, 0, G_OPTION_ARG_NONE, &MU_CONFIG.play,
"try to 'play' (open) the extracted parts", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
MU_CONFIG.targetdir = g_strdup("."); /* default is the current dir */
og = g_option_group_new("extract",
"options for the 'extract' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static GOptionGroup*
config_options_group_server (void)
{
GOptionGroup *og;
GOptionEntry entries[] = {
{"maildir", 'm', 0, G_OPTION_ARG_FILENAME, &MU_CONFIG.maildir,
"top of the maildir", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}
};
og = g_option_group_new("server",
"options for the 'server' command",
"", NULL, NULL);
g_option_group_add_entries(og, entries);
return og;
}
static gboolean
parse_cmd (int *argcp, char ***argvp)
{
int i;
struct {
const gchar* _name;
MuConfigCmd _cmd;
} cmd_map[] = {
{ "cfind", MU_CONFIG_CMD_CFIND },
{ "extract", MU_CONFIG_CMD_EXTRACT },
{ "find", MU_CONFIG_CMD_FIND },
{ "index", MU_CONFIG_CMD_INDEX },
{ "mkdir", MU_CONFIG_CMD_MKDIR },
{ "view", MU_CONFIG_CMD_VIEW },
{ "add", MU_CONFIG_CMD_ADD },
{ "remove", MU_CONFIG_CMD_REMOVE },
{ "server", MU_CONFIG_CMD_SERVER }
};
MU_CONFIG.cmd = MU_CONFIG_CMD_NONE;
MU_CONFIG.cmdstr = NULL;
if (*argcp < 2) /* no command found at all */
return TRUE;
else if ((**argvp)[1] == '-')
/* if the first param starts with '-', there is no
* command, just some option (like --version, --help
* etc.)*/
return TRUE;
MU_CONFIG.cmd = MU_CONFIG_CMD_UNKNOWN;
MU_CONFIG.cmdstr = (*argvp)[1];
for (i = 0; i != G_N_ELEMENTS(cmd_map); ++i)
if (strcmp (MU_CONFIG.cmdstr, cmd_map[i]._name) == 0)
MU_CONFIG.cmd = cmd_map[i]._cmd;
return TRUE;
}
static void
add_context_group (GOptionContext *context)
{
GOptionGroup *group;
switch (MU_CONFIG.cmd) {
case MU_CONFIG_CMD_INDEX:
group = config_options_group_index();
break;
case MU_CONFIG_CMD_FIND:
group = config_options_group_find();
break;
case MU_CONFIG_CMD_MKDIR:
group = config_options_group_mkdir();
break;
case MU_CONFIG_CMD_EXTRACT:
group = config_options_group_extract();
break;
case MU_CONFIG_CMD_CFIND:
group = config_options_group_cfind();
break;
case MU_CONFIG_CMD_VIEW:
group = config_options_group_view();
break;
case MU_CONFIG_CMD_SERVER:
group = config_options_group_server();
break;
default:
return; /* no group to add */
}
g_option_context_add_group(context, group);
}
static gboolean
parse_params (int *argcp, char ***argvp)
{
GError *err = NULL;
GOptionContext *context;
gboolean rv;
context = g_option_context_new("- mu general option");
g_option_context_set_main_group(context, config_options_group_mu());
add_context_group (context);
rv = g_option_context_parse (context, argcp, argvp, &err);
g_option_context_free (context);
if (!rv) {
g_printerr ("mu: error in options: %s\n", err->message);
g_error_free (err);
return FALSE;
}
return TRUE;
}
MuConfig*
mu_config_init (int *argcp, char ***argvp)
{
g_return_val_if_fail (argcp && argvp, NULL);
memset (&MU_CONFIG, 0, sizeof(MU_CONFIG));
if (!parse_cmd (argcp, argvp) ||
!parse_params(argcp, argvp)) {
mu_config_uninit (&MU_CONFIG);
return NULL;
}
/* fill in the defaults if user did not specify */
set_group_mu_defaults();
set_group_index_defaults();
set_group_find_defaults();
set_group_cfind_defaults();
set_group_view_defaults();
/* set_group_mkdir_defaults (config); */
return &MU_CONFIG;
}
void
mu_config_uninit (MuConfig *opts)
{
if (!opts)
return;
g_free (opts->muhome);
g_free (opts->maildir);
g_free (opts->linksdir);
g_free (opts->targetdir);
g_strfreev (opts->params);
memset (opts, 0, sizeof(MU_CONFIG));
}
size_t
mu_config_param_num (MuConfig *opts)
{
size_t n;
g_return_val_if_fail (opts && opts->params, 0);
for (n = 0; opts->params[n]; ++n);
return n;
}

201
mu/mu-config.h Normal file
View File

@ -0,0 +1,201 @@
/* -*-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, 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_CONFIG_H__
#define __MU_CONFIG_H__
#include <glib.h>
#include <sys/types.h> /* for mode_t */
#include <mu-msg-fields.h>
#include <mu-util.h>
G_BEGIN_DECLS
/* env var; if non-empty, color are disabled */
#define MU_NOCOLOR "MU_NOCOLOR"
enum _MuConfigFormat {
MU_CONFIG_FORMAT_UNKNOWN = 0,
/* for cfind, find, view */
MU_CONFIG_FORMAT_PLAIN, /* plain output */
/* for cfind */
MU_CONFIG_FORMAT_MUTT_ALIAS, /* mutt alias style */
MU_CONFIG_FORMAT_MUTT_AB, /* mutt ext abook */
MU_CONFIG_FORMAT_WL, /* Wanderlust abook */
MU_CONFIG_FORMAT_CSV, /* comma-sep'd values */
MU_CONFIG_FORMAT_ORG_CONTACT, /* org-contact */
MU_CONFIG_FORMAT_BBDB, /* BBDB */
/* for find, view */
MU_CONFIG_FORMAT_SEXP, /* output sexps (emacs) */
/* for find */
MU_CONFIG_FORMAT_LINKS, /* output as symlinks */
MU_CONFIG_FORMAT_XML, /* output xml */
MU_CONFIG_FORMAT_XQUERY /* output the xapian query */
};
typedef enum _MuConfigFormat MuConfigFormat;
enum _MuConfigCmd {
MU_CONFIG_CMD_UNKNOWN = 0,
MU_CONFIG_CMD_INDEX,
MU_CONFIG_CMD_FIND,
MU_CONFIG_CMD_MKDIR,
MU_CONFIG_CMD_VIEW,
MU_CONFIG_CMD_EXTRACT,
MU_CONFIG_CMD_CFIND,
MU_CONFIG_CMD_ADD,
MU_CONFIG_CMD_REMOVE,
MU_CONFIG_CMD_SERVER,
MU_CONFIG_CMD_NONE
};
typedef enum _MuConfigCmd MuConfigCmd;
/* struct with all configuration options for mu; it will be filled
* from the config file, and/or command line arguments */
struct _MuConfig {
MuConfigCmd cmd; /* the command, or
* MU_CONFIG_CMD_NONE */
const char *cmdstr; /* cmd string, for user
* info */
/* general options */
gboolean quiet; /* don't give any output */
gboolean debug; /* spew out debug info */
char *muhome; /* the House of Mu */
gboolean version; /* request mu version */
gboolean log_stderr; /* log to stderr (not logfile) */
gchar** params; /* parameters (for querying) */
gboolean nocolor; /* don't use use ansi-colors
* in some output */
/* options for indexing */
char *maildir; /* where the mails are */
gboolean nocleanup; /* don't cleanup del'd mails from db */
gboolean reindex; /* re-index existing mails */
gboolean rebuild; /* empty the database before indexing */
gboolean autoupgrade; /* automatically upgrade db
* when needed */
int xbatchsize; /* batchsize for xapian
* commits, or 0 for
* default */
int max_msg_size; /* maximum size for message files */
/* options for querying 'find' (and view-> 'summary') */
char *fields; /* fields to show in output */
char *sortfield; /* field to sort by (string) */
gboolean reverse; /* sort in revers order (z->a) */
gboolean threads; /* show message threads */
gboolean summary; /* include a summary? */
int summary_len; /* max # of lines for summary */
char *bookmark; /* use bookmark */
char *formatstr; /* output type for find
* (plain,links,xml,json,sexp)
* and view (plain, sexp) and cfind
*/
MuConfigFormat format; /* the decoded formatstr */
char *exec; /* command to execute on the
* files for the matched
* messages */
gboolean include_unreadable; /* don't ignore messages
* without a disk file */
/* options for view */
gboolean terminator; /* add separator \f between
* multiple messages in mu
* view */
/* output to a maildir with symlinks */
char *linksdir; /* maildir to output symlinks */
gboolean clearlinks; /* clear a linksdir before filling */
mode_t dirmode; /* mode for the created maildir */
/* options for extracting parts */
gboolean *save_all; /* extract all parts */
gboolean *save_attachments; /* extract all attachment parts */
gchar *parts; /* comma-sep'd list of parts
* to save / open */
char *targetdir; /* where to save the attachments */
gboolean overwrite; /* should we overwrite same-named files */
gboolean play; /* after saving, try to 'play'
* (open) the attmnt using xdgopen */
};
typedef struct _MuConfig MuConfig;
/**
* initialize a mu config object
*
* set default values for the configuration options; when you call
* mu_config_init, you should also call mu_config_uninit when the data
* is no longer needed.
*
* Note that is _static_ data, ie., mu_config_init will always return
* the same pointer
*
* @param opts options
*/
MuConfig *mu_config_init (int *argcp, char ***argvp)
G_GNUC_WARN_UNUSED_RESULT;
/**
* free the MuConfig structure
*
* @param opts a MuConfig struct, or NULL
*/
void mu_config_uninit (MuConfig *conf);
/**
* execute the command / options in this config
*
* @param opts a MuConfig struct
*
* @return a value denoting the success/failure of the execution;
* MU_ERROR_NONE (0) for success, non-zero for a failure. This is to used for
* the exit code of the process
*
*/
MuError mu_config_execute (MuConfig *conf);
/**
* count the number of non-option parameters
*
* @param opts a MuConfig struct
*
* @return the number of non-option parameters, or 0 in case of error
*/
size_t mu_config_param_num (MuConfig *conf);
G_END_DECLS
#endif /*__MU_CONFIG_H__*/

98
mu/mu.cc Normal file
View File

@ -0,0 +1,98 @@
/* -*-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.
**
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib.h>
#include <glib-object.h>
#include <locale.h>
#include "mu-config.h"
#include "mu-cmd.h"
#include "mu-runtime.h"
static void
handle_error (GError *err)
{
const char *advise;
if (!err)
return; /* nothing to do */
advise = NULL;
switch (err->code) {
case MU_ERROR_XAPIAN_CANNOT_GET_WRITELOCK:
advise = "maybe mu is already running?";
break;
case MU_ERROR_XAPIAN_CORRUPTION:
case MU_ERROR_XAPIAN_NOT_UP_TO_DATE:
advise = "please try 'mu index --rebuild'";
break;
case MU_ERROR_XAPIAN_IS_EMPTY:
advise = "please try 'mu index'";
break;
default:
break; /* nothing to do */
}
g_warning ("%s", err->message);
if (advise)
g_message ("%s", advise);
}
int
main (int argc, char *argv[])
{
GError *err;
MuError rv;
MuConfig *conf;
setlocale (LC_ALL, "");
g_type_init ();
conf = mu_config_init (&argc, &argv);
if (!conf)
return 1;
if (!mu_runtime_init (conf->muhome, PACKAGE_NAME)) {
mu_config_uninit (conf);
return 1;
}
err = NULL;
rv = mu_cmd_execute (conf, &err);
handle_error (err);
g_clear_error (&err);
mu_config_uninit (conf);
mu_runtime_uninit ();
return rv;
}

View File

@ -0,0 +1,387 @@
/* -*- 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, 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 "../mu-query.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "test-mu-common.h"
#include "mu-store.h"
static gchar*
fill_contacts_cache (void)
{
gchar *cmdline, *tmpdir;
tmpdir = test_mu_common_get_random_tmpdir();
cmdline = g_strdup_printf ("%s index --muhome=%s --maildir=%s"
" --quiet",
MU_PROGRAM, tmpdir, MU_TESTMAILDIR);
if (g_test_verbose())
g_print ("%s\n", cmdline);
g_assert (g_spawn_command_line_sync (cmdline, NULL, NULL,
NULL, NULL));
g_free (cmdline);
return tmpdir;
}
static void
test_mu_cfind_plain (void)
{
gchar *muhome, *cmdline, *output, *erroutput;
muhome = fill_contacts_cache ();
g_assert (muhome != NULL);
cmdline = g_strdup_printf ("%s cfind --muhome=%s --format=plain "
"'testmu\\.xxx?'",
MU_PROGRAM, muhome);
if (g_test_verbose())
g_print ("%s\n", cmdline);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
/* note, output order is unspecified */
g_assert (output);
if (output[0] == 'H')
g_assert_cmpstr (output,
==,
"Helmut Kröger hk@testmu.xxx\n"
"Mü testmu@testmu.xx\n");
else
g_assert_cmpstr (output,
==,
"Mü testmu@testmu.xx\n"
"Helmut Kröger hk@testmu.xxx\n");
g_free (cmdline);
g_free (muhome);
g_free (output);
g_free (erroutput);
}
static void
test_mu_cfind_bbdb (void)
{
gchar *muhome, *cmdline, *output, *erroutput, *expected;
gchar today[12];
const char* frm1;
const char *frm2;
struct tm *tmtoday;
time_t now;
const char *old_tz;
muhome = fill_contacts_cache ();
g_assert (muhome != NULL);
old_tz = set_tz ("Europe/Helsinki");
cmdline = g_strdup_printf ("%s cfind --muhome=%s --format=bbdb "
"'testmu\\.xxx?'",
MU_PROGRAM, muhome);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
frm1 = ";; -*-coding: utf-8-emacs;-*-\n"
";;; file-version: 6\n"
"[\"Helmut\" \"Kröger\" nil nil nil nil (\"hk@testmu.xxx\") "
"((creation-date . \"%s\") "
"(time-stamp . \"1970-01-01\")) nil]\n"
"[\"\" \"\" nil nil nil nil (\"testmu@testmu.xx\") "
"((creation-date . \"%s\") "
"(time-stamp . \"1970-01-01\")) nil]\n";
frm2 = ";; -*-coding: utf-8-emacs;-*-\n"
";;; file-version: 6\n"
"[\"\" \"\" nil nil nil nil (\"testmu@testmu.xx\") "
"((creation-date . \"%s\") "
"(time-stamp . \"1970-01-01\")) nil]\n"
"[\"Helmut\" \"Kröger\" nil nil nil nil (\"hk@testmu.xxx\") "
"((creation-date . \"%s\") "
"(time-stamp . \"1970-01-01\")) nil]\n";
g_assert (output);
now = time(NULL);
tmtoday = localtime(&now);
strftime(today,sizeof(today),"%Y-%m-%d", tmtoday);
expected = g_strdup_printf (output[52] == 'H' ? frm1 : frm2,
today, today);
/* g_print ("\n%s\n", output); */
g_assert_cmpstr (output, ==, expected);
g_free (cmdline);
g_free (muhome);
g_free (output);
g_free (erroutput);
g_free (expected);
set_tz (old_tz);
}
static void
test_mu_cfind_wl (void)
{
gchar *muhome, *cmdline, *output, *erroutput;
muhome = fill_contacts_cache ();
g_assert (muhome != NULL);
cmdline = g_strdup_printf ("%s cfind --muhome=%s --format=wl "
"'testmu\\.xxx?'",
MU_PROGRAM, muhome);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert (output);
if (output[0] == 'h')
g_assert_cmpstr (output,
==,
"hk@testmu.xxx \"HelmutK\" \"Helmut Kröger\"\n"
"testmu@testmu.xx \"\" \"\"\n");
else
g_assert_cmpstr (output,
==,
"testmu@testmu.xx \"\" \"\"\n"
"hk@testmu.xxx \"HelmutK\" \"Helmut Kröger\"\n");
g_free (cmdline);
g_free (muhome);
g_free (output);
g_free (erroutput);
}
static void
test_mu_cfind_mutt_alias (void)
{
gchar *muhome, *cmdline, *output, *erroutput;
muhome = fill_contacts_cache ();
g_assert (muhome != NULL);
cmdline = g_strdup_printf ("%s cfind --muhome=%s --format=mutt-alias "
"'testmu\\.xxx?'",
MU_PROGRAM, muhome);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
/* both orders are possible... */
g_assert (output);
if (output[6] == 'H')
g_assert_cmpstr (output,
==,
"alias HelmutK Helmut Kröger <hk@testmu.xxx>\n"
"alias Mü Mü <testmu@testmu.xx>\n");
else
g_assert_cmpstr (output,
==,
"alias Mü Mü <testmu@testmu.xx>\n"
"alias HelmutK Helmut Kröger <hk@testmu.xxx>\n");
g_free (cmdline);
g_free (muhome);
g_free (output);
g_free (erroutput);
}
static void
test_mu_cfind_mutt_ab (void)
{
gchar *muhome, *cmdline, *output, *erroutput;
muhome = fill_contacts_cache ();
g_assert (muhome != NULL);
cmdline = g_strdup_printf ("%s cfind --muhome=%s --format=mutt-ab "
"'testmu\\.xxx?'",
MU_PROGRAM, muhome);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert (output);
if (output[39] == 'h')
g_assert_cmpstr (output,
==,
"Matching addresses in the mu database:\n"
"hk@testmu.xxx\tHelmut Kröger\t\n"
"testmu@testmu.xx\t\t\n");
else
g_assert_cmpstr (output,
==,
"Matching addresses in the mu database:\n"
"testmu@testmu.xx\t\t\n"
"hk@testmu.xxx\tHelmut Kröger\t\n");
g_free (cmdline);
g_free (muhome);
g_free (output);
g_free (erroutput);
}
static void
test_mu_cfind_org_contact (void)
{
gchar *muhome, *cmdline, *output, *erroutput;
muhome = fill_contacts_cache ();
g_assert (muhome != NULL);
cmdline = g_strdup_printf ("%s cfind --muhome=%s --format=org-contact "
"'testmu\\.xxx?'",
MU_PROGRAM, muhome);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert (output);
if (output[2] == 'H')
g_assert_cmpstr (output,
==,
"* Helmut Kröger\n"
":PROPERTIES:\n"
":EMAIL: hk@testmu.xxx\n"
":END:\n\n"
"* Mü\n"
":PROPERTIES:\n"
":EMAIL: testmu@testmu.xx\n"
":END:\n\n");
else
g_assert_cmpstr (output,
==,
"* Mü\n"
":PROPERTIES:\n"
":EMAIL: testmu@testmu.xx\n"
":END:\n\n"
"* Helmut Kröger\n"
":PROPERTIES:\n"
":EMAIL: hk@testmu.xxx\n"
":END:\n\n");
g_free (cmdline);
g_free (muhome);
g_free (output);
g_free (erroutput);
}
static void
test_mu_cfind_csv (void)
{
gchar *muhome, *cmdline, *output, *erroutput;
muhome = fill_contacts_cache ();
g_assert (muhome != NULL);
cmdline = g_strdup_printf ("%s cfind --muhome=%s --format=csv "
"'testmu\\.xxx?'",
MU_PROGRAM, muhome);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert (output);
if (output[0] == 'H')
g_assert_cmpstr (output,
==,
"Helmut Kröger,hk@testmu.xxx\n"
"Mü,testmu@testmu.xx\n");
else
g_assert_cmpstr (output,
==,
"Mü,testmu@testmu.xx\n"
"Helmut Kröger,hk@testmu.xxx\n");
g_free (cmdline);
g_free (muhome);
g_free (output);
g_free (erroutput);
}
int
main (int argc, char *argv[])
{
int rv;
g_test_init (&argc, &argv, NULL);
if (!set_en_us_utf8_locale())
return 0; /* don't error out... */
g_test_add_func ("/mu-cmd-cfind/test-mu-cfind-plain", test_mu_cfind_plain);
g_test_add_func ("/mu-cmd-cfind/test-mu-cfind-bbdb", test_mu_cfind_bbdb);
g_test_add_func ("/mu-cmd-cfind/test-mu-cfind-wl", test_mu_cfind_wl);
g_test_add_func ("/mu-cmd-cfind/test-mu-cfind-mutt-alias",
test_mu_cfind_mutt_alias);
g_test_add_func ("/mu-cmd-cfind/test-mu-cfind-mutt-ab",
test_mu_cfind_mutt_ab);
g_test_add_func ("/mu-cmd-cfind/test-mu-cfind-org-contact",
test_mu_cfind_org_contact);
g_test_add_func ("/mu-cmd-cfind/test-mu-cfind-csv",
test_mu_cfind_csv);
g_log_set_handler (NULL,
G_LOG_LEVEL_MASK | G_LOG_LEVEL_WARNING|
G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,
(GLogFunc)black_hole, NULL);
rv = g_test_run ();
return rv;
}

803
mu/tests/test-mu-cmd.c Normal file
View File

@ -0,0 +1,803 @@
/* -*- 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, 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 "../mu-query.h"
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "test-mu-common.h"
#include "mu-store.h"
/* tests for the command line interface, uses testdir2 */
static gchar*
fill_database (void)
{
gchar *cmdline, *tmpdir;
tmpdir = test_mu_common_get_random_tmpdir();
cmdline = g_strdup_printf ("%s index --muhome=%s --maildir=%s"
" --quiet",
MU_PROGRAM,
tmpdir, MU_TESTMAILDIR2);
if (g_test_verbose())
g_print ("%s\n", cmdline);
g_assert (g_spawn_command_line_sync (cmdline, NULL, NULL,
NULL, NULL));
g_free (cmdline);
return tmpdir;
}
static unsigned
newlines_in_output (const char* str)
{
int count;
count = 0;
while (str && *str) {
if (*str == '\n')
++count;
++str;
}
return count;
}
static void
search (const char* query, unsigned expected)
{
gchar *muhome, *cmdline, *output, *erroutput;
muhome = fill_database ();
g_assert (muhome);
cmdline = g_strdup_printf ("%s find --muhome=%s %s",
MU_PROGRAM, muhome, query);
if (g_test_verbose())
g_printerr ("%s\n", cmdline);
g_assert (g_spawn_command_line_sync (cmdline,
&output, &erroutput,
NULL, NULL));
if (g_test_verbose())
g_print ("\nOutput:\n%s", output);
g_assert_cmpuint (newlines_in_output(output),==,expected);
/* we expect zero lines of error output if there is a match;
* otherwise there should be one line 'No matches found' */
/* g_assert_cmpuint (newlines_in_output(erroutput),==, */
/* expected == 0 ? 1 : 0); */
g_free (output);
g_free (erroutput);
g_free (cmdline);
g_free (muhome);
}
/* index testdir2, and make sure it adds two documents */
static void
test_mu_index (void)
{
MuStore *store;
gchar *muhome, *xpath;
muhome = fill_database ();
g_assert (muhome != NULL);
xpath = g_strdup_printf ("%s%c%s", muhome, G_DIR_SEPARATOR, "xapian");
store = mu_store_new_read_only (xpath, NULL);
g_assert (store);
g_assert_cmpuint (mu_store_count (store, NULL), ==, 12);
mu_store_unref (store);
g_free (muhome);
g_free (xpath);
}
static void
test_mu_find_empty_query (void)
{
search ("\"\"", 12);
}
static void
test_mu_find_01 (void)
{
search ("f:john fruit", 1);
search ("f:soc@example.com", 1);
search ("t:alki@example.com", 1);
search ("t:alcibiades", 1);
search ("f:soc@example.com OR f:john", 2);
search ("f:soc@example.com OR f:john OR t:edmond", 3);
search ("t:julius", 1);
search ("s:dude", 1);
search ("t:dantès", 1);
}
/* index testdir2, and make sure it adds two documents */
static void
test_mu_find_02 (void)
{
search ("bull", 1);
search ("bull m:foo", 0);
search ("bull m:/foo", 1);
search ("bull m:/Foo", 1);
search ("bull flag:a", 1);
search ("g:x", 0);
search ("flag:encrypted", 0);
search ("flag:attach", 1);
}
static void
test_mu_find_file (void)
{
search ("file:sittingbull.jpg", 1);
search ("file:custer.jpg", 1);
search ("file:custer.*", 1);
search ("j:sit*", 1);
}
static void
test_mu_find_mime (void)
{
search ("mime:image/jpeg", 1);
search ("mime:text/plain", 12);
search ("y:text*", 12);
search ("y:image*", 1);
search ("mime:message/rfc822", 2);
}
static void
test_mu_find_text_in_rfc822 (void)
{
search ("embed:dancing", 1);
search ("e:curious", 1);
search ("embed:with", 2);
search ("e:karjala", 0);
search ("embed:navigation", 1);
}
/* some more tests */
static void
test_mu_find_03 (void)
{
search ("bull", 1);
search ("bull m:foo", 0);
search ("bull m:/foo", 1);
search ("i:3BE9E6535E0D852173@emss35m06.us.lmco.com", 1);
}
static void /* error cases */
test_mu_find_04 (void)
{
gchar *muhome, *cmdline, *erroutput;
muhome = fill_database ();
g_assert (muhome);
cmdline = g_strdup_printf ("%s --muhome=%cfoo%cbar%cnonexistent "
"find f:socrates",
MU_PROGRAM,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
g_assert (g_spawn_command_line_sync (cmdline, NULL, &erroutput,
NULL, NULL));
/* we expect multiple lines of error output */
g_assert_cmpuint (newlines_in_output(erroutput),>=,1);
g_free (erroutput);
g_free (cmdline);
g_free (muhome);
}
static void
test_mu_find_links (void)
{
gchar *muhome, *cmdline, *output, *erroutput, *tmpdir;
muhome = fill_database ();
g_assert (muhome);
tmpdir = test_mu_common_get_random_tmpdir();
cmdline = g_strdup_printf (
"%s find --muhome=%s --format=links --linksdir=%s "
"mime:message/rfc822", MU_PROGRAM, muhome, tmpdir);
if (g_test_verbose())
g_printerr ("%s\n", cmdline);
g_assert (g_spawn_command_line_sync (cmdline,
&output, &erroutput,
NULL, NULL));
if (g_test_verbose())
g_print ("\nOutput:\n%s", output);
/* there should be no errors */
g_assert_cmpuint (newlines_in_output(output),==,0);
g_assert_cmpuint (newlines_in_output(erroutput),==,0);
g_free (output);
g_free (erroutput);
/* now we try again, we should get 2 + 1 lines of error output,
* because the target files already exist */
g_assert (g_spawn_command_line_sync (cmdline,
&output, &erroutput,
NULL, NULL));
if (g_test_verbose())
g_print ("\nOutput:\n%s", output);
g_assert_cmpuint (newlines_in_output(output),==,0);
g_assert_cmpuint (newlines_in_output(erroutput),==,3);
g_free (output);
g_free (erroutput);
/* now we try again with --clearlinks, and the we should be
* back to 0 errors */
g_free (cmdline);
cmdline = g_strdup_printf (
"%s find --muhome=%s --format=links --linksdir=%s --clearlinks "
"mime:message/rfc822", MU_PROGRAM, muhome, tmpdir);
g_assert (g_spawn_command_line_sync (cmdline,
&output, &erroutput,
NULL, NULL));
if (g_test_verbose())
g_print ("\nOutput:\n%s", output);
g_assert_cmpuint (newlines_in_output(output),==,0);
g_assert_cmpuint (newlines_in_output(erroutput),==,0);
g_free (output);
g_free (erroutput);
g_free (cmdline);
g_free (muhome);
g_free (tmpdir);
}
/* some more tests */
static void
test_mu_find_maildir_special (void)
{
/* ensure that maldirs with spaces in their names work... */
search ("\"maildir:/wom bat\" subject:atoms", 1);
search ("\"maildir:/wOm_bàT\"", 3);
search ("\"maildir:/wOm*\"", 3);
search ("\"maildir:/wOm *\"", 3);
search ("\"maildir:wom bat\"", 0);
search ("\"maildir:/wombat\"", 0);
search ("subject:atoms", 1);
}
/* static void */
/* test_mu_find_mime_types (void) */
/* { */
/* /\* ensure that maldirs with spaces in their names work... *\/ */
/* search ("\"maildir:/wom bat\" subject:atoms", 1); */
/* search ("\"maildir:/wOm_bàT\"", 3); */
/* search ("subject:atoms", 1); */
/* } */
static void
test_mu_extract_01 (void)
{
gchar *cmdline, *output, *erroutput, *tmpdir;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s extract --muhome=%s %s%cFoo%ccur%cmail5",
MU_PROGRAM,
tmpdir,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
if (g_test_verbose())
g_print ("cmd: %s\n", cmdline);
output = erroutput = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert_cmpstr (output,
==,
"MIME-parts in this message:\n"
" 0 <none> text/plain [<none>] (0.0 kB)\n"
" 1 sittingbull.jpg image/jpeg [inline] (23.9 kB)\n"
" 2 custer.jpg image/jpeg [inline] (21.6 kB)\n");
/* we expect zero lines of error output */
g_assert_cmpuint (newlines_in_output(erroutput),==,0);
g_free (output);
g_free (erroutput);
g_free (cmdline);
g_free (tmpdir);
}
static gint64
get_file_size (const char* path)
{
int rv;
struct stat statbuf;
rv = stat (path, &statbuf);
if (rv != 0)
return -1;
return (gint64)statbuf.st_size;
}
static void
test_mu_extract_02 (void)
{
gchar *cmdline, *output, *tmpdir;
gchar *att1, *att2;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s extract --muhome=%s -a "
"--target-dir=%s %s%cFoo%ccur%cmail5",
MU_PROGRAM,
tmpdir,
tmpdir,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
output = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, NULL,
NULL, NULL));
g_assert_cmpstr (output, ==, "");
att1 = g_strdup_printf ("%s%ccuster.jpg", tmpdir, G_DIR_SEPARATOR);
att2 = g_strdup_printf ("%s%csittingbull.jpg", tmpdir, G_DIR_SEPARATOR);
g_assert_cmpint (get_file_size(att1),==,15960);
g_assert_cmpint (get_file_size(att2),==,17674);
g_free (output);
g_free (tmpdir);
g_free (cmdline);
g_free (att1);
g_free (att2);
}
static void
test_mu_extract_03 (void)
{
gchar *cmdline, *output, *tmpdir;
gchar *att1, *att2;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s extract --muhome=%s --parts 2 "
"--target-dir=%s %s%cFoo%ccur%cmail5",
MU_PROGRAM,
tmpdir,
tmpdir,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
output = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, NULL,
NULL, NULL));
g_assert_cmpstr (output, ==, "");
att1 = g_strdup_printf ("%s%ccuster.jpg", tmpdir, G_DIR_SEPARATOR);
att2 = g_strdup_printf ("%s%csittingbull.jpg", tmpdir, G_DIR_SEPARATOR);
g_assert_cmpint (get_file_size(att1),==,15960); /* should not exist */
g_assert_cmpint (get_file_size(att2),==,-1);
g_free (output);
g_free (tmpdir);
g_free (cmdline);
g_free (att1);
g_free (att2);
}
static void
test_mu_extract_overwrite (void)
{
gchar *cmdline, *output, *erroutput, *tmpdir;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s extract --muhome=%s -a "
"--target-dir=%s %s%cFoo%ccur%cmail5",
MU_PROGRAM, tmpdir, tmpdir,
MU_TESTMAILDIR2, G_DIR_SEPARATOR,
G_DIR_SEPARATOR, G_DIR_SEPARATOR);
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert_cmpstr (output, ==, "");
g_assert_cmpstr (erroutput, ==, "");
g_free (erroutput);
g_free (output);
/* now, it should fail, because we don't allow overwrites
* without --overwrite */
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert_cmpstr (output, ==, "");
g_assert_cmpstr (erroutput, !=, "");
g_free (erroutput);
g_free (output);
g_free (cmdline);
/* this should work now, because we have specified --overwrite */
cmdline = g_strdup_printf ("%s extract --muhome=%s -a --overwrite "
"--target-dir=%s %s%cFoo%ccur%cmail5",
MU_PROGRAM, tmpdir, tmpdir,
MU_TESTMAILDIR2, G_DIR_SEPARATOR,
G_DIR_SEPARATOR, G_DIR_SEPARATOR);
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert_cmpstr (output, ==, "");
g_assert_cmpstr (erroutput, ==, "");
g_free (erroutput);
g_free (output);
g_free (tmpdir);
g_free (cmdline);
}
static void
test_mu_extract_by_name (void)
{
gchar *cmdline, *output, *erroutput, *tmpdir, *path;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s extract --muhome=%s "
"--target-dir=%s %s%cFoo%ccur%cmail5 "
"sittingbull.jpg",
MU_PROGRAM, tmpdir, tmpdir,
MU_TESTMAILDIR2, G_DIR_SEPARATOR,
G_DIR_SEPARATOR, G_DIR_SEPARATOR);
g_assert (g_spawn_command_line_sync (cmdline, &output, &erroutput,
NULL, NULL));
g_assert_cmpstr (output, ==, "");
g_assert_cmpstr (erroutput, ==, "");
path = g_strdup_printf ("%s%c%s", tmpdir, G_DIR_SEPARATOR,
"sittingbull.jpg");
g_assert (access (path, F_OK) == 0);
g_free (path);
g_free (erroutput);
g_free (output);
g_free (tmpdir);
g_free (cmdline);
}
static void
test_mu_view_01 (void)
{
gchar *cmdline, *output, *tmpdir;
int len;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s view --muhome=%s %s%cbar%ccur%cmail4",
MU_PROGRAM,
tmpdir,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
output = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, NULL,
NULL, NULL));
g_assert_cmpstr (output, !=, NULL);
/*
* note: there are two possibilities here; older versions of
* GMime will produce:
*
* From: "=?iso-8859-1?Q? =F6tzi ?=" <oetzi@web.de>
*
* while newer ones return something like:
*
* From: ?tzi <oetzi@web.de>
*
* or even
*
* From: \xc3\xb6tzi <oetzi@web.de>
*
* both are 'okay' from mu's perspective; it'd be even better
* to have some #ifdefs for the GMime versions, but this
* should work for now
*
* Added 350 as 'okay', which comes with gmime 2.4.24 (ubuntu 10.04)
*/
len = strlen(output);
/* g_print ("\n[%s] (%d)\n", output, len); */
g_assert (len > 349);
g_free (output);
g_free (cmdline);
g_free (tmpdir);
}
static void
test_mu_view_multi (void)
{
gchar *cmdline, *output, *tmpdir;
int len;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s view --muhome=%s "
"%s%cbar%ccur%cmail5 "
"%s%cbar%ccur%cmail5",
MU_PROGRAM,
tmpdir,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
output = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, NULL,
NULL, NULL));
g_assert_cmpstr (output, !=, NULL);
len = strlen(output);
/* g_print ("\n[%s](%u)\n", output, len); */
g_assert_cmpuint (len,>,150);
g_free (output);
g_free (cmdline);
g_free (tmpdir);
}
static void
test_mu_view_multi_separate (void)
{
gchar *cmdline, *output, *tmpdir;
int len;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s view --terminate --muhome=%s "
"%s%cbar%ccur%cmail5 "
"%s%cbar%ccur%cmail5",
MU_PROGRAM,
tmpdir,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
output = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, NULL,
NULL, NULL));
g_assert_cmpstr (output, !=, NULL);
len = strlen(output);
/* g_print ("\n[%s](%u)\n", output, len); */
g_assert_cmpuint (len,>,150);
g_free (output);
g_free (cmdline);
g_free (tmpdir);
}
static void
test_mu_view_attach (void)
{
gchar *cmdline, *output, *tmpdir;
int len;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s view --muhome=%s %s%cFoo%ccur%cmail5",
MU_PROGRAM,
tmpdir,
MU_TESTMAILDIR2,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
output = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, NULL,
NULL, NULL));
g_assert_cmpstr (output, !=, NULL);
len = strlen(output);
/* g_print ("\n[%s] (%d)\n", output, len);*/
g_assert (len == 170 || len == 168);
g_free (output);
g_free (cmdline);
g_free (tmpdir);
}
static void
test_mu_mkdir_01 (void)
{
gchar *cmdline, *output, *tmpdir;
gchar *dir;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (g_mkdir_with_parents (tmpdir, 0700) == 0);
cmdline = g_strdup_printf ("%s mkdir --muhome=%s %s%ctest1 %s%ctest2",
MU_PROGRAM,tmpdir,
tmpdir, G_DIR_SEPARATOR,
tmpdir, G_DIR_SEPARATOR);
output = NULL;
g_assert (g_spawn_command_line_sync (cmdline, &output, NULL,
NULL, NULL));
g_assert_cmpstr (output, ==, "");
dir = g_strdup_printf ("%s%ctest1%ccur", tmpdir, G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
g_assert (access (dir, F_OK) == 0);
g_free (dir);
dir = g_strdup_printf ("%s%ctest2%ctmp", tmpdir, G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
g_assert (access (dir, F_OK) == 0);
g_free (dir);
dir = g_strdup_printf ("%s%ctest1%cnew", tmpdir, G_DIR_SEPARATOR,
G_DIR_SEPARATOR);
g_assert (access (dir, F_OK) == 0);
g_free (dir);
g_free (output);
g_free (tmpdir);
g_free (cmdline);
}
int
main (int argc, char *argv[])
{
int rv;
g_test_init (&argc, &argv, NULL);
if (!set_en_us_utf8_locale())
return 0; /* don't error out... */
g_test_add_func ("/mu-cmd/test-mu-index", test_mu_index);
g_test_add_func ("/mu-cmd/test-mu-find-empty-query",
test_mu_find_empty_query);
g_test_add_func ("/mu-cmd/test-mu-find-01", test_mu_find_01);
g_test_add_func ("/mu-cmd/test-mu-find-02", test_mu_find_02);
g_test_add_func ("/mu-cmd/test-mu-find-file", test_mu_find_file);
g_test_add_func ("/mu-cmd/test-mu-find-mime", test_mu_find_mime);
g_test_add_func ("/mu-cmd/test-mu-find-links", test_mu_find_links);
g_test_add_func ("/mu-cmd/test-mu-find-text-in-rfc822",
test_mu_find_text_in_rfc822);
g_test_add_func ("/mu-cmd/test-mu-find-03", test_mu_find_03);
g_test_add_func ("/mu-cmd/test-mu-find-04", test_mu_find_04);
g_test_add_func ("/mu-cmd/test-mu-find-maildir-special",
test_mu_find_maildir_special);
g_test_add_func ("/mu-cmd/test-mu-extract-01", test_mu_extract_01);
g_test_add_func ("/mu-cmd/test-mu-extract-02", test_mu_extract_02);
g_test_add_func ("/mu-cmd/test-mu-extract-03", test_mu_extract_03);
g_test_add_func ("/mu-cmd/test-mu-extract-overwrite",
test_mu_extract_overwrite);
g_test_add_func ("/mu-cmd/test-mu-extract-by-name",
test_mu_extract_by_name);
g_test_add_func ("/mu-cmd/test-mu-view-01", test_mu_view_01);
g_test_add_func ("/mu-cmd/test-mu-view-multi",
test_mu_view_multi);
g_test_add_func ("/mu-cmd/test-mu-view-multi-separate",
test_mu_view_multi_separate);
g_test_add_func ("/mu-cmd/test-mu-view-attach", test_mu_view_attach);
g_test_add_func ("/mu-cmd/test-mu-mkdir-01", test_mu_mkdir_01);
g_log_set_handler (NULL,
G_LOG_LEVEL_MASK | G_LOG_LEVEL_WARNING|
G_LOG_FLAG_FATAL| G_LOG_FLAG_RECURSION,
(GLogFunc)black_hole, NULL);
rv = g_test_run ();
return rv;
}

184
mu/tests/test-mu-contacts.c Normal file
View File

@ -0,0 +1,184 @@
/*
** 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.
**
*/
#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-contacts.h"
static gchar*
fill_database (void)
{
gchar *cmdline, *tmpdir;
tmpdir = test_mu_common_get_random_tmpdir();
cmdline = g_strdup_printf ("%s index --muhome=%s --maildir=%s"
" --quiet",
MU_PROGRAM, tmpdir, MU_TESTMAILDIR);
g_assert (g_spawn_command_line_sync (cmdline, NULL, NULL,
NULL, NULL));
g_free (cmdline);
return tmpdir;
}
struct _Contact {
char *name, *email;
time_t tstamp;
};
typedef struct _Contact Contact;
static Contact*
contact_new (const char *email, const char *name, size_t tstamp)
{
Contact *contact;
contact = g_slice_new (Contact);
contact->name = name ? g_strdup (name) :NULL;
contact->email = email ? g_strdup (email) : NULL;
contact->tstamp = tstamp;
return contact;
}
static void
contact_destroy (Contact *contact)
{
if (contact) {
g_free (contact->name);
g_free (contact->email);
g_slice_free (Contact, contact);
}
}
static void
each_contact (const char *email, const char *name, time_t tstamp,
GSList **lst)
{
Contact *contact;
/* g_print ("[n:%s, e:%s]\n", name, email); */
contact = contact_new (email, name, tstamp);
*lst = g_slist_prepend (*lst, contact);
}
static gboolean
has_contact (GSList *lst, const char* name_or_email, gboolean use_name)
{
while (lst) {
Contact *c;
c = (Contact*)lst->data;
/* g_print ("{n:%s,e:%s}\n", c->name, c->email); */
if (use_name && g_strcmp0(name_or_email, c->name) == 0)
return TRUE;
if (g_strcmp0 (name_or_email, c->email) == 0)
return TRUE;
lst = g_slist_next (lst);
}
return FALSE;
}
static GSList*
accumulate_contacts (MuContacts *contacts, const gchar *pattern)
{
GSList *lst;
lst = NULL;
g_assert (mu_contacts_foreach (contacts,
(MuContactsForeachFunc)each_contact,
&lst, pattern, NULL));
return lst;
}
static void
test_mu_contacts_01 (void)
{
MuContacts *contacts;
gchar *muhome, *contactsfile;
GSList *clist;
muhome = fill_database ();
g_assert (muhome != NULL);
contactsfile = g_strdup_printf ("%s%ccache%ccontacts",
muhome, G_DIR_SEPARATOR, G_DIR_SEPARATOR);
/* g_print ("[%s]\n", contactsfile); */
contacts = mu_contacts_new (contactsfile);
g_assert (contacts);
clist = accumulate_contacts (contacts, "");
g_assert_cmpint (g_slist_length (clist), ==, 1);
g_assert (has_contact (clist, "", TRUE));
g_assert (has_contact (clist, "testmu@testmu.xx", FALSE));
g_slist_foreach (clist, (GFunc)contact_destroy, NULL);
g_slist_free (clist);
clist = accumulate_contacts (contacts, "testmu\\.xxx?");
g_assert_cmpint (g_slist_length (clist), ==, 2);
g_assert (has_contact (clist, "", TRUE));
g_assert (has_contact (clist, "testmu@testmu.xx", FALSE));
g_assert (has_contact (clist, "Helmut Kröger", TRUE));
g_assert (has_contact (clist, "hk@testmu.xxx", FALSE));
g_slist_foreach (clist, (GFunc)contact_destroy, NULL);
g_slist_free (clist);
mu_contacts_destroy (contacts);
g_free (contactsfile);
g_free (muhome);
}
int
main (int argc, char *argv[])
{
int rv;
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/mu-contacts/test-mu-contacts-01", test_mu_contacts_01);
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;
}

690
mu/tests/test-mu-query.c Normal file
View File

@ -0,0 +1,690 @@
/* -*-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.
**
*/
#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 <locale.h>
#include "test-mu-common.h"
#include "mu-query.h"
#include "mu-str.h"
#include "mu-store.h"
static gchar*
fill_database (const char *testdir)
{
gchar *cmdline, *tmpdir, *xpath;
tmpdir = test_mu_common_get_random_tmpdir();
cmdline = g_strdup_printf ("%s index --muhome=%s --maildir=%s"
" --quiet",
MU_PROGRAM, tmpdir, testdir);
if (g_test_verbose())
g_printerr ("\n%s\n", cmdline);
g_assert (g_spawn_command_line_sync (cmdline, NULL, NULL,
NULL, NULL));
g_free (cmdline);
xpath= g_strdup_printf ("%s%c%s", tmpdir,
G_DIR_SEPARATOR, "xapian");
g_free (tmpdir);
return xpath;
}
static void
assert_no_dups (MuMsgIter *iter)
{
GHashTable *hash;
hash = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify)g_free, NULL);
mu_msg_iter_reset (iter);
while (!mu_msg_iter_is_done(iter)) {
MuMsg *msg;
msg = mu_msg_iter_get_msg_floating (iter);
/* make sure there are no duplicates */
g_assert (!g_hash_table_lookup (hash, mu_msg_get_path (msg)));
g_hash_table_insert (hash, g_strdup (mu_msg_get_path(msg)),
GUINT_TO_POINTER(TRUE));
mu_msg_iter_next (iter);
}
mu_msg_iter_reset (iter);
g_hash_table_destroy (hash);
}
/* note: this also *moves the iter* */
static guint
run_and_count_matches (const char *xpath, const char *query)
{
MuQuery *mquery;
MuMsgIter *iter;
MuStore *store;
guint count1, count2;
store = mu_store_new_read_only (xpath, NULL);
g_assert (store);
mquery = mu_query_new (store, NULL);
g_assert (query);
mu_store_unref (store);
if (g_test_verbose()) {
char *xs;
g_print ("\n==> query: %s\n", query);
xs = mu_query_preprocess (query, NULL);
g_print ("==> preproc: '%s'\n", xs);
g_free (xs);
xs = mu_query_as_string (mquery, query, NULL);
g_print ("==> xquery: '%s'\n", xs);
g_free (xs);
}
iter = mu_query_run (mquery, query, FALSE, MU_MSG_FIELD_ID_NONE,
FALSE, -1, NULL);
mu_query_destroy (mquery);
g_assert (iter);
assert_no_dups (iter);
/* run query twice, to test mu_msg_iter_reset */
for (count1 = 0; !mu_msg_iter_is_done(iter);
mu_msg_iter_next(iter), ++count1);
mu_msg_iter_reset (iter);
assert_no_dups (iter);
for (count2 = 0; !mu_msg_iter_is_done(iter);
mu_msg_iter_next(iter), ++count2);
mu_msg_iter_destroy (iter);
g_assert_cmpuint (count1, ==, count2);
return count1;
}
typedef struct {
const char *query;
size_t count; /* expected number of matches */
} QResults;
static void
test_mu_query_01 (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "basic", 3 },
{ "question", 5 },
{ "thanks", 2 },
{ "html", 4 },
{ "subject:elisp", 1 },
{ "html AND contains", 1 },
{ "html and contains", 1 },
{ "from:pepernoot", 0 },
{ "foo:pepernoot", 0 },
{ "funky", 1 },
{ "fünkÿ", 1 },
{ "", 17 }
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_02 (void)
{
const char* q;
gchar *xpath;
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath);
q = "i:f7ccd24b0808061357t453f5962w8b61f9a453b684d0@mail.gmail.com";
g_assert_cmpuint (run_and_count_matches(xpath, q), ==, 1);
g_free (xpath);
}
static void
test_mu_query_03 (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "ploughed", 1},
{ "i:3BE9E6535E3029448670913581E7A1A20D852173@"
"emss35m06.us.lmco.com", 1},
/* subsets of the words in the subject should match */
{ "s:gcc include search order" , 1},
{ "s:gcc include search" , 1},
{ "s:search order" , 1},
{ "s:include" , 1},
{ "s:lisp", 1},
{ "s:LISP", 1},
{ "s:\"Re: Learning LISP; Scheme vs elisp.\"", 1},
{ "subject:Re: Learning LISP; Scheme vs elisp.", 0},
{ "subject:\"Re: Learning LISP; Scheme vs elisp.\"", 1},
{ "to:help-gnu-emacs@gnu.org", 4},
{ "t:help-gnu-emacs", 0},
{ "flag:flagged", 1}
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_04 (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "frodo@example.com", 1},
{ "f:frodo@example.com", 1},
{ "f:Frodo Baggins", 1},
{ "bilbo@anotherexample.com", 1},
{ "t:bilbo@anotherexample.com", 1},
{ "t:bilbo", 1},
{ "f:bilbo", 0},
{ "baggins", 1},
{ "prio:h", 1},
{ "prio:high", 1},
{ "prio:normal", 9},
{ "prio:l", 7},
{ "not prio:l", 10},
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_logic (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "subject:gcc" , 1},
{ "subject:lisp" , 1},
{ "subject:gcc OR subject:lisp" , 2},
{ "subject:gcc or subject:lisp" , 2},
{ "subject:gcc AND subject:lisp" , 0},
{ "subject:gcc OR (subject:scheme AND subject:elisp)" , 2},
{ "(subject:gcc OR subject:scheme) AND subject:elisp" , 1}
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_accented_chars_01 (void)
{
MuQuery *query;
MuMsgIter *iter;
MuMsg *msg;
MuStore *store;
gchar *xpath;
GError *err;
gchar *summ;
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
store = mu_store_new_read_only (xpath, NULL);
g_assert (store);
query = mu_query_new (store, NULL);
mu_store_unref (store);
iter = mu_query_run (query, "fünkÿ", FALSE, MU_MSG_FIELD_ID_NONE,
FALSE, -1, NULL);
err = NULL;
msg = mu_msg_iter_get_msg_floating (iter); /* don't unref */
if (!msg) {
g_warning ("error getting message: %s", err->message);
g_error_free (err);
g_assert_not_reached ();
}
g_assert_cmpstr (mu_msg_get_subject(msg),==,
"Greetings from Lothlórien");
/* TODO: fix this again */
summ = mu_str_summarize (mu_msg_get_body_text(msg), 5);
g_assert_cmpstr (summ,==, "Let's write some fünkÿ text using umlauts. Foo.");
g_free (summ);
mu_msg_iter_destroy (iter);
mu_query_destroy (query);
g_free (xpath);
}
static void
test_mu_query_accented_chars_02 (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "f:mü", 1},
{ "s:motörhead", 1},
{ "t:Helmut", 1},
{ "t:Kröger", 1},
{ "s:MotorHeäD", 1},
{ "queensryche", 1},
{ "Queensrÿche", 1},
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_wildcards (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "f:mü", 1},
{ "s:mo*", 1},
{ "t:Helm*", 1},
{ "queensryche", 1},
{ "Queen*", 1},
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_dates_helsinki (void)
{
gchar *xpath;
int i;
const char *old_tz;
QResults queries[] = {
{ "date:20080731..20080804", 5},
/* { "date:20080804..20080731", 5}, */
{ "date:20080731..20080804", 5},
{ "date:20080731..20080804 s:gcc", 1},
{ "date:200808110803..now", 5},
{ "date:200808110803..today", 5},
/* { "date:now..2008-08-11-08-03", 1}, */
/* { "date:today..2008-08-11-08-03", 1}, */
{ "date:200808110801..now", 5},
};
old_tz = set_tz ("Europe/Helsinki");
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
set_tz (old_tz);
}
static void
test_mu_query_dates_sydney (void)
{
gchar *xpath;
int i;
const char *old_tz;
QResults queries[] = {
{ "date:20080731..20080804", 5},
/* { "date:20080804..20080731", 5}, */
{ "date:20080731..20080804", 5},
{ "date:20080731..20080804 s:gcc", 1},
{ "date:200808110803..now", 5},
{ "date:200808110803..today", 5},
/* { "date:now..2008-08-11-08-03", 1}, */
/* { "date:today..2008-08-11-08-03", 1}, */
{ "date:200808110801..now", 5},
};
old_tz = set_tz ("Australia/Sydney");
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
set_tz (old_tz);
}
static void
test_mu_query_dates_la (void)
{
gchar *xpath;
int i;
const char *old_tz;
QResults queries[] = {
{ "date:20080731..20080804", 5},
/* { "date:20080804..20080731", 5}, */
{ "date:20080731..20080804", 5},
{ "date:20080731..20080804 s:gcc", 1},
{ "date:200808110803..now", 4},
{ "date:200808110803..today", 4},
/* { "date:now..2008-08-11-08-03", 1}, */
/* { "date:today..2008-08-11-08-03", 1}, */
{ "date:200808110801..now", 4}, /* does not match in LA */
};
old_tz = set_tz ("America/Los_Angeles");
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
set_tz (old_tz);
}
static void
test_mu_query_sizes (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "size:0b..2m", 17},
{ "size:2k..4k", 4},
{ "size:2m..0b", 17}
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_attach (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "j:sittingbull.jpg", 1},
{ "file:custer", 0},
{ "file:custer.jpg", 1}
};
xpath = fill_database (MU_TESTMAILDIR2);
g_assert (xpath != NULL);
/* g_print ("(%s)\n", xpath); */
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_tags (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "x:paradise", 1},
{ "tag:lost", 1},
{ "tag:lost tag:paradise", 1},
{ "tag:lost tag:horizon", 0},
{ "tag:lost OR tag:horizon", 1},
{ "x:paradise,lost", 0},
};
xpath = fill_database (MU_TESTMAILDIR2);
g_assert (xpath != NULL);
/* g_print ("(%s)\n", xpath); */
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_signed_encrypted (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "flag:encrypted", 2},
{ "flag:signed", 2},
};
xpath = fill_database (MU_TESTMAILDIR);
g_assert (xpath != NULL);
/* g_print ("(%s)\n", xpath); */
for (i = 0; i != G_N_ELEMENTS(queries); ++i)
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
g_free (xpath);
}
static void
test_mu_query_tags_02 (void)
{
gchar *xpath;
int i;
QResults queries[] = {
{ "x:paradise", 1},
{ "tag:@NextActions", 1},
{ "x:queensrÿche", 1},
{ "tag:lost OR tag:operation*", 2},
};
xpath = fill_database (MU_TESTMAILDIR2);
g_assert (xpath != NULL);
/* g_print ("(%s)\n", xpath); */
for (i = 0; i != G_N_ELEMENTS(queries); ++i) {
/* g_print ("%s\n", queries[i].query); */
g_assert_cmpuint (run_and_count_matches (xpath, queries[i].query),
==, queries[i].count);
}
g_free (xpath);
}
static void
test_mu_query_preprocess (void)
{
unsigned u;
struct {
const gchar *expr, *expected;
} testcases [] = {
{ "hello", "hello" },
{ "/[Gmail].Sent Mail", "__gmail__sent mail" }
/* add more */
};
for (u = 0; u != G_N_ELEMENTS(testcases); ++u) {
gchar *prep;
prep = mu_query_preprocess (testcases[u].expr, NULL);
g_assert_cmpstr (prep, ==, testcases[u].expected);
g_free (prep);
}
}
int
main (int argc, char *argv[])
{
int rv;
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/mu-query/test-mu-query-preprocess",
test_mu_query_preprocess);
g_test_add_func ("/mu-query/test-mu-query-01", test_mu_query_01);
g_test_add_func ("/mu-query/test-mu-query-02", test_mu_query_02);
g_test_add_func ("/mu-query/test-mu-query-03", test_mu_query_03);
g_test_add_func ("/mu-query/test-mu-query-04", test_mu_query_04);
g_test_add_func ("/mu-query/test-mu-query-signed-encrypted",
test_mu_query_signed_encrypted);
g_test_add_func ("/mu-query/test-mu-query-logic", test_mu_query_logic);
g_test_add_func ("/mu-query/test-mu-query-accented-chars-1",
test_mu_query_accented_chars_01);
g_test_add_func ("/mu-query/test-mu-query-accented-chars-2",
test_mu_query_accented_chars_02);
g_test_add_func ("/mu-query/test-mu-query-wildcards",
test_mu_query_wildcards);
g_test_add_func ("/mu-query/test-mu-query-sizes",
test_mu_query_sizes);
g_test_add_func ("/mu-query/test-mu-query-dates-helsinki",
test_mu_query_dates_helsinki); /* finland */
g_test_add_func ("/mu-query/test-mu-query-dates-sydney",
test_mu_query_dates_sydney);
g_test_add_func ("/mu-query/test-mu-query-dates-la",
test_mu_query_dates_la);
g_test_add_func ("/mu-query/test-mu-query-attach",
test_mu_query_attach);
g_test_add_func ("/mu-query/test-mu-query-tags",
test_mu_query_tags);
g_test_add_func ("/mu-query/test-mu-query-tags_02",
test_mu_query_tags_02);
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);
rv = g_test_run ();
return rv;
}

View File

@ -0,0 +1,98 @@
/*
** 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 "src/mu-runtime.h"
static void
test_mu_runtime_init (void)
{
gchar* tmpdir;
tmpdir = test_mu_common_get_random_tmpdir();
g_assert (tmpdir);
g_assert (mu_runtime_init (tmpdir, "test-mu-runtime") == TRUE);
mu_runtime_uninit ();
g_assert (mu_runtime_init (tmpdir, "test-mu-runtime") == TRUE);
mu_runtime_uninit ();
g_free (tmpdir);
}
static void
test_mu_runtime_data (void)
{
gchar *homedir, *xdir, *bmfile;
homedir = test_mu_common_get_random_tmpdir();
g_assert (homedir);
xdir = g_strdup_printf ("%s%c%s", homedir,
G_DIR_SEPARATOR, "xapian" );
bmfile = g_strdup_printf ("%s%c%s", homedir,
G_DIR_SEPARATOR, "bookmarks");
g_assert (mu_runtime_init (homedir, "test-mu-runtime") == TRUE);
g_assert_cmpstr (homedir, ==, mu_runtime_path (MU_RUNTIME_PATH_MUHOME));
g_assert_cmpstr (xdir, ==, mu_runtime_path (MU_RUNTIME_PATH_XAPIANDB));
g_assert_cmpstr (bmfile, ==, mu_runtime_path (MU_RUNTIME_PATH_BOOKMARKS));
mu_runtime_uninit ();
g_free (homedir);
g_free (xdir);
g_free (bmfile);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
/* mu_runtime_init/uninit */
g_test_add_func ("/mu-runtime/mu-runtime-init",
test_mu_runtime_init);
g_test_add_func ("/mu-runtime/mu-runtime-data",
test_mu_runtime_data);
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 ();
}

246
mu/tests/test-mu-threads.c Normal file
View File

@ -0,0 +1,246 @@
/* -*-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.
**
*/
#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-query.h"
#include "mu-str.h"
static gchar*
fill_database (const char *testdir)
{
gchar *cmdline, *tmpdir, *xpath;
tmpdir = test_mu_common_get_random_tmpdir();
cmdline = g_strdup_printf ("%s index --muhome=%s --maildir=%s"
" --quiet",
MU_PROGRAM, tmpdir, testdir);
/* g_print ("%s\n", cmdline); */
g_assert (g_spawn_command_line_sync (cmdline, NULL, NULL,
NULL, NULL));
g_free (cmdline);
xpath= g_strdup_printf ("%s%c%s", tmpdir,
G_DIR_SEPARATOR, "xapian");
g_free (tmpdir);
return xpath;
}
/* note: this also *moves the iter* */
static MuMsgIter*
run_and_get_iter (const char *xpath, const char *query)
{
MuQuery *mquery;
MuStore *store;
MuMsgIter *iter;
store = mu_store_new_read_only (xpath, NULL);
g_assert (store);
mquery = mu_query_new (store, NULL);
mu_store_unref (store);
g_assert (query);
iter = mu_query_run (mquery, query, TRUE, MU_MSG_FIELD_ID_DATE,
FALSE, -1, NULL);
mu_query_destroy (mquery);
g_assert (iter);
return iter;
}
static void
test_mu_threads_01 (void)
{
gchar *xpath;
MuMsgIter *iter;
unsigned u;
struct {
const char* threadpath;
const char *msgid;
const char* subject;
} items [] = {
{"0", "root0@msg.id", "root0"},
{"0:0", "child0.0@msg.id", "Re: child 0.0"},
{"0:1", "child0.1@msg.id", "Re: child 0.1"},
{"0:1:0", "child0.1.0@msg.id", "Re: child 0.1.0"},
{"1", "root1@msg.id", "root1"},
{"2", "root2@msg.id", "root2"},
/* next one's been promoted 2.0.0 => 2.0 */
{"2:0", "child2.0.0@msg.id", "Re: child 2.0.0"},
/* next one's been promoted 3.0.0.0.0 => 3 */
{"3", "child3.0.0.0.0@msg.id", "Re: child 3.0.0.0"},
/* two children of absent root 4.0 */
{"4:0", "child4.0@msg.id", "Re: child 4.0"},
{"4:1", "child4.1@msg.id", "Re: child 4.1"}
};
xpath = fill_database (MU_TESTMAILDIR3);
g_assert (xpath != NULL);
iter = run_and_get_iter (xpath, "abc");
g_assert (iter);
g_assert (!mu_msg_iter_is_done(iter));
u = 0;
while (!mu_msg_iter_is_done (iter) && u < G_N_ELEMENTS(items)) {
MuMsg *msg;
const MuMsgIterThreadInfo *ti;
ti = mu_msg_iter_get_thread_info (iter);
if (!ti)
g_print ("%s: thread info not found for %u\n",
__FUNCTION__, (unsigned)mu_msg_iter_get_docid(iter));
g_assert(ti);
msg = mu_msg_iter_get_msg_floating (iter);
g_assert (msg);
/* g_print ("%s %s %s\n", ti->threadpath, */
/* mu_msg_get_msgid(msg), */
/* mu_msg_get_path (msg) */
/* ); */
g_assert (u < G_N_ELEMENTS(items));
g_assert_cmpstr (ti->threadpath,==,items[u].threadpath);
g_assert_cmpstr (mu_msg_get_subject(msg),==,items[u].subject);
g_assert_cmpstr (mu_msg_get_msgid(msg),==,items[u].msgid);
++u;
mu_msg_iter_next (iter);
}
g_assert (u == G_N_ELEMENTS(items));
g_free (xpath);
mu_msg_iter_destroy (iter);
}
struct _tinfo {
const char* threadpath;
const char *msgid;
const char* subject;
};
typedef struct _tinfo tinfo;
static void
test_mu_threads_rogue (void)
{
gchar *xpath;
MuMsgIter *iter;
unsigned u;
tinfo *items;
tinfo items1 [] = {
{"0", "cycle0@msg.id", "cycle0"},
{"0:0", "cycle0.0@msg.id", "cycle0.0"},
{"0:0:0", "cycle0.0.0@msg.id", "cycle0.0.0"},
{"0:1", "rogue0@msg.id", "rogue0"},
};
tinfo items2 [] = {
{"0", "cycle0.0@msg.id", "cycle0.0"},
{"0:0", "cycle0@msg.id", "cycle0"},
{"0:0:0", "rogue0@msg.id", "rogue0" },
{"0:1", "cycle0.0.0@msg.id", "cycle0.0.0"}
};
xpath = fill_database (MU_TESTMAILDIR3);
g_assert (xpath != NULL);
iter = run_and_get_iter (xpath, "def");
g_assert (iter);
g_assert (!mu_msg_iter_is_done(iter));
/* due to the random order in files can be indexed, there are two possible ways
* for the threads to be built-up; both are okay */
if (g_strcmp0 (mu_msg_get_msgid(mu_msg_iter_get_msg_floating (iter)),
"cycle0@msg.id") == 0)
items = items1;
else
items = items2;
u = 0;
while (!mu_msg_iter_is_done (iter) && u < G_N_ELEMENTS(items1)) {
MuMsg *msg;
const MuMsgIterThreadInfo *ti;
ti = mu_msg_iter_get_thread_info (iter);
if (!ti)
g_print ("%s: thread info not found\n",
mu_msg_get_msgid(mu_msg_iter_get_msg_floating (iter)));
g_assert(ti);
msg = mu_msg_iter_get_msg_floating (iter); /* don't unref */
/* g_print ("%s %s %s\n", ti->threadpath, */
/* mu_msg_get_msgid(msg), */
/* mu_msg_get_path (msg) */
/* ); */
g_assert (u < G_N_ELEMENTS(items1));
g_assert_cmpstr (ti->threadpath,==,(items)[u].threadpath);
g_assert_cmpstr (mu_msg_get_subject(msg),==,(items)[u].subject);
g_assert_cmpstr (mu_msg_get_msgid(msg),==,(items)[u].msgid);
++u;
mu_msg_iter_next (iter);
}
g_assert (u == G_N_ELEMENTS(items1));
g_free (xpath);
mu_msg_iter_destroy (iter);
}
int
main (int argc, char *argv[])
{
int rv;
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/mu-query/test-mu-threads-01", test_mu_threads_01);
g_test_add_func ("/mu-query/test-mu-threads-rogue", test_mu_threads_rogue);
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;
}