mirror of
https://github.com/djcb/mu.git
synced 2024-06-28 07:41:04 +02:00
1039 lines
23 KiB
C
1039 lines
23 KiB
C
/* -*-mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-*/
|
|
/*
|
|
** 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 <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <glib/gprintf.h>
|
|
|
|
/* hopefully, the should get us a sane PATH_MAX */
|
|
#include <limits.h>
|
|
/* not all systems provide PATH_MAX in limits.h */
|
|
#ifndef PATH_MAX
|
|
#include <sys/param.h>
|
|
#ifndef PATH_MAX
|
|
#define PATH_MAX MAXPATHLEN
|
|
#endif /*!PATH_MAX */
|
|
#endif /*PATH_MAX */
|
|
|
|
#include "mu-str.h"
|
|
#include "mu-cmd.h"
|
|
#include "mu-maildir.h"
|
|
#include "mu-query.h"
|
|
#include "mu-index.h"
|
|
#include "mu-msg-part.h"
|
|
|
|
|
|
static gboolean MU_CAUGHT_SIGNAL;
|
|
|
|
static void
|
|
sig_handler (int sig)
|
|
{
|
|
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));;
|
|
}
|
|
|
|
|
|
/* BOX - beginning-of-expression */
|
|
#define BOX "\376"
|
|
|
|
static void send_expr (const char* frm, ...) G_GNUC_PRINTF(1, 2);
|
|
|
|
static void
|
|
send_expr (const char* frm, ...)
|
|
{
|
|
char *expr;
|
|
va_list ap;
|
|
char hdr[16];
|
|
size_t exprlen, hdrlen;
|
|
|
|
va_start (ap, frm);
|
|
|
|
expr = NULL;
|
|
exprlen = g_vasprintf (&expr, frm, ap);
|
|
hdrlen = snprintf (hdr, sizeof(hdr), BOX "%u" BOX,
|
|
(unsigned)exprlen);
|
|
|
|
if (write (fileno(stdout), hdr, hdrlen) < 0)
|
|
MU_WRITE_LOG ("error writing output: %s", strerror(errno));
|
|
|
|
if (write (fileno(stdout), expr, exprlen) < 0)
|
|
MU_WRITE_LOG ("error writing output: %s", strerror(errno));
|
|
|
|
g_free (expr);
|
|
va_end (ap);
|
|
}
|
|
|
|
|
|
static MuError server_error (GError **err, MuError merr, const char* frm, ...)
|
|
G_GNUC_PRINTF(3, 4);
|
|
|
|
static MuError
|
|
server_error (GError **err, MuError merr, const char* frm, ...)
|
|
{
|
|
gboolean has_err;
|
|
char *errmsg;
|
|
va_list ap;
|
|
|
|
va_start (ap, frm);
|
|
errmsg = g_strdup_vprintf (frm, ap);
|
|
|
|
has_err = err && *err;
|
|
send_expr ("(:error %u :error-message \"%s\")\n",
|
|
has_err ? (unsigned)(*err)->code : merr,
|
|
has_err ? (*err)->message : errmsg);
|
|
|
|
g_free (errmsg);
|
|
va_end (ap);
|
|
|
|
return has_err ? (unsigned)(*err)->code : merr;
|
|
}
|
|
|
|
|
|
|
|
#define MU_PROMPT ";; mu> "
|
|
|
|
static gchar*
|
|
my_readline (const char *prompt)
|
|
{
|
|
GString *gstr;
|
|
int kar;
|
|
|
|
gstr = g_string_sized_new (512);
|
|
|
|
fputs (prompt, stdout);
|
|
|
|
do {
|
|
kar = fgetc (stdin);
|
|
if (kar == '\n' || kar == EOF)
|
|
break;
|
|
else
|
|
gstr = g_string_append_c (gstr, (char)kar);
|
|
|
|
} while (1);
|
|
|
|
return g_string_free (gstr, FALSE);
|
|
}
|
|
|
|
enum _Cmd {
|
|
CMD_ADD,
|
|
CMD_COMPOSE,
|
|
CMD_FIND,
|
|
CMD_FLAG,
|
|
CMD_HELP,
|
|
CMD_INDEX,
|
|
CMD_MKDIR,
|
|
CMD_MOVE,
|
|
CMD_OPEN,
|
|
CMD_QUIT,
|
|
CMD_REMOVE,
|
|
CMD_SAVE,
|
|
CMD_SENT,
|
|
CMD_PING,
|
|
CMD_VIEW,
|
|
|
|
CMD_IGNORE
|
|
};
|
|
typedef enum _Cmd Cmd;
|
|
static const Cmd CMD_INVALID = (Cmd)-1;
|
|
|
|
static Cmd
|
|
cmd_from_string (const char *str)
|
|
{
|
|
unsigned u;
|
|
struct {
|
|
Cmd cmd;
|
|
const char *name;
|
|
} commands[] = {
|
|
{ CMD_ADD, "add"},
|
|
{ CMD_COMPOSE, "compose"},
|
|
{ CMD_FIND, "find" },
|
|
{ CMD_FLAG, "flag"},
|
|
{ CMD_INDEX, "index"},
|
|
{ CMD_MKDIR, "mkdir"},
|
|
{ CMD_MOVE, "move"},
|
|
{ CMD_OPEN, "open" },
|
|
{ CMD_QUIT, "quit"},
|
|
{ CMD_REMOVE, "remove" },
|
|
{ CMD_SAVE, "save"},
|
|
{ CMD_SENT, "sent"},
|
|
{ CMD_PING, "ping"},
|
|
{ CMD_VIEW, "view"}
|
|
};
|
|
|
|
for (u = 0; u != G_N_ELEMENTS(commands); ++u)
|
|
if (g_strcmp0 (str, commands[u].name) == 0)
|
|
return commands[u].cmd;
|
|
|
|
/* handle all-blank strings */
|
|
/* if (g_regex_match_simple ("^ *$", str, 0, 0)) */
|
|
/* return CMD_IGNORE; */
|
|
|
|
return CMD_INVALID;
|
|
}
|
|
|
|
|
|
static Cmd
|
|
parse_line (const gchar *line, GSList **args, GError **err)
|
|
{
|
|
Cmd cmd;
|
|
GSList *lst;
|
|
|
|
*args = NULL;
|
|
|
|
if (!line)
|
|
return CMD_IGNORE;
|
|
|
|
lst = mu_str_esc_to_list (line, err);
|
|
if (!lst)
|
|
return CMD_INVALID;
|
|
|
|
cmd = cmd_from_string ((const char*)lst->data);
|
|
|
|
if (cmd == CMD_INVALID) {
|
|
mu_str_free_list (lst);
|
|
*args = NULL;
|
|
} else {
|
|
*args = g_slist_next (lst);
|
|
lst->next = NULL;
|
|
mu_str_free_list (lst);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
|
|
/*
|
|
* creating a message object just to get a path seems a bit excessive
|
|
* maybe mu_store_get_path could be added if this turns out to be a problem
|
|
*
|
|
* NOTE: not re-entrant.
|
|
*/
|
|
static const char*
|
|
get_path_from_docid (MuStore *store, unsigned docid, GError **err)
|
|
{
|
|
MuMsg *msg;
|
|
const char* msgpath;
|
|
static char path[PATH_MAX + 1];
|
|
|
|
msg = mu_store_get_msg (store, docid, err);
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
msgpath = mu_msg_get_path (msg);
|
|
if (!msgpath) {
|
|
mu_msg_unref (msg);
|
|
return NULL;
|
|
}
|
|
|
|
strncpy (path, msgpath, sizeof(path));
|
|
|
|
mu_msg_unref (msg);
|
|
return path;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
check_param_num (GSList *args, unsigned min, unsigned max)
|
|
{
|
|
unsigned len;
|
|
|
|
len = g_slist_length (args);
|
|
|
|
return len >= min && len <= max;
|
|
}
|
|
|
|
|
|
#define return_if_fail_param_num(ARGS,MN,MX,USAGE) \
|
|
do { \
|
|
if (!check_param_num((ARGS),(MN),(MX))) \
|
|
return server_error(NULL,MU_ERROR_IN_PARAMETERS, \
|
|
"%s",(USAGE)); \
|
|
} while (0)
|
|
|
|
|
|
|
|
|
|
/* -> ping
|
|
* <- (:pong mu :version <version> :doccount <doccount>)
|
|
*/
|
|
static MuError
|
|
cmd_ping (MuStore *store, GSList *args, GError **err)
|
|
{
|
|
return_if_fail_param_num (args, 0, 0, "usage: version");
|
|
|
|
send_expr ("(:pong \"" PACKAGE_NAME "\" "
|
|
":version \"" VERSION "\" "
|
|
":doccount %u"
|
|
")\n",
|
|
mu_store_count (store, err));
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
static unsigned
|
|
output_found_sexps (MuMsgIter *iter, int maxnum)
|
|
{
|
|
unsigned u, max;
|
|
|
|
u = 0;
|
|
if (maxnum > 0)
|
|
max = (unsigned) maxnum;
|
|
else
|
|
max = G_MAXUINT32;
|
|
|
|
while (!mu_msg_iter_is_done (iter) && u <= max &&
|
|
!MU_CAUGHT_SIGNAL) {
|
|
|
|
MuMsg *msg;
|
|
|
|
msg = mu_msg_iter_get_msg_floating (iter);
|
|
|
|
if (mu_msg_is_readable (msg)) {
|
|
char *sexp;
|
|
sexp = mu_msg_to_sexp (msg, mu_msg_iter_get_docid (iter),
|
|
mu_msg_iter_get_thread_info (iter),
|
|
TRUE);
|
|
send_expr ("%s", sexp);
|
|
g_free (sexp);
|
|
++u;
|
|
}
|
|
mu_msg_iter_next (iter);
|
|
}
|
|
|
|
return u;
|
|
}
|
|
|
|
|
|
/*
|
|
* find <query> <maxnum>
|
|
* => list of s-expression, each describing a message
|
|
* => (:found <number of found messages>)
|
|
*/
|
|
static MuError
|
|
cmd_find (MuStore *store, MuQuery *query, GSList *args, GError **err)
|
|
{
|
|
MuMsgIter *iter;
|
|
int maxnum;
|
|
const char* usage;
|
|
|
|
usage = "usage: find <searchexpr> <maxnum>";
|
|
|
|
return_if_fail_param_num (args, 2, 2, usage);
|
|
|
|
if ((maxnum = atoi((const char*)args->next->data)) == 0)
|
|
return server_error (NULL, MU_ERROR_IN_PARAMETERS,
|
|
"%s", usage);
|
|
|
|
/* TODO: ask for *all* results, then, get the <maxnum> newest
|
|
* ones; it seems we cannot get a sorted list of a subset of
|
|
* the result --> needs investigation, this is a
|
|
* work-around */
|
|
iter = mu_query_run (query, (const char*)args->data, TRUE,
|
|
MU_MSG_FIELD_ID_DATE, TRUE, -1, err);
|
|
if (!iter)
|
|
return server_error (err, MU_ERROR_INTERNAL,
|
|
"couldn't get iterator");
|
|
|
|
/* before sending new results, send an 'erase' message, so the
|
|
* frontend knows it should erase the headers buffer. this
|
|
* will ensure that the output of two finds quickly will not
|
|
* be mixed. */
|
|
send_expr ("(:erase t)");
|
|
|
|
/* return results + the number of results found */
|
|
send_expr ("(:found %u)\n", output_found_sexps (iter, maxnum));
|
|
mu_msg_iter_destroy (iter);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
|
|
static MuError
|
|
cmd_mkdir (GSList *args, GError **err)
|
|
{
|
|
const char *path;
|
|
|
|
return_if_fail_param_num (args, 1, 1, "usage: mkdir <path>");
|
|
|
|
path = (const char*)args->data;
|
|
|
|
if (!mu_maildir_mkdir (path, 0755, FALSE, err))
|
|
return server_error (err, MU_G_ERROR_CODE (err),
|
|
"failed to create maildir '%s'",
|
|
path);
|
|
|
|
send_expr ("(:info mkdir :message \"%s has been created\")",
|
|
path);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
static unsigned
|
|
get_docid_from_msgid (MuQuery *query, const char *str, GError **err)
|
|
{
|
|
gchar *querystr;
|
|
unsigned docid;
|
|
MuMsgIter *iter;
|
|
|
|
querystr = g_strdup_printf ("msgid:%s", str);
|
|
iter = mu_query_run (query, querystr, FALSE,
|
|
MU_MSG_FIELD_ID_NONE, FALSE, 1, err);
|
|
g_free (querystr);
|
|
docid = MU_STORE_INVALID_DOCID;
|
|
if (!iter || mu_msg_iter_is_done (iter))
|
|
if (err && *err == NULL)
|
|
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_NO_MATCHES,
|
|
"could not find message %s", str);
|
|
else
|
|
return docid;
|
|
else {
|
|
MuMsg *msg;
|
|
msg = mu_msg_iter_get_msg_floating (iter);
|
|
if (!mu_msg_is_readable(msg)) {
|
|
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_FILE_CANNOT_READ,
|
|
"'%s' is not readable",
|
|
mu_msg_get_path(msg));
|
|
} else
|
|
docid = mu_msg_iter_get_docid (iter);
|
|
|
|
mu_msg_iter_destroy (iter);
|
|
}
|
|
|
|
return docid;
|
|
}
|
|
|
|
|
|
/* the string contains either a number (docid) or a message-id if it
|
|
* doesn't look like a number, and the query param is non-nil, try to
|
|
* locale the message with message-id in the database, and return its
|
|
* docid */
|
|
static unsigned
|
|
get_docid (MuQuery *query, const char *str, GError **err)
|
|
{
|
|
unsigned docid;
|
|
char *endptr;
|
|
|
|
docid = strtol (str, &endptr, 10);
|
|
if (*endptr != '\0') {
|
|
if (!query) {
|
|
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
|
|
"invalid docid '%s'", str);
|
|
return MU_STORE_INVALID_DOCID;
|
|
} else
|
|
return get_docid_from_msgid (query, str, err);
|
|
}
|
|
|
|
return docid;
|
|
}
|
|
|
|
|
|
static MuFlags
|
|
get_flags (const char *path, const char *flagstr)
|
|
{
|
|
if (!flagstr)
|
|
return MU_FLAG_NONE; /* ie., ignore flags */
|
|
else {
|
|
/* if there's a '+' or '-' sign in the string, it must
|
|
* be a flag-delta */
|
|
if (strstr (flagstr, "+") || strstr (flagstr, "-")) {
|
|
MuFlags oldflags;
|
|
oldflags = mu_maildir_get_flags_from_path (path);
|
|
return mu_flags_from_str_delta (flagstr, oldflags,
|
|
MU_FLAG_TYPE_ANY);
|
|
} else
|
|
return mu_flags_from_str (flagstr, MU_FLAG_TYPE_ANY);
|
|
}
|
|
}
|
|
|
|
static MuError
|
|
do_move (MuStore *store, unsigned docid, MuMsg *msg, const char *maildir,
|
|
MuFlags flags, gboolean is_move, GError **err)
|
|
{
|
|
gchar *sexp;
|
|
unsigned rv;
|
|
|
|
if (!mu_msg_move_to_maildir (msg, maildir, flags, TRUE, err))
|
|
return MU_G_ERROR_CODE (err);
|
|
|
|
/* note, after mu_msg_move_to_maildir, path will be the *new*
|
|
* path, and flags and maildir fields will be updated as
|
|
* wel */
|
|
rv = mu_store_update_msg (store, docid, msg, err);
|
|
if (rv == MU_STORE_INVALID_DOCID)
|
|
return server_error (err, MU_ERROR_XAPIAN, "%s",
|
|
"failed to update message");
|
|
|
|
sexp = mu_msg_to_sexp (msg, docid, NULL, FALSE/*include body*/);
|
|
send_expr ("(:update %s :move %s)", sexp, is_move ? "t" : "nil");
|
|
|
|
g_free (sexp);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
|
|
static MuError
|
|
move_or_flag (MuStore *store, MuQuery *query, GSList *args, gboolean is_move,
|
|
GError **err)
|
|
{
|
|
MuError merr;
|
|
unsigned docid;
|
|
MuMsg *msg;
|
|
MuFlags flags;
|
|
GSList *flagitem;
|
|
const char *mdir;
|
|
|
|
if ((docid = get_docid (query, (const char*)args->data, err)) == 0)
|
|
return server_error (err, MU_ERROR_IN_PARAMETERS,
|
|
"invalid docid '%s'", (char*)args->data);
|
|
|
|
if (!(msg = mu_store_get_msg (store, docid, err)))
|
|
return server_error (err, MU_ERROR, "failed to get message");
|
|
|
|
if (is_move) {
|
|
mdir = (const char*)g_slist_nth (args, 1)->data;
|
|
flagitem = g_slist_nth (args, 2);
|
|
} else { /* flag */
|
|
mdir = mu_msg_get_maildir (msg);
|
|
flagitem = g_slist_nth (args, 1);
|
|
}
|
|
|
|
flags = get_flags (mu_msg_get_path(msg),
|
|
flagitem ? (gchar*)flagitem->data : NULL);
|
|
if (flags == MU_FLAG_INVALID) {
|
|
mu_msg_unref (msg);
|
|
return server_error (err, MU_ERROR_IN_PARAMETERS, "invalid flags");
|
|
}
|
|
|
|
merr = do_move (store, docid, msg, mdir, flags, is_move, err);
|
|
mu_msg_unref (msg);
|
|
|
|
return (merr == MU_OK) ? MU_OK :
|
|
server_error (err, merr, "error moving/flagging file");
|
|
}
|
|
|
|
|
|
static MuError
|
|
cmd_move (MuStore *store, GSList *args, GError **err)
|
|
{
|
|
return_if_fail_param_num (args, 2, 3,
|
|
"usage: move <docid> <maildir> [<flags>]");
|
|
|
|
return move_or_flag (store, NULL, args, TRUE, err);
|
|
}
|
|
|
|
static MuError
|
|
cmd_flag (MuStore *store, MuQuery *query, GSList *args, GError **err)
|
|
{
|
|
return_if_fail_param_num (args, 2, 2,
|
|
"usage: flag <docid>|<msgid> <flags>");
|
|
|
|
return move_or_flag (store, query, args, FALSE, err);
|
|
}
|
|
|
|
|
|
|
|
static MuError
|
|
cmd_remove (MuStore *store, GSList *args, GError **err)
|
|
{
|
|
unsigned docid;
|
|
const char *path;
|
|
|
|
return_if_fail_param_num (args, 1, 1, "usage: remove <docid>");
|
|
|
|
docid = get_docid (NULL, (const char*)args->data, err);
|
|
if (docid == MU_STORE_INVALID_DOCID)
|
|
return server_error (err, MU_ERROR_IN_PARAMETERS,
|
|
"invalid docid");
|
|
|
|
path = get_path_from_docid (store, docid, err);
|
|
if (!path)
|
|
return server_error (err, MU_ERROR_IN_PARAMETERS,
|
|
"no valid doc for docid");
|
|
|
|
if (unlink (path) != 0)
|
|
return server_error (err, MU_ERROR_FILE_CANNOT_UNLINK, "%s",
|
|
strerror (errno));
|
|
|
|
if (!mu_store_remove_path (store, path))
|
|
return server_error (NULL, MU_ERROR_XAPIAN_REMOVE_FAILED,
|
|
"failed to remove from database");
|
|
|
|
send_expr ("(:remove %u)", docid);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
|
|
static MuError
|
|
save_part (MuMsg *msg, const char* targetpath, unsigned partindex, GError **err)
|
|
{
|
|
gboolean rv;
|
|
gchar *path;
|
|
|
|
rv = mu_msg_part_save (msg, targetpath, partindex,
|
|
TRUE/*overwrite*/, FALSE/*use cache*/, err);
|
|
if (!rv)
|
|
return server_error (err, MU_ERROR_FILE,
|
|
"failed to save to target path");
|
|
|
|
path = mu_str_escape_c_literal (targetpath, FALSE);
|
|
send_expr ("(:info save :message \"%s has been saved\")",
|
|
path);
|
|
g_free (path);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
static MuError
|
|
open_part (MuMsg *msg, unsigned partindex, GError **err)
|
|
{
|
|
char *targetpath;
|
|
gboolean rv;
|
|
|
|
targetpath = mu_msg_part_filepath_cache (msg, partindex);
|
|
|
|
rv = mu_msg_part_save (msg, targetpath, partindex,
|
|
FALSE/*overwrite*/, TRUE/*use cache*/, err);
|
|
if (!rv) {
|
|
g_free (targetpath);
|
|
return server_error (err, MU_ERROR_FILE,
|
|
"failed to save");
|
|
}
|
|
|
|
rv = mu_util_play (targetpath, TRUE/*allow local*/,
|
|
FALSE/*allow remote*/);
|
|
if (!rv) {
|
|
g_free (targetpath);
|
|
return server_error (err, MU_ERROR_FILE, "failed to open");
|
|
} else {
|
|
gchar *path;
|
|
path = mu_str_escape_c_literal (targetpath, FALSE);
|
|
send_expr ("(:info open :message \"%s has been opened\")",
|
|
path);
|
|
g_free (path);
|
|
}
|
|
|
|
g_free (targetpath);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
|
|
static MuError
|
|
save_or_open (MuStore *store, GSList *args, gboolean is_save, GError **err)
|
|
{
|
|
MuMsg *msg;
|
|
unsigned docid, partindex;
|
|
gboolean rv;
|
|
|
|
docid = get_docid (NULL, (const char*)args->data, err);
|
|
if (docid == MU_STORE_INVALID_DOCID)
|
|
return server_error (err, MU_ERROR_IN_PARAMETERS,
|
|
"invalid docid");
|
|
|
|
msg = mu_store_get_msg (store, docid, err);
|
|
if (!msg)
|
|
return server_error (err, MU_ERROR, "failed to get message");
|
|
|
|
partindex = atoi((const char*)g_slist_nth (args, 1)->data);
|
|
|
|
if (is_save) {
|
|
const char *targetpath;
|
|
targetpath = ((const char*)g_slist_nth (args, 2)->data);
|
|
rv = save_part (msg, targetpath, partindex, err);
|
|
} else
|
|
rv = open_part (msg, partindex, err);
|
|
|
|
mu_msg_unref (msg);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
|
|
static MuError
|
|
cmd_save (MuStore *store, GSList *args, GError **err)
|
|
{
|
|
return_if_fail_param_num (args, 3, 3,
|
|
"save <docid> <partindex> <targetpath>");
|
|
|
|
return save_or_open (store, args, TRUE, err);
|
|
}
|
|
|
|
|
|
static MuError
|
|
cmd_open (MuStore *store, GSList *args, GError **err)
|
|
{
|
|
return_if_fail_param_num (args, 2, 2,
|
|
"open <docid> <partindex>");
|
|
|
|
return save_or_open (store, args, FALSE, err);
|
|
}
|
|
|
|
|
|
|
|
static MuError
|
|
cmd_view (MuStore *store, MuQuery *query, GSList *args, GError **err)
|
|
{
|
|
MuMsg *msg;
|
|
unsigned docid;
|
|
char *sexp;
|
|
|
|
return_if_fail_param_num (args, 1, 1, "view <docid>|<msgid>");
|
|
|
|
docid = get_docid (query, (const char*)args->data, err);
|
|
if (docid == MU_STORE_INVALID_DOCID)
|
|
return server_error (err, MU_ERROR_IN_PARAMETERS,
|
|
"invalid docid");
|
|
|
|
msg = mu_store_get_msg (store, docid, err);
|
|
if (!msg)
|
|
return server_error (err, MU_ERROR,
|
|
"failed to get message");
|
|
|
|
sexp = mu_msg_to_sexp (msg, docid, NULL, FALSE);
|
|
mu_msg_unref (msg);
|
|
|
|
send_expr ("(:view %s)\n", sexp);
|
|
|
|
g_free (sexp);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
static void
|
|
each_part (MuMsg *msg, MuMsgPart *part, GSList **attlist)
|
|
{
|
|
char *att, *cachefile;
|
|
GError *err;
|
|
|
|
/* exclude things that don't look like proper attachments */
|
|
if (!mu_msg_part_looks_like_attachment(part, TRUE))
|
|
return;
|
|
|
|
err = NULL;
|
|
cachefile = mu_msg_part_save_temp (msg, part->index, &err);
|
|
if (!cachefile) {
|
|
server_error (&err, MU_ERROR_FILE, "could not save %s",
|
|
cachefile);
|
|
goto leave;
|
|
}
|
|
|
|
att = g_strdup_printf (
|
|
"(:file-name \"%s\" :mime-type \"%s/%s\" :disposition \"%s\")",
|
|
cachefile,
|
|
part->type, part->subtype,
|
|
part->disposition ? part->disposition : "attachment");
|
|
|
|
*attlist = g_slist_append (*attlist, att);
|
|
|
|
leave:
|
|
g_clear_error (&err);
|
|
g_free (cachefile);
|
|
}
|
|
|
|
|
|
/* take the attachments of msg, save them as tmp files, and return
|
|
* as sexp (as a string) describing them
|
|
*
|
|
* ((:name <filename> :mime-type <mime-type> :disposition
|
|
* <attachment|inline>) ... )
|
|
*
|
|
*/
|
|
static gchar*
|
|
include_attachments (MuMsg *msg)
|
|
{
|
|
GSList *attlist, *cur;
|
|
GString *gstr;
|
|
|
|
attlist = NULL;
|
|
mu_msg_part_foreach (msg, FALSE, (MuMsgPartForeachFunc)each_part,
|
|
&attlist);
|
|
|
|
gstr = g_string_sized_new (512);
|
|
gstr = g_string_append_c (gstr, '(');
|
|
for (cur = attlist; cur; cur = g_slist_next (cur))
|
|
g_string_append (gstr, (gchar*)cur->data);
|
|
gstr = g_string_append_c (gstr, ')');
|
|
|
|
mu_str_free_list (attlist);
|
|
|
|
return g_string_free (gstr, FALSE);
|
|
}
|
|
|
|
|
|
static MuError
|
|
cmd_compose (MuStore *store, GSList *args, GError **err)
|
|
{
|
|
MuMsg *msg;
|
|
unsigned docid;
|
|
char *sexp, *atts;
|
|
const char* ctype;
|
|
|
|
return_if_fail_param_num (args, 2, 2,
|
|
"compose <reply|forward|edit> <docid>");
|
|
|
|
ctype = (const char*)args->data;
|
|
if (strcmp (ctype, "reply") != 0 && strcmp(ctype, "forward") != 0
|
|
&& strcmp (ctype, "edit") != 0)
|
|
return server_error (NULL, MU_ERROR_IN_PARAMETERS,
|
|
"compose <reply|forward|edit> <docid>");
|
|
|
|
docid = get_docid (NULL, (const char*)g_slist_nth(args, 1)->data, err);
|
|
if (docid == MU_STORE_INVALID_DOCID)
|
|
return server_error (err, MU_ERROR_IN_PARAMETERS,
|
|
"invalid docid");
|
|
|
|
msg = mu_store_get_msg (store, docid, err);
|
|
if (!msg)
|
|
return server_error (err, MU_ERROR, "failed to get message");
|
|
|
|
sexp = mu_msg_to_sexp (msg, docid, NULL, FALSE);
|
|
atts = (!strcmp(ctype, "forward")) ? include_attachments (msg) : NULL;
|
|
|
|
mu_msg_unref (msg);
|
|
send_expr ("(:compose-type %s :original %s :include %s)",
|
|
ctype, sexp, atts ? atts : "()");
|
|
|
|
g_free (sexp);
|
|
g_free (atts);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
static MuError
|
|
index_msg_cb (MuIndexStats *stats, void *user_data)
|
|
{
|
|
if (MU_CAUGHT_SIGNAL)
|
|
return MU_STOP;
|
|
|
|
if (stats->_processed % 1000)
|
|
return MU_OK;
|
|
|
|
send_expr ("(:info index :status running "
|
|
":processed %u :updated %u)",
|
|
stats->_processed, stats->_updated);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
static MuError
|
|
cmd_add_or_sent (MuStore *store, Cmd add_or_sent, GSList *args, GError **err)
|
|
{
|
|
unsigned docid;
|
|
const char *path, *maildir;
|
|
gchar *escpath;
|
|
|
|
g_return_val_if_fail (add_or_sent == CMD_ADD || add_or_sent == CMD_SENT,
|
|
MU_ERROR_INTERNAL);
|
|
return_if_fail_param_num (args, 2, 2,
|
|
"usage: add|sent <path> <maildir>");
|
|
|
|
path = (const char*)args->data;
|
|
maildir = (const char*)g_slist_nth (args, 1)->data;
|
|
|
|
docid = mu_store_add_path (store, path, maildir, err);
|
|
if (docid == MU_STORE_INVALID_DOCID)
|
|
return server_error (err, MU_ERROR_XAPIAN,
|
|
"failed to add path '%s'", path);
|
|
|
|
escpath = mu_str_escape_c_literal (path, TRUE);
|
|
|
|
if (add_or_sent == CMD_ADD)
|
|
send_expr ("(:info add :path %s :docid %u)",
|
|
escpath, docid);
|
|
else
|
|
send_expr ("(:sent t :path %s :docid %u)",
|
|
escpath, docid);
|
|
|
|
g_free (escpath);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
static MuError
|
|
cmd_index (MuStore *store, GSList *args, GError **err)
|
|
{
|
|
MuIndex *index;
|
|
const char *maildir;
|
|
MuIndexStats stats, stats2;
|
|
MuError rv;
|
|
|
|
return_if_fail_param_num (args, 1, 1,
|
|
"usage: index <maildir>");
|
|
|
|
index = mu_index_new (store, err);
|
|
if (!index)
|
|
return server_error (err, MU_ERROR,
|
|
"failed to create index object");
|
|
|
|
mu_index_stats_clear (&stats);
|
|
maildir = (const char*)args->data;
|
|
rv = mu_index_run (index, maildir, FALSE, &stats, index_msg_cb, NULL, NULL);
|
|
|
|
if (rv != MU_OK && rv != MU_STOP) {
|
|
mu_index_destroy (index);
|
|
return server_error (NULL, rv, "indexing failed");
|
|
}
|
|
|
|
mu_index_stats_clear (&stats2);
|
|
rv = mu_index_cleanup (index, &stats2, NULL, NULL, err);
|
|
mu_index_destroy (index);
|
|
if (rv != MU_OK && rv != MU_STOP)
|
|
return server_error (err, rv, "cleanup failed");
|
|
|
|
mu_store_flush (store);
|
|
|
|
send_expr ("(:info index :status complete "
|
|
":processed %u :updated %u :cleaned-up %u)",
|
|
stats._processed, stats._updated, stats2._cleaned_up);
|
|
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
static MuError
|
|
cmd_quit (GSList *args, GError **err)
|
|
{
|
|
return_if_fail_param_num (args, 0, 0, "usage: quit");
|
|
|
|
send_expr (";; quiting\n");
|
|
return MU_OK;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
handle_command (Cmd cmd, MuStore *store, MuQuery *query, GSList *args,
|
|
GError **err)
|
|
{
|
|
MuError rv;
|
|
|
|
switch (cmd) {
|
|
|
|
case CMD_ADD: rv = cmd_add_or_sent (store, CMD_ADD,
|
|
args, err); break;
|
|
case CMD_COMPOSE: rv = cmd_compose (store, args, err); break;
|
|
case CMD_FIND: rv = cmd_find (store, query, args, err); break;
|
|
case CMD_FLAG: rv = cmd_flag (store, query, args, err); break;
|
|
case CMD_INDEX: rv = cmd_index (store, args, err); break;
|
|
case CMD_MKDIR: rv = cmd_mkdir (args, err); break;
|
|
case CMD_MOVE: rv = cmd_move (store, args, err); break;
|
|
case CMD_OPEN: rv = cmd_open (store, args, err); break;
|
|
case CMD_QUIT: rv = cmd_quit (args, err); break;
|
|
case CMD_REMOVE: rv = cmd_remove (store, args, err); break;
|
|
case CMD_SAVE: rv = cmd_save (store, args, err); break;
|
|
case CMD_SENT: rv = cmd_add_or_sent (store, CMD_SENT,
|
|
args, err); break;
|
|
case CMD_PING: rv = cmd_ping (store, args, err); break;
|
|
case CMD_VIEW: rv = cmd_view (store, query, args, err); break;
|
|
|
|
case CMD_IGNORE: return TRUE;
|
|
default:
|
|
g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_IN_PARAMETERS,
|
|
"unknown command");
|
|
return FALSE;
|
|
}
|
|
|
|
return rv == MU_OK ? TRUE : FALSE;
|
|
}
|
|
|
|
|
|
MuError
|
|
mu_cmd_server (MuStore *store, MuConfig *opts, GError **err)
|
|
{
|
|
MuQuery *query;
|
|
|
|
g_return_val_if_fail (store, MU_ERROR_INTERNAL);
|
|
|
|
install_sig_handler ();
|
|
|
|
fputs (";; welcome to mu\n", stdout);
|
|
fputs (";; type your commands, and press Enter to execute them\n", stdout);
|
|
|
|
query = mu_query_new (store, err);
|
|
if (!query)
|
|
return MU_G_ERROR_CODE (err);
|
|
|
|
while (!MU_CAUGHT_SIGNAL) {
|
|
|
|
char *line;
|
|
Cmd cmd;
|
|
GSList *args;
|
|
GError *my_err;
|
|
|
|
line = my_readline (MU_PROMPT);
|
|
cmd = parse_line (line, &args, err);
|
|
g_free (line);
|
|
|
|
if (cmd == CMD_INVALID)
|
|
|
|
|
|
my_err = NULL;
|
|
if (!handle_command (cmd, store, query, args, &my_err))
|
|
g_clear_error (&my_err);
|
|
|
|
mu_str_free_list (args);
|
|
|
|
if (cmd == CMD_QUIT) {
|
|
mu_store_flush (store);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mu_query_destroy (query);
|
|
|
|
return MU_OK;
|
|
}
|