diff --git a/src/mu-msg-file.c b/src/mu-msg-file.c new file mode 100644 index 00000000..c31722c2 --- /dev/null +++ b/src/mu-msg-file.c @@ -0,0 +1,290 @@ +/* +** Copyright (C) 2010 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. +** +*/ + +/* hopefully, the should get us a sane PATH_MAX */ +#include +/* not all systems provide PATH_MAX in limits.h */ +#ifndef PATH_MAX +#include +#ifndef PATH_MAX +#define PATH_MAX MAXPATHLEN +#endif /*!PATH_MAX */ +#endif /*PATH_MAX */ + +#include +#include "mu-msg-file.h" + +/* + * is this a 'new' msg or a 'cur' msg?; if new, we return + * (in info) a ptr to the info part + */ +enum _MsgType { + MSG_TYPE_CUR, + MSG_TYPE_NEW, + MSG_TYPE_OTHER +}; +typedef enum _MsgType MsgType; + +static MsgType +check_msg_type (const char *path, char **info) +{ + char *dir, *file; + MsgType mtype; + + /* try to find the info part */ + /* note that we can use either the ':' or '!' as separator; + * the former is the official, but as it does not work on e.g. VFAT + * file systems, some Maildir implementations use the latter instead + * (or both). For example, Tinymail/modest does this. The python + * documentation at http://docs.python.org/lib/mailbox-maildir.html + * mentions the '!' as well as a 'popular choice' + */ + + *info = NULL; + dir = g_path_get_dirname(path); + file = g_path_get_basename(path); + + if (!(*info = strrchr(file, ':'))) + *info = strrchr(file, '!'); /* Tinymail */ + if (*info) + ++(*info); /* skip the ':' or '!' */ + + if (g_str_has_suffix(dir, G_DIR_SEPARATOR_S "cur")) { + if (!*info) + g_debug("'cur' file, but no info part: %s", path); + mtype = MSG_TYPE_CUR; + } else if (g_str_has_suffix(dir, G_DIR_SEPARATOR_S "new")) { + if (*info) + g_debug("'new' file, ignoring info part: %s", path); + mtype = MSG_TYPE_NEW; + } else + mtype = MSG_TYPE_OTHER; /* file has been added explicitly as + a single message */ + if (*info) + *info = g_strdup(*info); + + g_free(dir); + g_free(file); + + return mtype; +} + + +MuMsgFlags +mu_msg_file_get_flags_from_path (const char *path) +{ + MuMsgFlags flags; + MsgType mtype; + char *info = NULL; + + g_return_val_if_fail (path, MU_MSG_FLAG_NONE); + g_return_val_if_fail (!g_str_has_suffix(path, G_DIR_SEPARATOR_S), + MU_MSG_FLAG_NONE); + + mtype = check_msg_type (path, &info); + if (mtype == MSG_TYPE_NEW) { /* we ignore any new-msg flags */ + g_free(info); + return MU_MSG_FLAG_NEW; + } + + flags = MU_MSG_FLAG_NONE; + if (mtype == MSG_TYPE_CUR || mtype == MSG_TYPE_OTHER) { + char *cursor = info; + /* only support the "2," format */ + if (cursor && cursor[0] == '2' && cursor[1] == ',') { + cursor += 2; /* jump past 2, */ + for (; *cursor; ++cursor) + switch (*cursor) { + case 'P': + flags |= MU_MSG_FLAG_PASSED; + break; + case 'T': + flags |= MU_MSG_FLAG_TRASHED; + break; + case 'R': + flags |= MU_MSG_FLAG_REPLIED; + break; + case 'S': + flags |= MU_MSG_FLAG_SEEN; + break; + case 'D': + flags |= MU_MSG_FLAG_DRAFT; + break; + case 'F': + flags |= MU_MSG_FLAG_FLAGGED; + break; + } + } + } + g_free(info); + + return flags; +} + + +static const char* +get_flags_str_s (MuMsgFlags flags) +{ + int i; + static char flagstr[7]; + + /* now, determine the flags to use */ + if (flags & MU_MSG_FLAG_DRAFT) + flagstr[i++] = 'D'; + if (flags & MU_MSG_FLAG_FLAGGED) + flagstr[i++] = 'F'; + if (flags & MU_MSG_FLAG_PASSED) + flagstr[i++] = 'P'; + if (flags & MU_MSG_FLAG_REPLIED) + flagstr[i++] = 'R'; + if (flags & MU_MSG_FLAG_SEEN) + flagstr[i++] = 'S'; + if (flags & MU_MSG_FLAG_TRASHED) + flagstr[i++] = 'T'; + + flagstr[i] = '\0'; + + return flagstr; +} + + +/* + * take an exising message path, and return a new path, based on whether it should be in + * 'new' or 'cur'; ie. + * + * /home/user/Maildir/foo/bar/cur/abc:2,F and flags == MU_MSG_FLAG_NEW + * => /home/user/Maildir/foo/bar/new + * and + * /home/user/Maildir/foo/bar/new/abc and flags == MU_MSG_FLAG_REPLIED + * => /home/user/Maildir/foo/bar/cur + * + * so only difference is whether MuMsgFlags matches MU_MSG_FLAG_NEW is set or not + * + */ +static char* +get_new_dir_name (const char* oldpath, MuMsgFlags flags) +{ + char *newpath, *dirpart; + + /* g_path_get_dirname is not explicit about whether it ends in + * a dir-separator (\ or /), so we need to check both */ + const char* cur4 = G_DIR_SEPARATOR_S "cur"; + const char* cur5 = G_DIR_SEPARATOR_S "cur" G_DIR_SEPARATOR_S; + const char* new4 = G_DIR_SEPARATOR_S "new"; + const char* new5 = G_DIR_SEPARATOR_S "new" G_DIR_SEPARATOR_S; + + g_return_val_if_fail (oldpath, NULL); + /* if MU_MSG_FLAG_NEW is set, it must be the only flag */ + g_return_val_if_fail (flags & MU_MSG_FLAG_NEW ? + flags == MU_MSG_FLAG_NEW : TRUE, NULL); + + newpath = g_path_get_dirname (oldpath); + if (g_str_has_suffix (newpath, cur4) || g_str_has_suffix (newpath, new4)) + dirpart = &newpath[strlen(newpath) - strlen(cur4)]; + else if (g_str_has_suffix (newpath, cur5) || g_str_has_suffix (newpath, new5)) + dirpart = &newpath[strlen(newpath) - strlen(cur5)]; + else { + g_warning ("invalid maildir path: %s", oldpath); + g_free (newpath); + return NULL; + } + + /* now, copy the desired dir part behind this */ + if (flags & MU_MSG_FLAG_NEW) + memcpy (dirpart, new4, strlen(new4) + 1); + else + memcpy (dirpart, cur4, strlen(cur4) + 1); + + return newpath; +} + +/* + * get a new filename for the message, based on the new flags; if the + * message has MU_MSG_FLAG_NEW, it will loose its flags + * + */ +static char* +get_new_file_name (const char *oldpath, MuMsgFlags flags) +{ + gchar *newname, *sep; + + /* if MU_MSG_FLAG_NEW is set, it must be the only flag */ + g_return_val_if_fail (flags & MU_MSG_FLAG_NEW ? + flags == MU_MSG_FLAG_NEW : TRUE, NULL); + + /* the normal separator is ':', but on e.g. vfat, '!' is seen + * as well */ + newname = g_path_get_basename (oldpath); + if (!newname) { + g_warning ("invalid path: '%s'", oldpath); + return NULL; + } + + if (!(sep = g_strrstr (newname, ":")) && + !(sep = g_strrstr (newname, "!"))) { + g_warning ("not a valid msg file name: '%s'", oldpath); + g_free (newname); + return NULL; + } + + if (flags & MU_MSG_FLAG_NEW) + sep[0] = '\0'; /* remove all, including ':' or '!' */ + else { + gchar *tmp; + sep[1] = '\0'; /* remove flags, but keep ':' or '!' */ + sep[flags & MU_MSG_FLAG_NEW ? 0 : 1] = '\0'; + tmp = newname; + newname = g_strdup_printf ("%s2,%s", newname, get_flags_str_s (flags)); + g_free (tmp); + } + + return newname; +} + +char* +mu_msg_file_get_path_from_flags (const char *oldpath, MuMsgFlags newflags) +{ + char *newname, *newdir, *newpath; + + g_return_val_if_fail (oldpath, NULL); + g_return_val_if_fail (newflags != MU_MSG_FLAG_NONE, NULL); + /* if MU_MSG_FLAG_NEW is set, it must be the only flag */ + g_return_val_if_fail (newflags & MU_MSG_FLAG_NEW ? + newflags == MU_MSG_FLAG_NEW : TRUE, NULL); + + + newname = get_new_file_name (oldpath, newflags); + if (!newname) + return NULL; + + newdir = get_new_dir_name (oldpath, newflags); + if (!newdir) { + g_free (newname); + return NULL; + } + + newpath = g_strdup_printf ("%s%c%s", newdir, G_DIR_SEPARATOR, newname); + g_free (newname); + g_free (newdir); + + return newpath; +} + + + diff --git a/src/mu-msg-file.h b/src/mu-msg-file.h new file mode 100644 index 00000000..883d9dc8 --- /dev/null +++ b/src/mu-msg-file.h @@ -0,0 +1,62 @@ +/* +** Copyright (C) 2010 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. +** +*/ + +#ifndef __MU_MSG_FILE_H__ +#define __MU_MSG_FILE_H__ + +#include +#include + +G_BEGIN_DECLS + +/** + * get the Maildir flags from the full path of a mailfile. The flags + * are as specified in http://cr.yp.to/proto/maildir.html, plus + * MU_MSG_FLAG_NEW for new messages, ie the ones that live in + * new/. The flags are logically OR'ed. Note that the file does not + * have to exist; the flags are based on the path only. + * + * @param pathname of a mailfile; it does not have to refer to an + * actual message + * + * @return the flags, or MU_MSG_FILE_FLAG_UNKNOWN in case of error + */ +MuMsgFlags mu_msg_file_get_flags_from_path (const char* pathname); + + +/** + * get the new pathname for a message, based on the old path and the + * new flags. Note that setting/removing the MU_MSG_FLAG_NEW will + * change the directory in which a message lives. The flags are as + * specified in http://cr.yp.to/proto/maildir.html, plus + * MU_MSG_FLAG_NEW for new messages, ie the ones that live in + * new/. The flags are logically OR'ed. Note that the file does not + * have to exist; the flags are based on the path only. + * + * @param oldpath the old (current) full path to the message (including the filename) + * @param newflags the new flags for this message + * + * @return a new path name; use g_free when done with. NULL in case of + * error. + */ +char* mu_msg_file_get_path_from_flags (const char *oldpath, MuMsgFlags newflags); + +G_END_DECLS + +#endif /*__MU_MSG_FILE_H__*/ diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index 627123de..4f237416 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -72,9 +72,13 @@ TEST_PROGS += test-mu-store test_mu_store_SOURCES= test-mu-store.c dummy.cc test_mu_store_LDADD= libtestmucommon.la +TEST_PROGS += test-mu-msg-file +test_mu_msg_file_SOURCES= test-mu-msg-file.c +test_mu_msg_file_LDADD= libtestmucommon.la -libtestmucommon_la_SOURCES= \ - test-mu-common.c \ + +libtestmucommon_la_SOURCES= \ + test-mu-common.c \ test-mu-common.h libtestmucommon_la_LIBADD= ../libmu.la diff --git a/src/tests/test-mu-msg-file.c b/src/tests/test-mu-msg-file.c new file mode 100644 index 00000000..51e98dce --- /dev/null +++ b/src/tests/test-mu-msg-file.c @@ -0,0 +1,106 @@ +/* +** Copyright (C) 2008-2010 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. +** +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif /*HAVE_CONFIG_H */ + +#include +#include +#include +#include + +#include + +#include "test-mu-common.h" +#include "src/mu-msg-file.h" + +static void test_mu_msg_file_get_flags_from_path(void) +{ + int i; + + struct { + const char *path; + MuMsgFlags flags; + } paths[] = { + { + "/home/foo/Maildir/test/cur/123456:2,FR", + MU_MSG_FLAG_REPLIED | MU_MSG_FLAG_FLAGGED}, { + "/home/foo/Maildir/test/new/123456", MU_MSG_FLAG_NEW}, { + "/home/foo/Maildir/test/new/123456:2,FR", + MU_MSG_FLAG_NEW}, { + "/home/foo/Maildir/test/cur/123456:2,DTP", + MU_MSG_FLAG_DRAFT | MU_MSG_FLAG_TRASHED | + MU_MSG_FLAG_PASSED}, { + "/home/foo/Maildir/test/cur/123456:2,S", + MU_MSG_FLAG_SEEN} + }; + + for (i = 0; i != G_N_ELEMENTS(paths); ++i) { + MuMsgFlags flags; + flags = mu_msg_file_get_flags_from_path(paths[i].path); + g_assert_cmpuint(flags, ==, paths[i].flags); + } +} + +static void test_mu_msg_file_get_path_from_flags(void) +{ + int i; + + struct { + const char *oldpath; + MuMsgFlags flags; + const char *newpath; + } paths[] = { + { + "/home/foo/Maildir/test/cur/123456:2,FR", + MU_MSG_FLAG_REPLIED, + "/home/foo/Maildir/test/cur/123456:2,R"}, { + "/home/foo/Maildir/test/cur/123456:2,FR", + MU_MSG_FLAG_NEW, + "/home/foo/Maildir/test/new/123456"}, { + "/home/foo/Maildir/test/new/123456:2,FR", + MU_MSG_FLAG_SEEN | MU_MSG_FLAG_REPLIED, + "/home/foo/Maildir/test/cur/123456:2,RS"} + }; + + for (i = 0; i != G_N_ELEMENTS(paths); ++i) { + gchar *str; + str = mu_msg_file_get_path_from_flags(paths[i].oldpath, + paths[i].flags); + g_assert_cmpstr(str, ==, paths[i].newpath); + g_free(str); + } +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/mu-msg-file/mu-msg-file-get-path-from-flags", + test_mu_msg_file_get_path_from_flags); + g_test_add_func("/mu-msg-file/mu-msg-file-get-flags-from-path", + test_mu_msg_file_get_flags_from_path); + + g_log_set_handler(NULL, + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | + G_LOG_FLAG_RECURSION, (GLogFunc) black_hole, NULL); + + return g_test_run(); +}