/* ** Copyright (C) 2011-2021 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #include "mu-guile-message.hh" #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" #include #pragma GCC diagnostic pop #include "mu-guile.hh" #include #include #include #include #include using namespace Mu; /* pseudo field, not in Xapian */ #define MU_GUILE_MSG_FIELD_ID_TIMESTAMP (MU_MSG_FIELD_ID_NUM + 1) /* some symbols */ static SCM SYMB_PRIO_LOW, SYMB_PRIO_NORMAL, SYMB_PRIO_HIGH; static SCM SYMB_FLAG_NEW, SYMB_FLAG_PASSED, SYMB_FLAG_REPLIED, SYMB_FLAG_SEEN, SYMB_FLAG_TRASHED, SYMB_FLAG_DRAFT, SYMB_FLAG_FLAGGED, SYMB_FLAG_SIGNED, SYMB_FLAG_ENCRYPTED, SYMB_FLAG_HAS_ATTACH, SYMB_FLAG_UNREAD, SYMB_FLAG_LIST; static SCM SYMB_CONTACT_TO, SYMB_CONTACT_CC, SYMB_CONTACT_BCC, SYMB_CONTACT_FROM; struct _MuMsgWrapper { MuMsg* _msg; gboolean _unrefme; }; typedef struct _MuMsgWrapper MuMsgWrapper; static long MSG_TAG; static gboolean mu_guile_scm_is_msg(SCM scm) { return SCM_NIMP(scm) && (long)SCM_CAR(scm) == MSG_TAG; } SCM mu_guile_msg_to_scm(MuMsg* msg) { MuMsgWrapper* msgwrap; g_return_val_if_fail(msg, SCM_UNDEFINED); msgwrap = (MuMsgWrapper*)scm_gc_malloc(sizeof(MuMsgWrapper), "msg"); msgwrap->_msg = msg; msgwrap->_unrefme = FALSE; SCM_RETURN_NEWSMOB(MSG_TAG, msgwrap); } struct _FlagData { MuFlags flags; SCM lst; }; typedef struct _FlagData FlagData; #define MU_GUILE_INITIALIZED_OR_ERROR \ do { \ if (!(mu_guile_initialized())) { \ mu_guile_error(FUNC_NAME, \ 0, \ "mu not initialized; call mu:initialize", \ SCM_UNDEFINED); \ return SCM_UNSPECIFIED; \ } \ } while (0) static void check_flag(MuFlags flag, FlagData* fdata) { SCM flag_scm; if (!(fdata->flags & flag)) return; switch (flag) { case MU_FLAG_NONE: flag_scm = SCM_UNDEFINED; break; case MU_FLAG_NEW: flag_scm = SYMB_FLAG_NEW; break; case MU_FLAG_PASSED: flag_scm = SYMB_FLAG_PASSED; break; case MU_FLAG_REPLIED: flag_scm = SYMB_FLAG_REPLIED; break; case MU_FLAG_SEEN: flag_scm = SYMB_FLAG_SEEN; break; case MU_FLAG_TRASHED: flag_scm = SYMB_FLAG_TRASHED; break; case MU_FLAG_SIGNED: flag_scm = SYMB_FLAG_SIGNED; break; case MU_FLAG_DRAFT: flag_scm = SYMB_FLAG_DRAFT; break; case MU_FLAG_FLAGGED: flag_scm = SYMB_FLAG_FLAGGED; break; case MU_FLAG_ENCRYPTED: flag_scm = SYMB_FLAG_ENCRYPTED; break; case MU_FLAG_HAS_ATTACH: flag_scm = SYMB_FLAG_HAS_ATTACH; break; case MU_FLAG_UNREAD: flag_scm = SYMB_FLAG_UNREAD; break; case MU_FLAG_LIST: flag_scm = SYMB_FLAG_LIST; break; default: flag_scm = SCM_UNDEFINED; } fdata->lst = scm_append_x(scm_list_2(fdata->lst, scm_list_1(flag_scm))); } static SCM get_flags_scm(MuMsg* msg) { FlagData fdata; fdata.flags = mu_msg_get_flags(msg); fdata.lst = SCM_EOL; mu_flags_foreach((MuFlagsForeachFunc)check_flag, &fdata); return fdata.lst; } static SCM get_prio_scm(MuMsg* msg) { switch (mu_msg_get_prio(msg)) { case MU_MSG_PRIO_LOW: return SYMB_PRIO_LOW; case MU_MSG_PRIO_NORMAL: return SYMB_PRIO_NORMAL; case MU_MSG_PRIO_HIGH: return SYMB_PRIO_HIGH; default: g_return_val_if_reached(SCM_UNDEFINED); } } static SCM msg_string_list_field(MuMsg* msg, MuMsgFieldId mfid) { SCM scmlst; const GSList* lst; lst = mu_msg_get_field_string_list(msg, mfid); for (scmlst = SCM_EOL; lst; lst = g_slist_next(lst)) { SCM item; item = scm_list_1(mu_guile_scm_from_str((const char*)lst->data)); scmlst = scm_append_x(scm_list_2(scmlst, item)); } return scmlst; } static SCM get_body(MuMsg* msg, gboolean html) { SCM data; const char* body; MuMsgOptions opts; opts = MU_MSG_OPTION_NONE; if (html) body = mu_msg_get_body_html(msg, opts); else body = mu_msg_get_body_text(msg, opts); if (body) data = mu_guile_scm_from_str(body); else data = SCM_BOOL_F; /* explicitly close the file backend, so we won't run of fds */ mu_msg_unload_msg_file(msg); return data; } SCM_DEFINE(get_field, "mu:c:get-field", 2, 0, 0, (SCM MSG, SCM FIELD), "Get the field FIELD from message MSG.\n") #define FUNC_NAME s_get_field { MuMsgWrapper* msgwrap; MuMsgFieldId mfid; msgwrap = (MuMsgWrapper*)SCM_CDR(MSG); MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT(mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT(scm_integer_p(FIELD), FIELD, SCM_ARG2, FUNC_NAME); mfid = scm_to_int(FIELD); SCM_ASSERT(mfid < MU_MSG_FIELD_ID_NUM || mfid == MU_GUILE_MSG_FIELD_ID_TIMESTAMP, FIELD, SCM_ARG2, FUNC_NAME); switch (mfid) { case MU_MSG_FIELD_ID_PRIO: return get_prio_scm(msgwrap->_msg); case MU_MSG_FIELD_ID_FLAGS: return get_flags_scm(msgwrap->_msg); case MU_MSG_FIELD_ID_BODY_HTML: return get_body(msgwrap->_msg, TRUE); case MU_MSG_FIELD_ID_BODY_TEXT: return get_body(msgwrap->_msg, FALSE); /* our pseudo-field; we get it from the message file */ case MU_GUILE_MSG_FIELD_ID_TIMESTAMP: return scm_from_uint((unsigned)mu_msg_get_timestamp(msgwrap->_msg)); default: break; } switch (mu_msg_field_type(mfid)) { case MU_MSG_FIELD_TYPE_STRING: return mu_guile_scm_from_str(mu_msg_get_field_string(msgwrap->_msg, mfid)); case MU_MSG_FIELD_TYPE_BYTESIZE: case MU_MSG_FIELD_TYPE_TIME_T: return scm_from_uint(mu_msg_get_field_numeric(msgwrap->_msg, mfid)); case MU_MSG_FIELD_TYPE_INT: return scm_from_int(mu_msg_get_field_numeric(msgwrap->_msg, mfid)); case MU_MSG_FIELD_TYPE_STRING_LIST: return msg_string_list_field(msgwrap->_msg, mfid); default: SCM_ASSERT(0, FIELD, SCM_ARG2, FUNC_NAME); } } #undef FUNC_NAME struct _EachContactData { SCM lst; MuMsgContactType ctype; }; typedef struct _EachContactData EachContactData; static void contacts_to_list(MuMsgContact* contact, EachContactData* ecdata) { SCM item; if (ecdata->ctype != MU_MSG_CONTACT_TYPE_ALL && mu_msg_contact_type(contact) != ecdata->ctype) return; item = scm_list_1(scm_cons(mu_guile_scm_from_str(mu_msg_contact_name(contact)), mu_guile_scm_from_str(mu_msg_contact_email(contact)))); ecdata->lst = scm_append_x(scm_list_2(ecdata->lst, item)); } SCM_DEFINE(get_contacts, "mu:c:get-contacts", 2, 0, 0, (SCM MSG, SCM CONTACT_TYPE), "Get a list of contact information pairs.\n") #define FUNC_NAME s_get_contacts { MuMsgWrapper* msgwrap; EachContactData ecdata; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT(mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT(scm_symbol_p(CONTACT_TYPE) || scm_is_bool(CONTACT_TYPE), CONTACT_TYPE, SCM_ARG2, FUNC_NAME); if (CONTACT_TYPE == SCM_BOOL_F) return SCM_UNSPECIFIED; /* nothing to do */ else if (CONTACT_TYPE == SCM_BOOL_T) ecdata.ctype = MU_MSG_CONTACT_TYPE_ALL; else { if (scm_is_eq(CONTACT_TYPE, SYMB_CONTACT_TO)) ecdata.ctype = MU_MSG_CONTACT_TYPE_TO; else if (scm_is_eq(CONTACT_TYPE, SYMB_CONTACT_CC)) ecdata.ctype = MU_MSG_CONTACT_TYPE_CC; else if (scm_is_eq(CONTACT_TYPE, SYMB_CONTACT_BCC)) ecdata.ctype = MU_MSG_CONTACT_TYPE_BCC; else if (scm_is_eq(CONTACT_TYPE, SYMB_CONTACT_FROM)) ecdata.ctype = MU_MSG_CONTACT_TYPE_FROM; else { mu_guile_error(FUNC_NAME, 0, "invalid contact type", SCM_UNDEFINED); return SCM_UNSPECIFIED; } } ecdata.lst = SCM_EOL; msgwrap = (MuMsgWrapper*)SCM_CDR(MSG); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" mu_msg_contact_foreach(msgwrap->_msg, (MuMsgContactForeachFunc)contacts_to_list, &ecdata); #pragma GCC diagnostic pop /* explicitly close the file backend, so we won't run out of fds */ mu_msg_unload_msg_file(msgwrap->_msg); return ecdata.lst; } #undef FUNC_NAME struct _AttInfo { SCM attlist; gboolean attachments_only; }; typedef struct _AttInfo AttInfo; static void each_part(MuMsg* msg, MuMsgPart* part, AttInfo* attinfo) { char *mime_type, *filename; SCM elm; if (!part->type) return; if (attinfo->attachments_only && !mu_msg_part_maybe_attachment(part)) return; mime_type = g_strdup_printf("%s/%s", part->type, part->subtype); filename = mu_msg_part_get_filename(part, FALSE); elm = scm_list_5( /* msg */ mu_guile_scm_from_str(mu_msg_get_path(msg)), /* index */ scm_from_uint(part->index), /* filename or #f */ filename ? mu_guile_scm_from_str(filename) : SCM_BOOL_F, /* mime-type */ mime_type ? mu_guile_scm_from_str(mime_type) : SCM_BOOL_F, /* size */ part->size > 0 ? scm_from_uint(part->size) : SCM_BOOL_F); g_free(mime_type); g_free(filename); attinfo->attlist = scm_cons(elm, attinfo->attlist); } SCM_DEFINE(get_parts, "mu:c:get-parts", 1, 1, 0, (SCM MSG, SCM ATTS_ONLY), "Get the list of mime-parts for MSG. If ATTS_ONLY is #t, only" "get parts that are (look like) attachments. The resulting list has " "elements which are list of the form (index name mime-type size).\n") #define FUNC_NAME s_get_parts { MuMsgWrapper* msgwrap; AttInfo attinfo; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT(mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT(scm_is_bool(ATTS_ONLY), ATTS_ONLY, SCM_ARG2, FUNC_NAME); attinfo.attlist = SCM_EOL; /* empty list */ attinfo.attachments_only = ATTS_ONLY == SCM_BOOL_T ? TRUE : FALSE; msgwrap = (MuMsgWrapper*)SCM_CDR(MSG); mu_msg_part_foreach(msgwrap->_msg, MU_MSG_OPTION_NONE, (MuMsgPartForeachFunc)each_part, &attinfo); /* explicitly close the file backend, so we won't run of fds */ mu_msg_unload_msg_file(msgwrap->_msg); return attinfo.attlist; } #undef FUNC_NAME SCM_DEFINE(get_header, "mu:c:get-header", 2, 0, 0, (SCM MSG, SCM HEADER), "Get an arbitrary HEADER from MSG.\n") #define FUNC_NAME s_get_header { MuMsgWrapper* msgwrap; char* header; SCM val; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT(mu_guile_scm_is_msg(MSG), MSG, SCM_ARG1, FUNC_NAME); SCM_ASSERT(scm_is_string(HEADER) || HEADER == SCM_UNDEFINED, HEADER, SCM_ARG2, FUNC_NAME); msgwrap = (MuMsgWrapper*)SCM_CDR(MSG); header = scm_to_utf8_string(HEADER); val = mu_guile_scm_from_str(mu_msg_get_header(msgwrap->_msg, header)); free(header); /* explicitly close the file backend, so we won't run of fds */ mu_msg_unload_msg_file(msgwrap->_msg); return val; } #undef FUNC_NAME static Mu::Option get_query_results(Mu::Store& store, const char* expr, int maxnum) { Mu::Query query(store); return query.run(expr, MU_MSG_FIELD_ID_NONE, Mu::QueryFlags::None, maxnum); } SCM_DEFINE(for_each_message, "mu:c:for-each-message", 3, 0, 0, (SCM FUNC, SCM EXPR, SCM MAXNUM), "Call FUNC for each msg in the message store matching EXPR. EXPR is" "either a string containing a mu search expression or a boolean; in the former " "case, limit the messages to only those matching the expression, in the " "latter case, match /all/ messages if the EXPR equals #t, and match " "none if EXPR equals #f.") #define FUNC_NAME s_for_each_message { char* expr{}; MU_GUILE_INITIALIZED_OR_ERROR; SCM_ASSERT(scm_procedure_p(FUNC), FUNC, SCM_ARG1, FUNC_NAME); SCM_ASSERT(scm_is_bool(EXPR) || scm_is_string(EXPR), EXPR, SCM_ARG2, FUNC_NAME); SCM_ASSERT(scm_is_integer(MAXNUM), MAXNUM, SCM_ARG3, FUNC_NAME); if (EXPR == SCM_BOOL_F) return SCM_UNSPECIFIED; /* nothing to do */ if (EXPR == SCM_BOOL_T) expr = strdup(""); /* note, "" matches *all* messages */ else expr = scm_to_utf8_string(EXPR); const auto res{get_query_results(mu_guile_store(), expr, scm_to_int(MAXNUM))}; free(expr); if (!res) return SCM_UNSPECIFIED; for (auto&& mi : *res) { auto msg{mi.floating_msg()}; if (msg) { auto msgsmob{mu_guile_msg_to_scm(mu_msg_ref(msg))}; scm_call_1(FUNC, msgsmob); } } return SCM_UNSPECIFIED; } #undef FUNC_NAME static SCM register_symbol(const char* name) { SCM scm; scm = scm_from_utf8_symbol(name); scm_c_define(name, scm); scm_c_export(name, NULL); return scm; } static void define_symbols(void) { SYMB_CONTACT_TO = register_symbol("mu:contact:to"); SYMB_CONTACT_CC = register_symbol("mu:contact:cc"); SYMB_CONTACT_FROM = register_symbol("mu:contact:from"); SYMB_CONTACT_BCC = register_symbol("mu:contact:bcc"); SYMB_PRIO_LOW = register_symbol("mu:prio:low"); SYMB_PRIO_NORMAL = register_symbol("mu:prio:normal"); SYMB_PRIO_HIGH = register_symbol("mu:prio:high"); SYMB_FLAG_NEW = register_symbol("mu:flag:new"); SYMB_FLAG_PASSED = register_symbol("mu:flag:passed"); SYMB_FLAG_REPLIED = register_symbol("mu:flag:replied"); SYMB_FLAG_SEEN = register_symbol("mu:flag:seen"); SYMB_FLAG_TRASHED = register_symbol("mu:flag:trashed"); SYMB_FLAG_DRAFT = register_symbol("mu:flag:draft"); SYMB_FLAG_FLAGGED = register_symbol("mu:flag:flagged"); SYMB_FLAG_SIGNED = register_symbol("mu:flag:signed"); SYMB_FLAG_ENCRYPTED = register_symbol("mu:flag:encrypted"); SYMB_FLAG_HAS_ATTACH = register_symbol("mu:flag:has-attach"); SYMB_FLAG_UNREAD = register_symbol("mu:flag:unread"); SYMB_FLAG_LIST = register_symbol("mu:flag:list"); } static struct { const char* name; unsigned val; } VAR_PAIRS[] = { {"mu:field:bcc", MU_MSG_FIELD_ID_BCC}, {"mu:field:body-html", MU_MSG_FIELD_ID_BODY_HTML}, {"mu:field:body-txt", MU_MSG_FIELD_ID_BODY_TEXT}, {"mu:field:cc", MU_MSG_FIELD_ID_CC}, {"mu:field:date", MU_MSG_FIELD_ID_DATE}, {"mu:field:flags", MU_MSG_FIELD_ID_FLAGS}, {"mu:field:from", MU_MSG_FIELD_ID_FROM}, {"mu:field:maildir", MU_MSG_FIELD_ID_MAILDIR}, {"mu:field:message-id", MU_MSG_FIELD_ID_MSGID}, {"mu:field:path", MU_MSG_FIELD_ID_PATH}, {"mu:field:prio", MU_MSG_FIELD_ID_PRIO}, {"mu:field:refs", MU_MSG_FIELD_ID_REFS}, {"mu:field:size", MU_MSG_FIELD_ID_SIZE}, {"mu:field:subject", MU_MSG_FIELD_ID_SUBJECT}, {"mu:field:tags", MU_MSG_FIELD_ID_TAGS}, {"mu:field:to", MU_MSG_FIELD_ID_TO}, /* non-Xapian field: timestamp */ {"mu:field:timestamp", MU_GUILE_MSG_FIELD_ID_TIMESTAMP}}; static void define_vars(void) { unsigned u; for (u = 0; u != G_N_ELEMENTS(VAR_PAIRS); ++u) { scm_c_define(VAR_PAIRS[u].name, scm_from_uint(VAR_PAIRS[u].val)); scm_c_export(VAR_PAIRS[u].name, NULL); } } static SCM msg_mark(SCM msg_smob) { MuMsgWrapper* msgwrap; msgwrap = (MuMsgWrapper*)SCM_CDR(msg_smob); msgwrap->_unrefme = TRUE; return SCM_UNSPECIFIED; } static size_t msg_free(SCM msg_smob) { MuMsgWrapper* msgwrap; msgwrap = (MuMsgWrapper*)SCM_CDR(msg_smob); if (msgwrap->_unrefme) mu_msg_unref(msgwrap->_msg); return sizeof(MuMsgWrapper); } static int msg_print(SCM msg_smob, SCM port, scm_print_state* pstate) { MuMsgWrapper* msgwrap; msgwrap = (MuMsgWrapper*)SCM_CDR(msg_smob); scm_puts("#_msg), port); scm_puts(">", port); return 1; } void* mu_guile_message_init(void* data) { MSG_TAG = scm_make_smob_type("msg", sizeof(MuMsgWrapper)); scm_set_smob_mark(MSG_TAG, msg_mark); scm_set_smob_free(MSG_TAG, msg_free); scm_set_smob_print(MSG_TAG, msg_print); define_vars(); define_symbols(); #ifndef SCM_MAGIC_SNARFER #include "mu-guile-message.x" #endif /*SCM_MAGIC_SNARFER*/ return NULL; }