Merge branch 'wip/djcb/more-scm'
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** Copyright (C) 2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
** Copyright (C) 2023-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
**
|
**
|
||||||
** This program is free software; you can redistribute it and/or modify it
|
** 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
|
** under the terms of the GNU General Public License as published by the
|
||||||
@ -39,18 +39,18 @@ namespace Mu {
|
|||||||
|
|
||||||
struct Property {
|
struct Property {
|
||||||
enum struct Id {
|
enum struct Id {
|
||||||
BatchSize, /**< Xapian batch-size */
|
BatchSize, /**< Xapian batch-size */
|
||||||
Contacts, /**< Cache of contact information */
|
Contacts, /**< Cache of contact information */
|
||||||
Created, /**< Time of creation */
|
Created, /**< Time of creation */
|
||||||
IgnoredAddresses,/**< Email addresses ignored for the contacts-cache */
|
IgnoredAddresses, /**< Email addresses ignored for the contacts-cache */
|
||||||
LastChange, /**< Time of last change */
|
LastChange, /**< Time of last change */
|
||||||
LastIndex, /**< Time of last index */
|
LastIndex, /**< Time of last index */
|
||||||
MaxMessageSize, /**< Maximum message size (in bytes) */
|
MaxMessageSize, /**< Maximum message size (in bytes) */
|
||||||
PersonalAddresses, /**< List of personal e-mail addresses */
|
PersonalAddresses, /**< List of personal e-mail addresses */
|
||||||
RootMaildir, /**< Root maildir path */
|
RootMaildir, /**< Root maildir path */
|
||||||
SchemaVersion, /**< Xapian DB schema version */
|
SchemaVersion, /**< Xapian DB schema version */
|
||||||
SupportNgrams, /**< Support ngrams for indexing & querying
|
SupportNgrams, /**< Support ngrams for indexing & querying
|
||||||
* for e.g. CJK languages */
|
* for e.g. CJK languages */
|
||||||
/* <private> */
|
/* <private> */
|
||||||
_count_ /* Number of Ids */
|
_count_ /* Number of Ids */
|
||||||
};
|
};
|
||||||
@ -65,6 +65,7 @@ struct Property {
|
|||||||
Configurable = 1 << 1, /**< A user-configurable parameter; name
|
Configurable = 1 << 1, /**< A user-configurable parameter; name
|
||||||
* starts with 'conf-' */
|
* starts with 'conf-' */
|
||||||
Internal = 1 << 2, /**< Mu-internal field */
|
Internal = 1 << 2, /**< Mu-internal field */
|
||||||
|
Runtime = 1 << 3, /**< May change at runtime */
|
||||||
};
|
};
|
||||||
enum struct Type {
|
enum struct Type {
|
||||||
Boolean, /**< Some boolean value */
|
Boolean, /**< Some boolean value */
|
||||||
@ -132,7 +133,7 @@ public:
|
|||||||
{
|
{
|
||||||
Id::LastChange,
|
Id::LastChange,
|
||||||
Type::Timestamp,
|
Type::Timestamp,
|
||||||
Flags::ReadOnly,
|
Flags::ReadOnly | Flags::Runtime,
|
||||||
MetadataIface::last_change_key,
|
MetadataIface::last_change_key,
|
||||||
{},
|
{},
|
||||||
"Time when last change occurred"
|
"Time when last change occurred"
|
||||||
@ -140,7 +141,7 @@ public:
|
|||||||
{
|
{
|
||||||
Id::LastIndex,
|
Id::LastIndex,
|
||||||
Type::Timestamp,
|
Type::Timestamp,
|
||||||
Flags::ReadOnly,
|
Flags::ReadOnly | Flags::Runtime,
|
||||||
"last-index",
|
"last-index",
|
||||||
{},
|
{},
|
||||||
"Time when last indexing operation was completed"
|
"Time when last indexing operation was completed"
|
||||||
@ -222,6 +223,47 @@ public:
|
|||||||
return Nothing;
|
return Nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string-value for prop.
|
||||||
|
*
|
||||||
|
* For internal use
|
||||||
|
*
|
||||||
|
* @param prop some property
|
||||||
|
*
|
||||||
|
* @return a string
|
||||||
|
*/
|
||||||
|
std::string get_str(const Property& prop) const {
|
||||||
|
const auto str = cstore_.metadata(std::string{prop.name});
|
||||||
|
return str.empty() ? std::string{prop.default_val} : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the property value decoded based on the type
|
||||||
|
*
|
||||||
|
* @param prop_id a property id
|
||||||
|
*
|
||||||
|
* @return the value or Nothing
|
||||||
|
*/
|
||||||
|
template<Type type>
|
||||||
|
static constexpr auto decode(const std::string& str) {
|
||||||
|
if constexpr (type == Type::Number)
|
||||||
|
return static_cast<size_t>(str.empty() ? 0 : std::atoll(str.c_str()));
|
||||||
|
if constexpr (type == Type::Boolean)
|
||||||
|
return static_cast<size_t>(str.empty() ? false :
|
||||||
|
std::atol(str.c_str()) != 0);
|
||||||
|
else if constexpr (type == Type::Timestamp)
|
||||||
|
return static_cast<time_t>(str.empty() ? 0 : std::atoll(str.c_str()));
|
||||||
|
else if constexpr (type == Type::Path || type == Type::String)
|
||||||
|
return str;
|
||||||
|
else if constexpr (type == Type::StringList)
|
||||||
|
return split(str, SepaChar1);
|
||||||
|
throw std::logic_error("invalid type");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the property value of the correct type
|
* Get the property value of the correct type
|
||||||
*
|
*
|
||||||
@ -231,24 +273,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
template<Id ID>
|
template<Id ID>
|
||||||
auto get() const {
|
auto get() const {
|
||||||
constexpr auto&& prop{property<ID>()};
|
constexpr auto& prop{property<ID>()};
|
||||||
const auto str = std::invoke([&]()->std::string {
|
return decode<prop.type>(get_str(prop));
|
||||||
const auto str = cstore_.metadata(std::string{prop.name});
|
|
||||||
return str.empty() ? std::string{prop.default_val} : str;
|
|
||||||
});
|
|
||||||
if constexpr (prop.type == Type::Number)
|
|
||||||
return static_cast<size_t>(str.empty() ? 0 : std::atoll(str.c_str()));
|
|
||||||
if constexpr (prop.type == Type::Boolean)
|
|
||||||
return static_cast<size_t>(str.empty() ? false :
|
|
||||||
std::atol(str.c_str()) != 0);
|
|
||||||
else if constexpr (prop.type == Type::Timestamp)
|
|
||||||
return static_cast<time_t>(str.empty() ? 0 : std::atoll(str.c_str()));
|
|
||||||
else if constexpr (prop.type == Type::Path || prop.type == Type::String)
|
|
||||||
return str;
|
|
||||||
else if constexpr (prop.type == Type::StringList)
|
|
||||||
return split(str, SepaChar1);
|
|
||||||
|
|
||||||
throw std::logic_error("invalid prop " + std::string{prop.name});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -38,6 +38,57 @@ to_store(SCM scm, const char *func, int pos)
|
|||||||
return *reinterpret_cast<Store*>(scm_foreign_object_ref(scm, 0));
|
return *reinterpret_cast<Store*>(scm_foreign_object_ref(scm, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SCM
|
||||||
|
subr_cc_store_alist(SCM store_scm) try {
|
||||||
|
constexpr auto func{"cc-store-alist"};
|
||||||
|
|
||||||
|
SCM alist{SCM_EOL};
|
||||||
|
const auto& conf{to_store(store_scm, func, 1).config()};
|
||||||
|
|
||||||
|
using MuConfig = Mu::Config;
|
||||||
|
using Type = MuConfig::Type;
|
||||||
|
|
||||||
|
for (const auto& prop: Mu::Config::properties) {
|
||||||
|
|
||||||
|
// don't expose internal values & values that may change during
|
||||||
|
// runtime
|
||||||
|
if (any_of(prop.flags &
|
||||||
|
(MuConfig::Flags::Internal | MuConfig::Flags::Runtime)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto str{conf.get_str(prop)};
|
||||||
|
if (str.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto name{make_symbol(prop.name)};
|
||||||
|
const auto val = std::invoke([&]() {
|
||||||
|
switch (prop.type) {
|
||||||
|
case Type::Number:
|
||||||
|
return to_scm(MuConfig::decode<Type::Number>(str));
|
||||||
|
case Type::Boolean:
|
||||||
|
return to_scm(MuConfig::decode<Type::Boolean>(str));
|
||||||
|
case Type::Timestamp:
|
||||||
|
return to_scm(MuConfig::decode<Type::Timestamp>(str));
|
||||||
|
case Type::Path:
|
||||||
|
return to_scm(MuConfig::decode<Type::Path>(str));
|
||||||
|
case Type::String:
|
||||||
|
return to_scm(MuConfig::decode<Type::String>(str));
|
||||||
|
case Type::StringList:
|
||||||
|
return to_scm(MuConfig::decode<Type::StringList>(str));
|
||||||
|
default:
|
||||||
|
throw ScmError{ScmError::Id::WrongType, func, 1, store_scm, "store"};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
alist = scm_acons(name, val, alist);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scm_reverse_x(alist, SCM_EOL);
|
||||||
|
|
||||||
|
} catch (const ScmError& err) {
|
||||||
|
err.throw_scm();
|
||||||
|
}
|
||||||
|
|
||||||
static SCM
|
static SCM
|
||||||
subr_cc_store_mcount(SCM store_scm) try {
|
subr_cc_store_mcount(SCM store_scm) try {
|
||||||
return to_scm(to_store(store_scm, "cc-store-mcount", 1).size());
|
return to_scm(to_store(store_scm, "cc-store-mcount", 1).size());
|
||||||
@ -139,6 +190,9 @@ init_subrs()
|
|||||||
reinterpret_cast<scm_t_subr>(subr_cc_store_mcount));
|
reinterpret_cast<scm_t_subr>(subr_cc_store_mcount));
|
||||||
scm_c_define_gsubr("cc-store-cfind", 5/*req*/, 0/*opt*/, 0/*rst*/,
|
scm_c_define_gsubr("cc-store-cfind", 5/*req*/, 0/*opt*/, 0/*rst*/,
|
||||||
reinterpret_cast<scm_t_subr>(subr_cc_store_cfind));
|
reinterpret_cast<scm_t_subr>(subr_cc_store_cfind));
|
||||||
|
scm_c_define_gsubr("cc-store-alist", 1/*req*/, 0/*opt*/, 0/*rst*/,
|
||||||
|
reinterpret_cast<scm_t_subr>(subr_cc_store_alist));
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +210,7 @@ Mu::Scm::init_store(const Store& store)
|
|||||||
|
|
||||||
default_store = scm_make_foreign_object_1(
|
default_store = scm_make_foreign_object_1(
|
||||||
store_type, const_cast<Store*>(&store));
|
store_type, const_cast<Store*>(&store));
|
||||||
scm_c_define("%default-store-object", default_store);
|
scm_c_define("%cc-default-store", default_store);
|
||||||
|
|
||||||
init_subrs();
|
init_subrs();
|
||||||
|
|
||||||
@ -167,8 +221,8 @@ Mu::Scm::init_store(const Store& store)
|
|||||||
SCM
|
SCM
|
||||||
Mu::Scm::to_scm(const Contact& contact)
|
Mu::Scm::to_scm(const Contact& contact)
|
||||||
{
|
{
|
||||||
static SCM email{scm_from_utf8_symbol("email")};
|
static SCM email{make_symbol("email")};
|
||||||
static SCM name{scm_from_utf8_symbol("name")};
|
static SCM name{make_symbol("name")};
|
||||||
|
|
||||||
SCM alist = scm_acons(email, to_scm(contact.email), SCM_EOL);
|
SCM alist = scm_acons(email, to_scm(contact.email), SCM_EOL);
|
||||||
if (!contact.name.empty())
|
if (!contact.name.empty())
|
||||||
|
|||||||
@ -3,14 +3,18 @@
|
|||||||
(use-modules (mu) (srfi srfi-64)
|
(use-modules (mu) (srfi srfi-64)
|
||||||
(ice-9 textual-ports))
|
(ice-9 textual-ports))
|
||||||
|
|
||||||
(define (test-basic)
|
(define (test-store)
|
||||||
(test-begin "test-basic")
|
(test-begin "test-store")
|
||||||
|
|
||||||
(test-equal "mcount" 19 (mcount))
|
(test-equal "mcount" 19 (mcount))
|
||||||
(test-equal "cfind" 29 (length (cfind "")))
|
(test-equal "cfind" 29 (length (cfind "")))
|
||||||
(test-equal "mfind" 19 (length (mfind "")))
|
(test-equal "mfind" 19 (length (mfind "")))
|
||||||
|
|
||||||
(test-end "test-basic"))
|
(let ((info (store->alist)))
|
||||||
|
(test-equal 50000 (assoc-ref info 'batch-size))
|
||||||
|
(test-equal 100000000 (assoc-ref info 'max-message-size)))
|
||||||
|
|
||||||
|
(test-end "test-store"))
|
||||||
|
|
||||||
(define (test-basic-mfind)
|
(define (test-basic-mfind)
|
||||||
|
|
||||||
@ -33,10 +37,8 @@
|
|||||||
(let ((recip (car (to msg))))
|
(let ((recip (car (to msg))))
|
||||||
(test-equal "Bilbo Baggins" (assoc-ref recip 'name))
|
(test-equal "Bilbo Baggins" (assoc-ref recip 'name))
|
||||||
(test-equal "bilbo@anotherexample.com" (assoc-ref recip 'email)))
|
(test-equal "bilbo@anotherexample.com" (assoc-ref recip 'email)))
|
||||||
|
|
||||||
;; no date
|
;; no date
|
||||||
(test-assert (not (date msg)))
|
(test-assert (not (date msg)))
|
||||||
|
|
||||||
;; flags
|
;; flags
|
||||||
(test-equal '(unread) (flags msg))
|
(test-equal '(unread) (flags msg))
|
||||||
(test-assert (unread? msg))
|
(test-assert (unread? msg))
|
||||||
@ -80,13 +82,21 @@
|
|||||||
(references msg))
|
(references msg))
|
||||||
(test-equal "439C1136.90504@euler.org" (thread-id msg)))
|
(test-equal "439C1136.90504@euler.org" (thread-id msg)))
|
||||||
|
|
||||||
(let ((msg (car (mfind "subject:\"gcc include search order\""))))
|
(let* ((msg (car (mfind "subject:\"gcc include search order\"")))
|
||||||
|
(alist (message->alist msg)))
|
||||||
(test-equal "gcc include search order" (subject msg))
|
(test-equal "gcc include search order" (subject msg))
|
||||||
(test-equal "klub" (header msg "precedence"))
|
(test-equal "klub" (header msg "precedence"))
|
||||||
(test-equal "gcc-help.gcc.gnu.org" (mailing-list msg))
|
(test-equal "gcc-help.gcc.gnu.org" (mailing-list msg))
|
||||||
(test-equal #f (references msg))
|
(test-equal #f (references msg))
|
||||||
(test-equal "3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com" (message-id msg))
|
(test-equal "3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com" (message-id msg))
|
||||||
(test-equal "3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com" (thread-id msg)))
|
(test-equal "3BE9E6535E3029448670913581E7A1A20D852173@emss35m06.us.lmco.com" (thread-id msg))
|
||||||
|
|
||||||
|
;; alist
|
||||||
|
(test-equal "gcc include search order" (assoc-ref alist 'subject))
|
||||||
|
(test-equal 'normal (assoc-ref alist 'priority))
|
||||||
|
(test-equal '((email . "anon@example.com") (name . "Mickey Mouse"))
|
||||||
|
(car (assoc-ref alist 'from))))
|
||||||
|
|
||||||
(test-end "test-message-more"))
|
(test-end "test-message-more"))
|
||||||
|
|
||||||
|
|
||||||
@ -121,10 +131,14 @@
|
|||||||
((index . 2) (content-type . "image/jpeg") (size . 21566) (filename . "custer.jpg")))
|
((index . 2) (content-type . "image/jpeg") (size . 21566) (filename . "custer.jpg")))
|
||||||
(map (lambda (part) (mime-part->alist part)) (mime-parts msg)))
|
(map (lambda (part) (mime-part->alist part)) (mime-parts msg)))
|
||||||
|
|
||||||
|
(test-equal "mime-part-0" (filename (list-ref (mime-parts msg) 0)))
|
||||||
|
(test-equal "sittingbull.jpg" (filename (list-ref (mime-parts msg) 1)))
|
||||||
|
(test-equal "custer.jpg" (filename (list-ref (mime-parts msg) 2)))
|
||||||
|
|
||||||
(let* ((part (list-ref (mime-parts msg) 1))
|
(let* ((part (list-ref (mime-parts msg) 1))
|
||||||
(alist (mime-part->alist part))
|
(alist (mime-part->alist part))
|
||||||
(fname (format #f "~a/~a" tmpdir (assoc-ref alist 'filename))))
|
(fname (format #f "~a/~a" tmpdir (assoc-ref alist 'filename))))
|
||||||
(write-to-file part #:filename fname)
|
(write-to-file part #:path fname)
|
||||||
(test-assert (access? fname R_OK))
|
(test-assert (access? fname R_OK))
|
||||||
;; note, the 23881 is the _encoded_ size.
|
;; note, the 23881 is the _encoded_ size.
|
||||||
(test-equal 17674 (stat:size (stat fname))))
|
(test-equal 17674 (stat:size (stat fname))))
|
||||||
@ -167,7 +181,7 @@
|
|||||||
(test-with-runner runner
|
(test-with-runner runner
|
||||||
(test-begin "mu-scm-tests")
|
(test-begin "mu-scm-tests")
|
||||||
|
|
||||||
(test-basic)
|
(test-store)
|
||||||
(test-basic-mfind)
|
(test-basic-mfind)
|
||||||
(test-mfind)
|
(test-mfind)
|
||||||
(test-message-full)
|
(test-message-full)
|
||||||
|
|||||||
@ -221,6 +221,13 @@ namespace Mu::Scm {
|
|||||||
return scm_from_utf8_stringn(val.data(), val.size());
|
return scm_from_utf8_stringn(val.data(), val.size());
|
||||||
else if constexpr (is_char_array_v<Type>|| std::is_same_v<Type, const char*>)
|
else if constexpr (is_char_array_v<Type>|| std::is_same_v<Type, const char*>)
|
||||||
return scm_from_utf8_string(val);
|
return scm_from_utf8_string(val);
|
||||||
|
else if constexpr (std::is_same_v<Type, std::vector<std::string>>) {
|
||||||
|
SCM lst{SCM_EOL};
|
||||||
|
for (const auto& s: val)
|
||||||
|
lst = scm_append_x(scm_list_2(lst,
|
||||||
|
scm_list_1(to_scm(s))));
|
||||||
|
return lst;
|
||||||
|
}
|
||||||
else if constexpr (std::is_same_v<Type, bool>)
|
else if constexpr (std::is_same_v<Type, bool>)
|
||||||
return scm_from_bool(val);
|
return scm_from_bool(val);
|
||||||
else if constexpr (std::is_same_v<Type, size_t>)
|
else if constexpr (std::is_same_v<Type, size_t>)
|
||||||
|
|||||||
291
scm/mu-scm.scm
291
scm/mu-scm.scm
@ -28,14 +28,17 @@
|
|||||||
<mime-part>
|
<mime-part>
|
||||||
mime-part->alist
|
mime-part->alist
|
||||||
make-port
|
make-port
|
||||||
|
filename
|
||||||
write-to-file
|
write-to-file
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Message
|
;; Message
|
||||||
<message>
|
<message>
|
||||||
make-message
|
make-message
|
||||||
|
|
||||||
|
message->alist
|
||||||
|
|
||||||
date
|
date
|
||||||
last-change
|
changed
|
||||||
|
|
||||||
message-id
|
message-id
|
||||||
path
|
path
|
||||||
@ -86,6 +89,7 @@
|
|||||||
mfind
|
mfind
|
||||||
mcount
|
mcount
|
||||||
cfind
|
cfind
|
||||||
|
store->alist
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Other
|
;; Other
|
||||||
@ -100,6 +104,17 @@
|
|||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;; Helpers
|
||||||
|
|
||||||
|
(define (set-documentation! symbol docstring)
|
||||||
|
"Set the docstring for symbol in current module to docstring.
|
||||||
|
This is useful for symbols that do not support docstrings directly, such
|
||||||
|
as (define foo 123)."
|
||||||
|
;; https://git.wolfsden.cz/guile-wolfsden/tree/wolfsden/documentation.scm
|
||||||
|
(set-object-property! (module-ref (current-module) symbol)
|
||||||
|
'documentation docstring))
|
||||||
|
|
||||||
;; some helpers for dealing with plists / alists
|
;; some helpers for dealing with plists / alists
|
||||||
(define (plist-for-each func plist)
|
(define (plist-for-each func plist)
|
||||||
"Call FUNC for each key/value in the PLIST.
|
"Call FUNC for each key/value in the PLIST.
|
||||||
@ -129,57 +144,79 @@ If not found, return #f."
|
|||||||
(string->symbol (string-drop name 1))
|
(string->symbol (string-drop name 1))
|
||||||
sym)))
|
sym)))
|
||||||
|
|
||||||
|
(define (emacs-time->epoch-secs lst)
|
||||||
|
"Convert emacs-style timestamp LST to a number of seconds since epoch.
|
||||||
|
If LST is #f, return #f."
|
||||||
|
(if lst
|
||||||
|
(+ (ash (car lst) 16) (cadr lst))
|
||||||
|
#f))
|
||||||
|
|
||||||
(define (plist->alist plist)
|
(define (plist->alist plist)
|
||||||
"Convert a plist into an alist."
|
"Convert a plist into an alist.
|
||||||
|
This is specific for message plists."
|
||||||
(let ((alist '()))
|
(let ((alist '()))
|
||||||
(plist-for-each
|
(plist-for-each
|
||||||
(lambda (k v)
|
(lambda (k v)
|
||||||
(set! alist
|
(let ((key (decolonize-symbol k)))
|
||||||
(append! alist
|
(set! alist
|
||||||
(list (cons (decolonize-symbol k)
|
(append! alist
|
||||||
v)))))
|
(list (cons key
|
||||||
|
(cond
|
||||||
|
((member key '(from to cc bcc))
|
||||||
|
(map plist->alist v))
|
||||||
|
((member key '(date changed))
|
||||||
|
(emacs-time->epoch-secs v))
|
||||||
|
(else v))))))))
|
||||||
plist)
|
plist)
|
||||||
alist))
|
alist))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
;; MIME-parts
|
;; MIME-parts
|
||||||
;;
|
|
||||||
;; A <mime-object> has two slots:
|
|
||||||
;; partobj --> wraps a GMimePart* as a "foreign object"
|
|
||||||
;; alist --> alist with information about some MIME-part
|
|
||||||
(define-class <mime-part> ()
|
(define-class <mime-part> ()
|
||||||
(mimepart #:init-value #f #:init-keyword #:mimepart)
|
(cc-mimepart #:init-value #f #:init-keyword #:mimepart #:getter cc-mimepart)
|
||||||
(alist #:init-value #f #:init-keyword #:alist #:getter mime-part->alist))
|
(alist #:init-value #f #:init-keyword #:alist #:getter mime-part->alist))
|
||||||
|
|
||||||
|
(set-documentation!
|
||||||
|
'<mime-part>
|
||||||
|
"A <mime-part> represents the information about a message's MIME-part.
|
||||||
|
It has a few slots:
|
||||||
|
- cc-mimepart: a 'foreign object' wrapping a GMimePart*
|
||||||
|
- alist: the association-list representation of the MIME-part.")
|
||||||
|
|
||||||
(define* (make-port mime-part #:key (content-only? #t) (decode? #t))
|
(define* (make-port mime-part #:key (content-only? #t) (decode? #t))
|
||||||
"Create a read port for MIME-PART.
|
"Create a read port for MIME-PART.
|
||||||
If CONTENT-ONLY? is #t, only include the contents, not headers.
|
If CONTENT-ONLY? is #t, only include the contents, not headers.
|
||||||
If DECODE? is #t, decode the content (from e.g., base64); in that case,
|
If DECODE? is #t, decode the content (from e.g., base64); in that case,
|
||||||
CONTENT-ONLY? is implied to be #t."
|
CONTENT-ONLY? is implied to be #t."
|
||||||
(cc-mime-make-stream-port (slot-ref mime-part 'mimepart) content-only? decode?))
|
(cc-mime-make-stream-port (cc-mimepart mime-part) content-only? decode?))
|
||||||
|
|
||||||
(define* (make-output-file mime-part #:key (filename #f) (overwrite? #f))
|
(define-method (filename (mime-part <mime-part>))
|
||||||
|
"Determine the file-name for MIME-part.
|
||||||
|
Either the 'filename' field in the mime-part and if that does not exist, use
|
||||||
|
'mime-part-<index>' with <index> being the number of the mime-part."
|
||||||
|
(let ((alist (mime-part->alist mime-part)))
|
||||||
|
(or (assoc-ref alist 'filename)
|
||||||
|
(format #f "mime-part-~d" (assoc-ref alist 'index)))))
|
||||||
|
|
||||||
|
(define* (make-output-file mime-part #:key (path #f) (overwrite? #f))
|
||||||
"Create a port for the file to write MIME-PART to.
|
"Create a port for the file to write MIME-PART to.
|
||||||
|
|
||||||
FILENAME is the path to the file name. If not specified, use the 'filename'
|
PATH is file-name or path to the file name. If not specified, use the 'filename'
|
||||||
field in the mime-part and if that does not exist, use 'mime-part-<index>' with
|
field in the mime-part and if that does not exist, use 'mime-part-<index>' with
|
||||||
<index> being the number of the mime-part.
|
<index> being the number of the mime-part.
|
||||||
|
|
||||||
OVERWRITE? specifies whether existing files by the same name or overwritten.
|
OVERWRITE? specifies whether existing files by the same name or overwritten.
|
||||||
Otherwise, trying to overwrite an existing file raises an error."
|
Otherwise, trying to overwrite an existing file raises an error."
|
||||||
(let* ((alist (mime-part->alist mime-part))
|
(let* ((alist (mime-part->alist mime-part))
|
||||||
(filename (or filename
|
(path (or path (filename mime-part))))
|
||||||
(assoc-ref alist 'filename)
|
;; we need an fd-based port since we want to support overwrite?
|
||||||
(format #f "mime-part-~d" (assoc-ref alist 'index)))))
|
(open path
|
||||||
;; we need an fd-based port since we want to support overwrite?
|
(logior O_WRONLY O_CREAT O_TRUNC (if overwrite? O_EXCL 0)) #o644)))
|
||||||
(open filename
|
|
||||||
(logior O_WRONLY O_CREAT O_TRUNC (if overwrite? O_EXCL 0)) #o644)))
|
|
||||||
|
|
||||||
(define* (write-to-file mime-part #:key (filename #f) (overwrite? #f))
|
(define* (write-to-file mime-part #:key (path #f) (overwrite? #f))
|
||||||
"Write MIME-PART to a file.
|
"Write MIME-PART to a file.
|
||||||
|
|
||||||
FILENAME is the path to the file name. If not specified, use the 'filename'
|
PATH is the path/filename for the file. If not specified, use the 'filename'
|
||||||
field in the mime-part and if that does not exist, use 'mime-part-<index>' with
|
field in the mime-part and if that does not exist, use 'mime-part-<index>' with
|
||||||
<index> being the number of the mime-part.
|
<index> being the number of the mime-part.
|
||||||
|
|
||||||
@ -187,7 +224,7 @@ OVERWRITE? specifies whether existing files by the same name or overwritten.
|
|||||||
Otherwise, trying to overwrite an existing file raises an error."
|
Otherwise, trying to overwrite an existing file raises an error."
|
||||||
(let* ((input (make-port mime-part))
|
(let* ((input (make-port mime-part))
|
||||||
(output (make-output-file mime-part
|
(output (make-output-file mime-part
|
||||||
#:filename filename #:overwrite? overwrite?))
|
#:path path #:overwrite? overwrite?))
|
||||||
(buf (make-bytevector 4096)) ;; just a guess...
|
(buf (make-bytevector 4096)) ;; just a guess...
|
||||||
(bytes 0))
|
(bytes 0))
|
||||||
(while (not (eof-object? bytes)) ;; XXX do this in a more elegant way.
|
(while (not (eof-object? bytes)) ;; XXX do this in a more elegant way.
|
||||||
@ -197,42 +234,59 @@ Otherwise, trying to overwrite an existing file raises an error."
|
|||||||
(close output)))
|
(close output)))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
;; Message
|
;; Message
|
||||||
;;
|
|
||||||
;; A <message> has two slots:
|
|
||||||
;; plist --> this is the message sexp cached in the database;
|
|
||||||
;; for each message (for mu4e, but we reuse here)
|
|
||||||
;; object--> wraps a Mu::Message* as a "foreign object"
|
|
||||||
;; parts --> MIME-parts
|
|
||||||
;; generally the plist is a bit cheaper, since the mu-message
|
|
||||||
;; captures a file-descriptor.
|
|
||||||
|
|
||||||
(define-class <message> ()
|
(define-class <message> ()
|
||||||
(object #:init-value #f #:init-keyword #:object)
|
(cc-message #:init-value #f #:init-keyword #:cc-message)
|
||||||
|
(parts #:init-value #f #:init-keyword #:parts)
|
||||||
(plist #:init-value #f #:init-keyword #:plist)
|
(plist #:init-value #f #:init-keyword #:plist)
|
||||||
(parts #:init-value #f #:init-keyword #:parts))
|
(alist #:init-value #f))
|
||||||
|
|
||||||
|
(set-documentation!
|
||||||
|
'<message>
|
||||||
|
"A <message> represents the information about a message.
|
||||||
|
|
||||||
|
Exactly what information depends on how the object came to be;
|
||||||
|
these are the slots:
|
||||||
|
|
||||||
|
- cc-message: this is a foreign-object representing the mu
|
||||||
|
message object, and needs to be passed to some 'cc-' methods.
|
||||||
|
- parts: a list of <mime-part> objects
|
||||||
|
- plist: this is an Emacs-style property list which is cached
|
||||||
|
for each message in the store; this was originally added
|
||||||
|
for use in mu4e, but we re-use it here.
|
||||||
|
- alist: an association list; this is just more 'scheme-like'
|
||||||
|
version of the plist, it is created on-demand (message->alist).
|
||||||
|
|
||||||
|
A message that came from a search such as 'mfind' initially only
|
||||||
|
has the plist, but when a message is loaded from file, either
|
||||||
|
through make-message or by calling a function that needs a
|
||||||
|
full message, such as header or body, the cc-message is initialized.
|
||||||
|
|
||||||
|
Only having a plist is cheaper.")
|
||||||
|
|
||||||
(define (make-message path)
|
(define (make-message path)
|
||||||
"Create a <message> from file at PATH."
|
"Create a <message> from file at PATH."
|
||||||
(make <message> #:object (cc-message-make path)))
|
(make <message> #:cc-message (cc-message-make path)))
|
||||||
|
|
||||||
(define-method (plist (message <message>))
|
(define-method (plist (message <message>))
|
||||||
"Get the PLIST for this MESSAGE."
|
"Get the PLIST for this MESSAGE."
|
||||||
(when (not (slot-ref message 'plist))
|
(when (not (slot-ref message 'plist))
|
||||||
(slot-set! message 'plist (cc-message-plist (object message))))
|
(slot-set! message 'plist (cc-message-plist (cc-message message))))
|
||||||
(slot-ref message 'plist))
|
(slot-ref message 'plist))
|
||||||
|
|
||||||
(define-method (object (message <message>))
|
(define-method (message->alist (message <message>))
|
||||||
|
"Get an association-list (alist) representation for MESSAGE."
|
||||||
|
(when (not (slot-ref message 'alist))
|
||||||
|
(slot-set! message 'alist (plist->alist (plist message))))
|
||||||
|
(slot-ref message 'alist))
|
||||||
|
|
||||||
|
(define-method (cc-message (message <message>))
|
||||||
"Get the foreign object for this MESSAGE.
|
"Get the foreign object for this MESSAGE.
|
||||||
If MESSAGE does not have such an object yet, create it from the
|
If MESSAGE does not have such an object yet, create it from the
|
||||||
path of the message."
|
path of the message."
|
||||||
(if (not (slot-ref message 'object))
|
(if (not (slot-ref message 'cc-message))
|
||||||
(slot-set! message 'object (cc-message-make (path message))))
|
(slot-set! message 'cc-message (cc-message-make (path message))))
|
||||||
(slot-ref message 'object))
|
(slot-ref message 'cc-message))
|
||||||
|
|
||||||
(define-method (find-field (message <message>) field)
|
|
||||||
(plist-find (plist message) field))
|
|
||||||
|
|
||||||
(define-method (sexp (message <message>))
|
(define-method (sexp (message <message>))
|
||||||
"Get the s-expression (plist) for this MESSAGE.
|
"Get the s-expression (plist) for this MESSAGE.
|
||||||
@ -241,51 +295,44 @@ This is an internal data-structure, originally for use with mu4e, but useful
|
|||||||
here as well. However, the precise details are not part of mu-scm API."
|
here as well. However, the precise details are not part of mu-scm API."
|
||||||
(plist message))
|
(plist message))
|
||||||
|
|
||||||
(define (emacs-time->epoch-secs lst)
|
|
||||||
"Convert emacs-style timestamp LST to a number of seconds since epoch.
|
|
||||||
If LST is #f, return #f."
|
|
||||||
(if lst
|
|
||||||
(+ (ash (car lst) 16) (cadr lst))
|
|
||||||
#f))
|
|
||||||
|
|
||||||
;; Accessors for the fields
|
;; Accessors for the fields
|
||||||
|
|
||||||
(define-method (subject (message <message>))
|
(define-method (subject (message <message>))
|
||||||
"Get the subject for MESSAGE or #f if not found."
|
"Get the subject for MESSAGE or #f if not found."
|
||||||
(find-field message ':subject))
|
(assoc-ref (message->alist message) 'subject))
|
||||||
|
|
||||||
(define-method (maildir (message <message>))
|
(define-method (maildir (message <message>))
|
||||||
"Get the maildir for MESSAGE or #f if not found."
|
"Get the maildir for MESSAGE or #f if not found."
|
||||||
(find-field message ':maildir))
|
(assoc-ref (message->alist message) 'maildir))
|
||||||
|
|
||||||
(define-method (message-id (message <message>))
|
(define-method (message-id (message <message>))
|
||||||
"Get the message-id for MESSAGE or #f if not found."
|
"Get the message-id for MESSAGE or #f if not found."
|
||||||
(find-field message ':message-id))
|
(assoc-ref (message->alist message) 'message-id))
|
||||||
|
|
||||||
(define-method (date (message <message>))
|
(define-method (date (message <message>))
|
||||||
"Get the date for MESSAGE was sent.
|
"Get the timestamp for MESSAGE was sent.
|
||||||
This is the number of seconds since epoch; #f if not found."
|
This is the number of seconds since epoch; #f if not found."
|
||||||
(emacs-time->epoch-secs (find-field message ':date)))
|
(assoc-ref (message->alist message) 'date))
|
||||||
|
|
||||||
(define-method (last-change (message <message>))
|
(define-method (changed (message <message>))
|
||||||
"Get the date for the last change to MESSAGE.
|
"Get the timestamp for the last change to MESSAGE.
|
||||||
This is the number of seconds since epoch; #f if not found."
|
This is the number of seconds since epoch; #f if not found."
|
||||||
(emacs-time->epoch-secs (find-field message ':changed)))
|
(assoc-ref (message->alist message) 'changed))
|
||||||
|
|
||||||
(define-method (path (message <message>))
|
(define-method (path (message <message>))
|
||||||
"Get the file-system path for MESSAGE.
|
"Get the file-system path for MESSAGE.
|
||||||
A symbol, either 'high, 'low or 'normal, or #f if not found."
|
A symbol, either 'high, 'low or 'normal, or #f if not found."
|
||||||
(find-field message ':path))
|
(assoc-ref (message->alist message) 'path))
|
||||||
|
|
||||||
(define-method (priority (message <message>))
|
(define-method (priority (message <message>))
|
||||||
"Get the priority for MESSAGE.
|
"Get the priority for MESSAGE.
|
||||||
A symbol, either 'high, 'low or 'normal, or #f if not found."
|
A symbol, either 'high, 'low or 'normal, or #f if not found."
|
||||||
(find-field message ':priority))
|
(assoc-ref (message->alist message) 'priority))
|
||||||
|
|
||||||
(define-method (language (message <message>))
|
(define-method (language (message <message>))
|
||||||
"Get the ISO-639-1 language code for the MESSAGE as a symbol, if detected.
|
"Get the ISO-639-1 language code for the MESSAGE as a symbol, if detected.
|
||||||
Return #f otherwise."
|
Return #f otherwise."
|
||||||
(let ((lang (find-field message ':language)))
|
(let ((lang ( (assoc-ref (message->alist message) 'language))))
|
||||||
(if lang
|
(if lang
|
||||||
(string->symbol lang)
|
(string->symbol lang)
|
||||||
#f)))
|
#f)))
|
||||||
@ -293,7 +340,7 @@ Return #f otherwise."
|
|||||||
|
|
||||||
(define-method (size (message <message>))
|
(define-method (size (message <message>))
|
||||||
"Get the size of the MESSAGE in bytes or #f if not available."
|
"Get the size of the MESSAGE in bytes or #f if not available."
|
||||||
(find-field message ':size))
|
(assoc-ref (message->alist message) 'size))
|
||||||
|
|
||||||
(define-method (references (message <message>))
|
(define-method (references (message <message>))
|
||||||
"Get the list of reference of MESSAGE or #f if not available.
|
"Get the list of reference of MESSAGE or #f if not available.
|
||||||
@ -301,7 +348,7 @@ Return #f otherwise."
|
|||||||
reference (message-id) will appear at most once, duplicates and
|
reference (message-id) will appear at most once, duplicates and
|
||||||
fake-message-id (see impls) are filtered out. If there are no references, return
|
fake-message-id (see impls) are filtered out. If there are no references, return
|
||||||
#f."
|
#f."
|
||||||
(find-field message ':references))
|
(assoc-ref (message->alist message) 'references))
|
||||||
|
|
||||||
(define-method (thread-id (message <message>))
|
(define-method (thread-id (message <message>))
|
||||||
"Get the oldest (first) reference for MESSAGE, or message-id if there are none.
|
"Get the oldest (first) reference for MESSAGE, or message-id if there are none.
|
||||||
@ -314,20 +361,19 @@ This is method is useful to determine the thread a message is in."
|
|||||||
|
|
||||||
(define-method (mailing-list (message <message>))
|
(define-method (mailing-list (message <message>))
|
||||||
"Get the mailing-list id for MESSAGE or #f if not available."
|
"Get the mailing-list id for MESSAGE or #f if not available."
|
||||||
(find-field message ':list))
|
(assoc-ref (message->alist message) 'list))
|
||||||
|
|
||||||
;; Flags.
|
;; Flags.
|
||||||
|
|
||||||
(define-method (flags (message <message>))
|
(define-method (flags (message <message>))
|
||||||
"Get the size of the MESSAGE in bytes or #f if not available."
|
"Get the size of the MESSAGE in bytes or #f if not available."
|
||||||
(find-field message ':flags))
|
(assoc-ref (message->alist message) 'flags))
|
||||||
|
|
||||||
(define-method (flag? (message <message>) flag)
|
(define-method (flag? (message <message>) flag)
|
||||||
"Does MESSAGE have FLAG?"
|
"Does MESSAGE have FLAG?"
|
||||||
(let ((flags
|
(let ((flgs (flags message)))
|
||||||
(find-field message ':flags)))
|
(if flgs
|
||||||
(if flags
|
(if (member flag flgs) #t #f)
|
||||||
(if (member flag flags) #t #f)
|
|
||||||
#f)))
|
#f)))
|
||||||
|
|
||||||
(define-method (draft? (message <message>))
|
(define-method (draft? (message <message>))
|
||||||
@ -386,76 +432,89 @@ This is method is useful to determine the thread a message is in."
|
|||||||
"Does MESSAGE have a calender invitation?"
|
"Does MESSAGE have a calender invitation?"
|
||||||
(flag? message 'calendar))
|
(flag? message 'calendar))
|
||||||
|
|
||||||
(define-method (find-contact-field (message <message>) field)
|
|
||||||
"Get contact FIELD from MESSAGE as an alist.
|
|
||||||
Helper method "
|
|
||||||
(let ((cs (find-field message field)))
|
|
||||||
(if cs
|
|
||||||
(map plist->alist cs)
|
|
||||||
#f)))
|
|
||||||
|
|
||||||
(define-method (from (message <message>))
|
(define-method (from (message <message>))
|
||||||
"Get the sender (the From: field) for MESSAGE or #f if not found."
|
"Get the sender (the From: field) for MESSAGE or #f if not found."
|
||||||
(find-contact-field message ':from))
|
(assoc-ref (message->alist message) 'from))
|
||||||
|
|
||||||
(define-method (to (message <message>))
|
(define-method (to (message <message>))
|
||||||
"Get the (intended) recipient for MESSAGE (the To: field) or #f if not found."
|
"Get the (intended) recipient for MESSAGE (the To: field) or #f if not found."
|
||||||
(find-contact-field message ':to))
|
(assoc-ref (message->alist message) 'to))
|
||||||
|
|
||||||
(define-method (cc (message <message>))
|
(define-method (cc (message <message>))
|
||||||
"Get the (intended) carbon-copy recipient for MESSAGE (the Cc: field) or #f if
|
"Get the (intended) carbon-copy recipient for MESSAGE (the Cc: field) or #f if
|
||||||
not found."
|
not found."
|
||||||
(find-contact-field message ':cc))
|
(assoc-ref (message->alist message) 'cc))
|
||||||
|
|
||||||
(define-method (bcc (message <message>))
|
(define-method (bcc (message <message>))
|
||||||
"Get the (intended) blind carbon-copy recipient for MESSAGE (the Bcc: field) or
|
"Get the (intended) blind carbon-copy recipient for MESSAGE (the Bcc: field) or
|
||||||
#f if not found."
|
#f if not found."
|
||||||
(find-contact-field message ':bcc))
|
(assoc-ref (message->alist message) 'bcc))
|
||||||
|
|
||||||
(define* (body message #:key (html? #f))
|
(define* (body message #:key (html? #f))
|
||||||
"Get the MESSAGE body or #f if not found
|
"Get the MESSAGE body or #f if not found
|
||||||
If #:html is non-#f, instead search for the HTML body.
|
If #:html is non-#f, instead search for the HTML body.
|
||||||
Requires the full message."
|
Requires the full message."
|
||||||
(cc-message-body (object message) html?))
|
(cc-message-body (cc-message message) html?))
|
||||||
|
|
||||||
(define-method (header (message <message>) (field <string>))
|
(define-method (header (message <message>) (field <string>))
|
||||||
"Get the raw MESSAGE header FIELD or #f if not found.
|
"Get the raw MESSAGE header FIELD or #f if not found.
|
||||||
FIELD is case-insensitive and should not have the ':' suffix.
|
FIELD is case-insensitive and should not have the ':' suffix.
|
||||||
Requires the full message."
|
Requires the full message."
|
||||||
(cc-message-header (object message) field))
|
(cc-message-header (cc-message message) field))
|
||||||
|
|
||||||
(define-method (mime-parts (message <message>))
|
(define-method (mime-parts (message <message>))
|
||||||
"Get the MIME-parts for this message.
|
"Get the MIME-parts for this message.
|
||||||
This is a list of <mime-part> objects."
|
This is a list of <mime-part> objects."
|
||||||
(let ((msgobj (object message)))
|
(map (lambda (mimepart-alist)
|
||||||
(map (lambda (mimepart-alist)
|
(make <mime-part>
|
||||||
(make <mime-part>
|
#:mimepart (car mimepart-alist)
|
||||||
#:mimepart (car mimepart-alist)
|
#:alist (cdr mimepart-alist)))
|
||||||
#:alist (cdr mimepart-alist)))
|
(cc-message-parts (cc-message message))))
|
||||||
(cc-message-parts msgobj))))
|
|
||||||
|
|
||||||
;; Store
|
;; Store
|
||||||
;;
|
;;
|
||||||
;; Note: we have a %default-store, which is the store we opened during
|
;; Note: we have a %default-store, which is the store we opened during startup;
|
||||||
;; startup; for now that's the only store supported, but we keep things
|
;; for now that's the only store supported, but we keep things open.
|
||||||
;; open.
|
|
||||||
;;
|
;;
|
||||||
;; Since it's the default store, we'd like to call the methods without
|
;; Since it's the default store, we'd like to call the methods without
|
||||||
;; explicitly using %default-store; with GOOPS, we cannot pass a default for
|
;; explicitly using %default-store; with GOOPS, we cannot pass a default for
|
||||||
;; that, nor can we use keyword arguments (I think?). So use define* for that.
|
;; that, nor can we use keyword arguments (I think?). So use define* for that.
|
||||||
|
|
||||||
;; the 'store-object' is a foreign object wrapping a const Store*.
|
|
||||||
(define-class <store> ()
|
(define-class <store> ()
|
||||||
(store-object #:init-keyword #:store-object #:getter store-object))
|
(cc-store #:init-keyword #:cc-store #:getter cc-store)
|
||||||
|
(alist #:init-value #f))
|
||||||
|
|
||||||
|
(set-documentation!
|
||||||
|
'<store>
|
||||||
|
"A <store> represents mu's message store (database).
|
||||||
|
|
||||||
|
It has a few slots:
|
||||||
|
- cc-store: this is a foreign-object for a Mu::Store*.
|
||||||
|
It needs to be passed to some 'cc-' methods.
|
||||||
|
- alist: an association list; this is the cached representation
|
||||||
|
of some store properties.")
|
||||||
|
|
||||||
;; not exported
|
;; not exported
|
||||||
(define-method (make-store store-object)
|
(define-method (make-store store-obj)
|
||||||
"Make a store from some STORE-OBJECT."
|
"Make a store from some STORE-OBJ.
|
||||||
(make <store> #:store-object store-object))
|
STORE-OBJ a 'foreign-object' for a mu Store pointer."
|
||||||
|
(make <store> #:cc-store store-obj))
|
||||||
|
|
||||||
(define %default-store
|
(define %default-store
|
||||||
;; %default-store-object is defined in mu-scm-store.cc
|
(make-store %cc-default-store))
|
||||||
(make-store %default-store-object))
|
|
||||||
|
(set-documentation! '%default-store "Default store.")
|
||||||
|
|
||||||
|
(set-documentation! '%cc-default-store
|
||||||
|
"Default store object.
|
||||||
|
This is defined in the C++ code, and represents a \"foreign\" Store* object.")
|
||||||
|
|
||||||
|
(define* (store->alist #:key (store %default-store))
|
||||||
|
"Get an alist-representation for some store.
|
||||||
|
Keyword arguments:
|
||||||
|
#:store %default-store. Leave at default."
|
||||||
|
(when (not (slot-ref store 'alist))
|
||||||
|
(slot-set! store 'alist (cc-store-alist (cc-store store))))
|
||||||
|
(slot-ref store 'alist))
|
||||||
|
|
||||||
(define* (mfind query
|
(define* (mfind query
|
||||||
#:key
|
#:key
|
||||||
@ -465,7 +524,7 @@ This is a list of <mime-part> objects."
|
|||||||
(sort-field 'date)
|
(sort-field 'date)
|
||||||
(reverse? #f)
|
(reverse? #f)
|
||||||
(max-results #f))
|
(max-results #f))
|
||||||
"Find messages matching some query.
|
"Find messages matching some query.
|
||||||
|
|
||||||
The query is mandatory, the other (keyword) arguments are optional.
|
The query is mandatory, the other (keyword) arguments are optional.
|
||||||
(mfind QUERY
|
(mfind QUERY
|
||||||
@ -475,16 +534,16 @@ The query is mandatory, the other (keyword) arguments are optional.
|
|||||||
#:sort-field? field to sort by, a symbol. Default: date
|
#:sort-field? field to sort by, a symbol. Default: date
|
||||||
#:reverse? sort in descending order (z-a)
|
#:reverse? sort in descending order (z-a)
|
||||||
#:max-results max. number of matches. Default: false (unlimited))."
|
#:max-results max. number of matches. Default: false (unlimited))."
|
||||||
(map (lambda (plist)
|
(map (lambda (plist)
|
||||||
(make <message> #:plist plist))
|
(make <message> #:plist plist))
|
||||||
(cc-store-mfind (store-object store) query
|
(cc-store-mfind (cc-store store) query
|
||||||
related? skip-dups? sort-field reverse? max-results)))
|
related? skip-dups? sort-field reverse? max-results)))
|
||||||
|
|
||||||
(define* (mcount
|
(define* (mcount
|
||||||
#:key
|
#:key
|
||||||
(store %default-store))
|
(store %default-store))
|
||||||
"Get the number of messages."
|
"Get the number of messages."
|
||||||
(cc-store-mcount (store-object store)))
|
(cc-store-mcount (cc-store store)))
|
||||||
|
|
||||||
(define* (cfind pattern
|
(define* (cfind pattern
|
||||||
#:key
|
#:key
|
||||||
@ -500,7 +559,7 @@ The pattern is mandatory; the other (keyword) arguments are optional.
|
|||||||
#:personal? only include 'personal' contacts. Default: all
|
#:personal? only include 'personal' contacts. Default: all
|
||||||
#:after only include contacts last seen time_t: Default all
|
#:after only include contacts last seen time_t: Default all
|
||||||
#:max-results max. number of matches. Default: false (unlimited))."
|
#:max-results max. number of matches. Default: false (unlimited))."
|
||||||
(cc-store-cfind (store-object store) pattern personal? after max-results))
|
(cc-store-cfind (cc-store store) pattern personal? after max-results))
|
||||||
|
|
||||||
;;; Misc
|
;;; Misc
|
||||||
|
|
||||||
@ -511,16 +570,18 @@ The pattern is mandatory; the other (keyword) arguments are optional.
|
|||||||
;; The alist maps symbols to values; a value of #f indicates that the value is at
|
;; The alist maps symbols to values; a value of #f indicates that the value is at
|
||||||
;; its default.
|
;; its default.
|
||||||
%options ;; defined in c++
|
%options ;; defined in c++
|
||||||
|
(set-documentation! '%options
|
||||||
|
"Alist with the command-line parameters.")
|
||||||
|
|
||||||
;; Alist of user-preferences.
|
|
||||||
;;
|
|
||||||
;; - short-date: a strftime-compatibie string for the display
|
|
||||||
;; format of short dates.
|
|
||||||
;; - utc? : whether to assume use UTC for dates/times
|
|
||||||
(define %preferences
|
(define %preferences
|
||||||
'( (short-date . "%F %T")
|
'( (short-date . "%F %T")
|
||||||
(utc? . #f)))
|
(utc? . #f)))
|
||||||
;; XXX; not exposed yet. Perhaps we need a "fluid" here?
|
;; XXX; not exposed yet. Perhaps we need a "fluid" here?
|
||||||
|
(set-documentation! '%preferences
|
||||||
|
"Alist with user-preferences.
|
||||||
|
- short-date: a strftime-compatibie string for the display
|
||||||
|
format of short dates.
|
||||||
|
- utc? : whether to assume use UTC for dates/times")
|
||||||
|
|
||||||
(define (value-or-preference val key)
|
(define (value-or-preference val key)
|
||||||
"If VAL is the symbol 'preference, return the value for KEY from %preferences.
|
"If VAL is the symbol 'preference, return the value for KEY from %preferences.
|
||||||
|
|||||||
@ -260,7 +260,7 @@ few key concepts, represented in some GOOP objects and other data-structures:
|
|||||||
@end itemize
|
@end itemize
|
||||||
|
|
||||||
@menu
|
@menu
|
||||||
* Store:: the database of all information
|
* Store:: where message information lives
|
||||||
* Message:: inspecting individual messages
|
* Message:: inspecting individual messages
|
||||||
* Miscellaneous:: other functions
|
* Miscellaneous:: other functions
|
||||||
* Helpers:: some helper functions
|
* Helpers:: some helper functions
|
||||||
@ -352,6 +352,18 @@ Example usage:
|
|||||||
=> 140728
|
=> 140728
|
||||||
@end lisp
|
@end lisp
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} store->alist
|
||||||
|
@end deffn
|
||||||
|
Retrieve an association list (``alist'') with information about the store.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
@lisp
|
||||||
|
(store->alist)
|
||||||
|
=> ((batch-size . 50000) (created . 1741180008) (max-message-size . 100000000)
|
||||||
|
(personal-addresses "djcb@@example.com" "msx@@example.com")
|
||||||
|
(root-maildir . "/home/user/Maildir") (schema-version . 500))
|
||||||
|
@end lisp
|
||||||
|
|
||||||
@node Message
|
@node Message
|
||||||
@section Message
|
@section Message
|
||||||
|
|
||||||
@ -515,12 +527,20 @@ case, @code{content-only?} is implied to be #t.
|
|||||||
Write MIME-part to file.
|
Write MIME-part to file.
|
||||||
|
|
||||||
Use @code{filename} is the file/path to use for writing; if this is @code{#f},
|
Use @code{filename} is the file/path to use for writing; if this is @code{#f},
|
||||||
the name is taken from the @t{filename} property of the MIME-part alist. If that
|
the name using the @code{filename} procedure.
|
||||||
does not exist, a generic name is chosen.
|
|
||||||
|
|
||||||
If @code{overwrite?} is true, overwrite existing files of the same name;
|
If @code{overwrite?} is true, overwrite existing files of the same name;
|
||||||
otherwise, raise an error if the file already exists.
|
otherwise, raise an error if the file already exists.
|
||||||
|
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} filename mime-part
|
||||||
|
@end deffn
|
||||||
|
Determine a filename for the given MIME-part.
|
||||||
|
|
||||||
|
This is either taken from the @t{filename} property of the MIME-part alist, or,
|
||||||
|
If that does not exist, a generic name.
|
||||||
|
|
||||||
|
|
||||||
@subsection Contacts
|
@subsection Contacts
|
||||||
|
|
||||||
Message fields @t{To:}, @t{From:}, @t{Cc:} and @t{Bcc:} contain @emph{contacts}.
|
Message fields @t{To:}, @t{From:}, @t{Cc:} and @t{Bcc:} contain @emph{contacts}.
|
||||||
@ -638,13 +658,13 @@ Is this a personal message? Returns @t{#t} or @t{#f}.
|
|||||||
|
|
||||||
@deffn {Scheme Procedure} calendar? message
|
@deffn {Scheme Procedure} calendar? message
|
||||||
@end deffn
|
@end deffn
|
||||||
Does this message include a calendar invitation? Returns @t{#t} or @t{#f}.
|
Does this message include a calendar invitation? Returns @t{#t} or @code{#f}.
|
||||||
|
|
||||||
@subsection Miscellaneous
|
@subsection Miscellaneous
|
||||||
|
|
||||||
@deffn {Scheme Procedure} last-change message
|
@deffn {Scheme Procedure} changed message
|
||||||
@end deffn
|
@end deffn
|
||||||
Get the time of the message's last change (through @t{mu}), or @t{#f} if there
|
Get the time of the message's last change (through @t{mu}), or @code{#f} if there
|
||||||
is none. The time is expressed the data as the number of seconds since epoch,
|
is none. The time is expressed the data as the number of seconds since epoch,
|
||||||
@t{time_t}.
|
@t{time_t}.
|
||||||
|
|
||||||
@ -657,7 +677,7 @@ For example:
|
|||||||
@deffn {Scheme Procedure} priority message
|
@deffn {Scheme Procedure} priority message
|
||||||
@end deffn
|
@end deffn
|
||||||
Get the message's priority. This is a symbol, either @t{high}, @t{normal} or
|
Get the message's priority. This is a symbol, either @t{high}, @t{normal} or
|
||||||
@t{low}, or @t{#f} if not present.
|
@t{low}, or @code{#f} if not present.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
@lisp
|
@lisp
|
||||||
@ -677,7 +697,7 @@ For example:
|
|||||||
|
|
||||||
@deffn {Scheme Procedure} language message
|
@deffn {Scheme Procedure} language message
|
||||||
@end deffn
|
@end deffn
|
||||||
Get the ISO-639-1 language code for message's primary language or @t{#f} if not
|
Get the ISO-639-1 language code for message's primary language or @code{#f} if not
|
||||||
found. This is available only if @t{mu} was built with CLD2 support, see
|
found. This is available only if @t{mu} was built with CLD2 support, see
|
||||||
@command{mu info}. The language code is represented as a symbol, such as @t{en},
|
@command{mu info}. The language code is represented as a symbol, such as @t{en},
|
||||||
@t{nl} or @t{fi}.
|
@t{nl} or @t{fi}.
|
||||||
@ -813,7 +833,7 @@ Convert a @t{time_t} value (``seconds-since-epoch'') to a string. The optional
|
|||||||
output format, while the @code{#:utc?} determines whether to use UTC.
|
output format, while the @code{#:utc?} determines whether to use UTC.
|
||||||
@c Defaults are determined by the @code{%preferences} variable.
|
@c Defaults are determined by the @code{%preferences} variable.
|
||||||
|
|
||||||
If @var{time_t} is @t{#f}, return @code{#f}.
|
If @var{time_t} is @code{#f}, return @code{#f}.
|
||||||
|
|
||||||
@node GNU Free Documentation License
|
@node GNU Free Documentation License
|
||||||
@appendix GNU Free Documentation License
|
@appendix GNU Free Documentation License
|
||||||
|
|||||||
Reference in New Issue
Block a user