mirror of https://github.com/djcb/mu.git
* mu-msg, mu-msg-part: overhaul of mime-part handling
This commit is contained in:
parent
a778d233c0
commit
da16959a33
|
@ -28,7 +28,39 @@
|
||||||
#include "mu-msg-priv.h"
|
#include "mu-msg-priv.h"
|
||||||
#include "mu-msg-part.h"
|
#include "mu-msg-part.h"
|
||||||
|
|
||||||
|
struct _FindPartData {
|
||||||
|
guint idx, wanted_idx;
|
||||||
|
GMimeObject *part;
|
||||||
|
};
|
||||||
|
typedef struct _FindPartData FindPartData;
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
find_part_cb (GMimeObject *parent, GMimeObject *part, FindPartData *fpdata)
|
||||||
|
{
|
||||||
|
if (fpdata->part || fpdata->wanted_idx != fpdata->idx++)
|
||||||
|
return; /* not yet found */
|
||||||
|
|
||||||
|
fpdata->part = part;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GMimeObject*
|
||||||
|
find_part (MuMsg* msg, guint partidx)
|
||||||
|
{
|
||||||
|
FindPartData fpdata;
|
||||||
|
|
||||||
|
fpdata.wanted_idx = partidx;
|
||||||
|
fpdata.idx = 0;
|
||||||
|
fpdata.part = NULL;
|
||||||
|
|
||||||
|
g_mime_message_foreach (msg->_mime_msg,
|
||||||
|
(GMimeObjectForeachFunc)find_part_cb,
|
||||||
|
&fpdata);
|
||||||
|
return fpdata.part;
|
||||||
|
}
|
||||||
|
|
||||||
struct _PartData {
|
struct _PartData {
|
||||||
|
MuMsg *_msg;
|
||||||
unsigned _idx;
|
unsigned _idx;
|
||||||
MuMsgPartForeachFunc _func;
|
MuMsgPartForeachFunc _func;
|
||||||
gpointer _user_data;
|
gpointer _user_data;
|
||||||
|
@ -47,6 +79,7 @@ part_foreach_cb (GMimeObject *parent, GMimeObject *part, PartData *pdata)
|
||||||
pi.content_id = (char*)g_mime_object_get_content_id (part);
|
pi.content_id = (char*)g_mime_object_get_content_id (part);
|
||||||
|
|
||||||
ct = g_mime_object_get_content_type (part);
|
ct = g_mime_object_get_content_type (part);
|
||||||
|
|
||||||
if (GMIME_IS_CONTENT_TYPE(ct)) {
|
if (GMIME_IS_CONTENT_TYPE(ct)) {
|
||||||
pi.type = (char*)g_mime_content_type_get_media_type (ct);
|
pi.type = (char*)g_mime_content_type_get_media_type (ct);
|
||||||
pi.subtype = (char*)g_mime_content_type_get_media_subtype (ct);
|
pi.subtype = (char*)g_mime_content_type_get_media_subtype (ct);
|
||||||
|
@ -57,21 +90,19 @@ part_foreach_cb (GMimeObject *parent, GMimeObject *part, PartData *pdata)
|
||||||
pi.file_name = (char*)g_mime_part_get_filename (GMIME_PART(part));
|
pi.file_name = (char*)g_mime_part_get_filename (GMIME_PART(part));
|
||||||
}
|
}
|
||||||
|
|
||||||
pdata->_func(&pi, pdata->_user_data);
|
pdata->_func(pdata->_msg, &pi, pdata->_user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
mu_msg_msg_part_foreach (MuMsg *msg,
|
mu_msg_part_foreach (MuMsg *msg, MuMsgPartForeachFunc func,
|
||||||
MuMsgPartForeachFunc func,
|
gpointer user_data)
|
||||||
gpointer user_data)
|
|
||||||
{
|
{
|
||||||
PartData pdata;
|
PartData pdata;
|
||||||
|
|
||||||
g_return_if_fail (msg);
|
g_return_if_fail (msg);
|
||||||
g_return_if_fail (GMIME_IS_OBJECT(msg->_mime_msg));
|
g_return_if_fail (GMIME_IS_OBJECT(msg->_mime_msg));
|
||||||
|
|
||||||
|
pdata._msg = msg;
|
||||||
pdata._idx = 0;
|
pdata._idx = 0;
|
||||||
pdata._func = func;
|
pdata._func = func;
|
||||||
pdata._user_data = user_data;
|
pdata._user_data = user_data;
|
||||||
|
@ -82,19 +113,6 @@ mu_msg_msg_part_foreach (MuMsg *msg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct _SavePartData {
|
|
||||||
guint idx, wanted_idx;
|
|
||||||
const gchar* targetdir;
|
|
||||||
gboolean overwrite;
|
|
||||||
gboolean stream;
|
|
||||||
guint cookie;
|
|
||||||
gboolean result;
|
|
||||||
gboolean play;
|
|
||||||
};
|
|
||||||
typedef struct _SavePartData SavePartData;
|
|
||||||
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
write_to_stream (GMimeObject *part, int fd)
|
write_to_stream (GMimeObject *part, int fd)
|
||||||
{
|
{
|
||||||
|
@ -103,14 +121,14 @@ write_to_stream (GMimeObject *part, int fd)
|
||||||
gboolean rv;
|
gboolean rv;
|
||||||
|
|
||||||
stream = g_mime_stream_fs_new (fd);
|
stream = g_mime_stream_fs_new (fd);
|
||||||
if (!stream) {
|
if (!GMIME_IS_STREAM(stream)) {
|
||||||
g_critical ("%s: failed to create stream",__FUNCTION__);
|
g_critical ("%s: failed to create stream",__FUNCTION__);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
g_mime_stream_fs_set_owner (GMIME_STREAM_FS(stream), FALSE);
|
g_mime_stream_fs_set_owner (GMIME_STREAM_FS(stream), FALSE);
|
||||||
|
|
||||||
wrapper = g_mime_part_get_content_object (GMIME_PART(part));
|
wrapper = g_mime_part_get_content_object (GMIME_PART(part));
|
||||||
if (!wrapper) {
|
if (!GMIME_IS_DATA_WRAPPER(wrapper)) {
|
||||||
g_critical ("%s: failed to create wrapper", __FUNCTION__);
|
g_critical ("%s: failed to create wrapper", __FUNCTION__);
|
||||||
g_object_unref (stream);
|
g_object_unref (stream);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
@ -129,102 +147,177 @@ write_to_stream (GMimeObject *part, int fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static gboolean
|
gboolean
|
||||||
save_part (GMimeObject *part, const char *filename,
|
save_part (GMimeObject *part, const char *fullpath,
|
||||||
const char *targetdir, gboolean overwrite, gboolean tryplay)
|
gboolean overwrite, gboolean use_existing)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
char *fullpath;
|
|
||||||
gboolean rv;
|
gboolean rv;
|
||||||
|
|
||||||
fullpath = g_strdup_printf ("%s%s%s",
|
/* don't try to overwrite when we already have it; useful when
|
||||||
targetdir ? targetdir : "",
|
* you're sure it's not a different file with the same name */
|
||||||
targetdir ? G_DIR_SEPARATOR_S : "",
|
if (use_existing && access (fullpath, F_OK) == 0)
|
||||||
filename);
|
return TRUE;
|
||||||
|
|
||||||
|
/* ok, try to create the file */
|
||||||
fd = mu_util_create_writeable_fd (fullpath, 0600, overwrite);
|
fd = mu_util_create_writeable_fd (fullpath, 0600, overwrite);
|
||||||
if (fd == -1) {
|
if (fd == -1)
|
||||||
g_free (fullpath);
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
|
||||||
|
|
||||||
rv = write_to_stream (part, fd);
|
rv = write_to_stream (part, fd);
|
||||||
if (close (fd) != 0)
|
if (close (fd) != 0)
|
||||||
g_warning ("%s: failed to close %s: %s", __FUNCTION__,
|
g_warning ("%s: failed to close %s: %s", __FUNCTION__,
|
||||||
fullpath, strerror(errno));
|
fullpath, strerror(errno));
|
||||||
|
|
||||||
if (rv && tryplay) {
|
return TRUE;
|
||||||
rv = mu_util_play (fullpath);
|
}
|
||||||
if (!rv)
|
|
||||||
g_warning ("%s: failed to play %s", __FUNCTION__,
|
|
||||||
fullpath);
|
gchar*
|
||||||
|
mu_msg_part_filepath (MuMsg *msg, const char* targetdir, guint partidx)
|
||||||
|
{
|
||||||
|
char *fname, *filepath;
|
||||||
|
GMimeObject* part;
|
||||||
|
|
||||||
|
part = find_part (msg, partidx);
|
||||||
|
if (!part) {
|
||||||
|
g_warning ("%s: cannot find part %u", __FUNCTION__, partidx);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rv;
|
/* the easy case: the part has a filename */
|
||||||
|
fname = (gchar*)g_mime_part_get_filename (GMIME_PART(part));
|
||||||
|
if (fname) /* security: don't include any directory components... */
|
||||||
|
fname = g_path_get_basename (fname);
|
||||||
|
else
|
||||||
|
fname = g_strdup_printf ("%x-part-%u",
|
||||||
|
g_str_hash (mu_msg_get_path (msg)),
|
||||||
|
partidx);
|
||||||
|
|
||||||
|
filepath = g_build_path (G_DIR_SEPARATOR_S, targetdir ? targetdir : "",
|
||||||
|
fname, NULL);
|
||||||
|
g_free (fname);
|
||||||
|
|
||||||
|
return filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
part_foreach_save_cb (GMimeObject *parent, GMimeObject *part,
|
gchar*
|
||||||
SavePartData *spd)
|
mu_msg_part_filepath_cache (MuMsg *msg, guint partid)
|
||||||
{
|
{
|
||||||
const gchar* filename;
|
char *dirname, *filepath;
|
||||||
|
const char* path;
|
||||||
/* did we find the right part yet? */
|
|
||||||
if (spd->result || spd->wanted_idx != spd->idx++)
|
g_return_val_if_fail (msg, NULL);
|
||||||
return;
|
|
||||||
|
path = mu_msg_get_path (msg);
|
||||||
if (!GMIME_IS_PART(part)) /* ie., multiparts are ignored */
|
if (!path)
|
||||||
return;
|
return NULL;
|
||||||
|
|
||||||
filename = g_mime_part_get_filename (GMIME_PART(part));
|
/* g_compute_checksum_for_string may be better, but requires
|
||||||
if (filename) {
|
* rel. new glib (2.16) */
|
||||||
spd->result = save_part (part, filename,
|
dirname = g_strdup_printf ("%s%c%x%c%u",
|
||||||
spd->targetdir,
|
mu_util_cache_dir(), G_DIR_SEPARATOR,
|
||||||
spd->overwrite,
|
g_str_hash (path), G_DIR_SEPARATOR,
|
||||||
spd->play);
|
partid);
|
||||||
} else { /* make up a filename */
|
|
||||||
gchar *my_filename;
|
if (!mu_util_create_dir_maybe (dirname, 0700)) {
|
||||||
my_filename = g_strdup_printf ("%x-part-%u",
|
g_free (dirname);
|
||||||
spd->cookie,
|
return NULL;
|
||||||
spd->wanted_idx);
|
|
||||||
spd->result = save_part (part, my_filename,
|
|
||||||
spd->targetdir,
|
|
||||||
spd->overwrite,
|
|
||||||
spd->play);
|
|
||||||
g_free (my_filename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filepath = mu_msg_part_filepath (msg, dirname, partid);
|
||||||
|
g_free (dirname);
|
||||||
|
if (!filepath)
|
||||||
|
g_warning ("%s: could not get filename", __FUNCTION__);
|
||||||
|
|
||||||
|
return filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
mu_msg_mime_part_save (MuMsg *msg, unsigned wanted_idx,
|
mu_msg_part_save (MuMsg *msg, const char *fullpath, guint partidx,
|
||||||
const char *targetdir, gboolean overwrite, gboolean play)
|
gboolean overwrite, gboolean use_cached)
|
||||||
{
|
{
|
||||||
SavePartData spd;
|
GMimeObject *part;
|
||||||
const char *msgid;
|
|
||||||
|
|
||||||
g_return_val_if_fail (msg, FALSE);
|
g_return_val_if_fail (msg, FALSE);
|
||||||
|
g_return_val_if_fail (fullpath, FALSE);
|
||||||
spd.idx = 0;
|
g_return_val_if_fail (!overwrite||!use_cached, FALSE);
|
||||||
spd.wanted_idx = wanted_idx;
|
|
||||||
spd.targetdir = targetdir;
|
part = find_part (msg, partidx);
|
||||||
spd.overwrite = overwrite;
|
if (!GMIME_IS_PART(part)) {
|
||||||
spd.stream = FALSE; /* not used yet */
|
g_warning ("%s: cannot find part %u", __FUNCTION__, partidx);
|
||||||
spd.result = FALSE;
|
return FALSE;
|
||||||
spd.play = play;
|
}
|
||||||
|
|
||||||
/* get something fairly unique for building a filename;
|
if (!save_part (part, fullpath, overwrite, use_cached)) {
|
||||||
* normally based on the message id, but if that is not
|
g_warning ("%s: failed to save '%s'", __FUNCTION__, fullpath);
|
||||||
* available, a random number. basing it on the message id
|
return FALSE;
|
||||||
* gives makes it easy to distinguish the parts of different
|
}
|
||||||
* messages */
|
|
||||||
msgid = mu_msg_get_msgid (msg);
|
return TRUE;
|
||||||
spd.cookie = (guint)(msgid ? g_str_hash (msgid) : (guint)random());
|
|
||||||
|
|
||||||
g_mime_message_foreach (msg->_mime_msg,
|
|
||||||
(GMimeObjectForeachFunc)part_foreach_save_cb,
|
|
||||||
&spd);
|
|
||||||
return spd.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef gboolean (*MatchFunc) (GMimeObject *part, gpointer data);
|
||||||
|
|
||||||
|
struct _MatchData {
|
||||||
|
MatchFunc _matcher;
|
||||||
|
gpointer _user_data;
|
||||||
|
gint _idx, _found_idx;
|
||||||
|
};
|
||||||
|
typedef struct _MatchData MatchData;
|
||||||
|
|
||||||
|
static void
|
||||||
|
part_match_foreach_cb (GMimeObject *parent, GMimeObject *part, MatchData *mdata)
|
||||||
|
{
|
||||||
|
if (mdata->_found_idx >= 0)
|
||||||
|
if (mdata->_matcher (part, mdata->_user_data))
|
||||||
|
mdata->_found_idx = mdata->_idx;
|
||||||
|
|
||||||
|
++mdata->_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
msg_part_find_idx (GMimeMessage *msg, MatchFunc func, gpointer user_data)
|
||||||
|
{
|
||||||
|
MatchData mdata;
|
||||||
|
|
||||||
|
g_return_val_if_fail (msg, -1);
|
||||||
|
g_return_val_if_fail (GMIME_IS_MESSAGE(msg), -1);
|
||||||
|
|
||||||
|
mdata._idx = 0;
|
||||||
|
mdata._found_idx = -1;
|
||||||
|
mdata._matcher = func;
|
||||||
|
mdata._user_data = user_data;
|
||||||
|
|
||||||
|
g_mime_message_foreach (msg,
|
||||||
|
(GMimeObjectForeachFunc)part_match_foreach_cb,
|
||||||
|
&mdata);
|
||||||
|
|
||||||
|
return mdata._found_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
match_content_id (GMimeObject *part, const char *sought_cid)
|
||||||
|
{
|
||||||
|
return g_strcmp0 (g_mime_object_get_content_id (part),
|
||||||
|
sought_cid) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mu_msg_part_find_cid (MuMsg *msg, const char* content_id)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (msg, -1L);
|
||||||
|
g_return_val_if_fail (content_id, -1);
|
||||||
|
|
||||||
|
return msg_part_find_idx (msg->_mime_msg, (MatchFunc)match_content_id,
|
||||||
|
(gpointer)content_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -74,11 +74,77 @@ typedef struct _MuMsgPart MuMsgPart;
|
||||||
#define mu_msg_part_content_type(pi) ((pi)->content_type)
|
#define mu_msg_part_content_type(pi) ((pi)->content_type)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
typedef void (*MuMsgPartForeachFunc) (MuMsgPart *part, gpointer data);
|
|
||||||
/**
|
/**
|
||||||
* call a function for each of the contacts in a message
|
* macro to get the content-id (cid) for this mime-part
|
||||||
|
*
|
||||||
|
* @param pi a MuMsgPart instance
|
||||||
|
*
|
||||||
|
* @return the file name
|
||||||
|
*/
|
||||||
|
#define mu_msg_part_content_id(pi) ((pi)->content_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save a specific attachment to some targetdir
|
||||||
|
*
|
||||||
|
* @param msg a valid MuMsg instance
|
||||||
|
* @gchar filepath the filepath to save
|
||||||
|
* @param partidx index of the attachment you want to save
|
||||||
|
* @param overwrite overwrite existing files?
|
||||||
|
* @param don't raise error when the file already exists
|
||||||
|
*
|
||||||
|
* @return full path to the message part saved or NULL in case or error; free with g_free
|
||||||
|
*/
|
||||||
|
gboolean mu_msg_part_save (MuMsg *msg, const char *filepath, guint partidx,
|
||||||
|
gboolean overwrite, gboolean use_cached);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a filename for the saving the message part; try the filename
|
||||||
|
* specified for the message part if any, otherwise determine a unique
|
||||||
|
* name based on the partidx and the message path
|
||||||
|
*
|
||||||
|
* @param msg a msg
|
||||||
|
* @param targetdir where to store the part
|
||||||
|
* @param partidx the part for which to determine a filename
|
||||||
|
*
|
||||||
|
* @return a filepath (g_free when done with it) or NULL in case of error
|
||||||
|
*/
|
||||||
|
gchar* mu_msg_part_filepath (MuMsg *msg, const char* targetdir,
|
||||||
|
guint partidx) G_GNUC_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a full path name for saving the message part in the cache
|
||||||
|
* directory for this message; if needed, create the directory (but
|
||||||
|
* not the file)
|
||||||
|
*
|
||||||
|
* @param msg a msg
|
||||||
|
* @param partidx the part for which to determine a filename
|
||||||
|
*
|
||||||
|
* @return a filepath (g_free when done with it) or NULL in case of error
|
||||||
|
*/
|
||||||
|
gchar* mu_msg_part_filepath_cache (MuMsg *msg, guint partid)
|
||||||
|
G_GNUC_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the part inede for the message part with a certain content-id
|
||||||
|
*
|
||||||
|
* @param msg a message
|
||||||
|
* @param content_id a content-id to search
|
||||||
|
*
|
||||||
|
* @return the part index number of the found part, or -1 if it was not found
|
||||||
|
*/
|
||||||
|
int mu_msg_part_find_cid (MuMsg *msg, const char* content_id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef void (*MuMsgPartForeachFunc) (MuMsg *, MuMsgPart *, gpointer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call a function for each of the mime part in a message
|
||||||
*
|
*
|
||||||
* @param msg a valid MuMsg* instance
|
* @param msg a valid MuMsg* instance
|
||||||
* @param func a callback function to call for each contact; when
|
* @param func a callback function to call for each contact; when
|
||||||
|
@ -86,9 +152,9 @@ typedef void (*MuMsgPartForeachFunc) (MuMsgPart *part, gpointer data);
|
||||||
* @param user_data a user-provide pointer that will be passed to the callback
|
* @param user_data a user-provide pointer that will be passed to the callback
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void mu_msg_msg_part_foreach (MuMsg *msg,
|
void mu_msg_part_foreach (MuMsg *msg, MuMsgPartForeachFunc func,
|
||||||
MuMsgPartForeachFunc func,
|
gpointer user_data);
|
||||||
gpointer user_data);
|
|
||||||
|
|
||||||
|
|
||||||
#endif /*__MU_MSG_PART_H__*/
|
#endif /*__MU_MSG_PART_H__*/
|
||||||
|
|
16
src/mu-msg.h
16
src/mu-msg.h
|
@ -125,20 +125,7 @@ const char* mu_msg_get_body_html (MuMsg *msg);
|
||||||
*/
|
*/
|
||||||
const char* mu_msg_get_summary (MuMsg *msg, size_t max_lines);
|
const char* mu_msg_get_summary (MuMsg *msg, size_t max_lines);
|
||||||
|
|
||||||
/**
|
|
||||||
* save a specific attachment to some targetdir
|
|
||||||
*
|
|
||||||
* @param msg a valid MuMsg instance
|
|
||||||
* @param wanted_idx index of the attachment you want to save
|
|
||||||
* @param targetdir filesystem directory to save the attachment
|
|
||||||
* @param overwrite overwrite existing files?
|
|
||||||
* @param play try to 'play' (open) the saved mime-part after saving
|
|
||||||
*
|
|
||||||
* @return TRUE if saving succeeded, FALSE otherwise
|
|
||||||
*/
|
|
||||||
gboolean mu_msg_mime_part_save (MuMsg *msg, unsigned wanted_idx,
|
|
||||||
const char *targetdir, gboolean overwrite,
|
|
||||||
gboolean play);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the sender (From:) of this message
|
* get the sender (From:) of this message
|
||||||
|
@ -304,6 +291,7 @@ MuMsgPrio mu_msg_get_prio (MuMsg *msg);
|
||||||
*/
|
*/
|
||||||
time_t mu_msg_get_timestamp (MuMsg *msg);
|
time_t mu_msg_get_timestamp (MuMsg *msg);
|
||||||
|
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /*__MU_MSG_H__*/
|
#endif /*__MU_MSG_H__*/
|
||||||
|
|
Loading…
Reference in New Issue