Last active
August 29, 2015 14:10
-
-
Save skx/4f0de38a21f4488ba52c to your computer and use it in GitHub Desktop.
Proof of concept binding for lumail 2.x
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This is a simple piece of proof of concept for lumail2. | |
* | |
* The code does three things: | |
* | |
* Creates a Lua-callable "Maildir" object. | |
* | |
* Creates a Lua-callable "Message" object. | |
* | |
* Allows a maildir to return an array of messages. | |
* | |
* The last point is the reason why this sample was created, because | |
* I wanted to be able to write some Lua like this: | |
* | |
** | |
* | |
* dir = Maildir.new( "/home/skx/Maildir/.tldp" ) | |
* tmp = dir:messages() | |
* | |
* for k,v in pairs(tmp) do | |
* print( k .. " -> " .. v:path() ) | |
* end | |
* | |
** | |
* | |
* This code seems to work, but I suspect a Lua developer might have | |
* words to say about the style. | |
* | |
*/ | |
extern "C" | |
{ | |
#include <lua.h> | |
#include <lauxlib.h> | |
#include <lualib.h> | |
} | |
#include <algorithm> | |
#include <cstdlib> | |
#include <dirent.h> | |
#include <fcntl.h> | |
#include <fstream> | |
#include <iostream> | |
#include <sstream> | |
#include <string.h> | |
#include <string> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
#include <unordered_map> | |
#include <vector> | |
#include <gmime/gmime.h> | |
/** | |
* Maildir object. | |
* | |
* This is the C++ implementation of the maildir class. | |
* | |
*/ | |
class CMaildir | |
{ | |
public: | |
CMaildir (const std::string name) | |
{ | |
m_path = name; | |
}; | |
static bool is_directory (std::string path) | |
{ | |
struct stat sb; | |
if (stat (path.c_str (), &sb) < 0) | |
return false; | |
return (S_ISDIR (sb.st_mode)); | |
}; | |
bool exists () | |
{ | |
std::vector < std::string > dirs; | |
dirs.push_back (m_path); | |
dirs.push_back (m_path + "/cur"); | |
dirs.push_back (m_path + "/tmp"); | |
dirs.push_back (m_path + "/new"); | |
for (std::vector < std::string >::iterator it = dirs.begin (); | |
it != dirs.end (); ++it) | |
{ | |
if (!CMaildir::is_directory (*it)) | |
return false; | |
} | |
return true; | |
}; | |
/** | |
* This method is bogus. Ideally we'd cache C++ objects | |
* on the mtime of the directory and return a vector of CMessage | |
* objects. | |
* | |
* Instead we're returning a vector of paths. | |
*/ | |
std::vector< std::string > messages() | |
{ | |
std::vector < std::string > tmp; | |
dirent *de; | |
DIR *dp; | |
/** | |
* Directories we search. | |
*/ | |
std::vector<std::string> dirs; | |
dirs.push_back(m_path + "/cur/"); | |
dirs.push_back(m_path + "/new/"); | |
/** | |
* For each directory. | |
*/ | |
for (std::vector < std::string >::iterator it = dirs.begin (); | |
it != dirs.end (); ++it) | |
{ | |
std::string path = *it; | |
dp = opendir(path.c_str()); | |
if (dp) | |
{ | |
while (true) | |
{ | |
de = readdir(dp); | |
if (de == NULL) | |
break; | |
/** Maybe we should check for DT_REG || DT_LNK ? */ | |
if ( (de->d_type != DT_DIR) | |
|| ( de->d_type == DT_UNKNOWN && !CMaildir::is_directory (std::string(path + de->d_name)))) | |
{ | |
if ( de->d_name[0] != '.' ) | |
{ | |
tmp.push_back( path + de->d_name); | |
} | |
} | |
} | |
closedir(dp); | |
} | |
} | |
return tmp; | |
}; | |
std::string path () | |
{ | |
return (m_path); | |
}; | |
int count () | |
{ | |
std::vector< std::string > tmp = messages(); | |
return tmp.size(); | |
}; | |
~CMaildir () | |
{ | |
} | |
private: | |
std::string m_path; | |
}; | |
/** | |
* This is the C++ object which represents an email message. | |
*/ | |
class CMessage | |
{ | |
public: | |
CMessage (const std::string name) | |
{ | |
m_path = name; | |
}; | |
std::string path () | |
{ | |
return (m_path); | |
}; | |
/** | |
* Pretend we parsed the message with GMIME to return the | |
* value of a header. | |
*/ | |
std::string header( std::string name ) | |
{ | |
return( "HEADER " + name ); | |
}; | |
std::unordered_map<std::string, std::string> headers() | |
{ | |
std::unordered_map<std::string, std::string> m_header_values; | |
GMimeMessage *m_message; | |
GMimeParser *parser; | |
GMimeStream *stream; | |
int fd; | |
if ((fd = open( m_path.c_str(), O_RDONLY, 0)) == -1) | |
throw "Opening the message failed"; | |
stream = g_mime_stream_fs_new (fd); | |
parser = g_mime_parser_new_with_stream (stream); | |
g_object_unref (stream); | |
m_message = g_mime_parser_construct_message (parser); | |
g_object_unref (parser); | |
const char *name; | |
const char *value; | |
/** | |
* Prepare to iterate. | |
*/ | |
GMimeHeaderList *ls = GMIME_OBJECT (m_message)->headers; | |
GMimeHeaderIter *iter = g_mime_header_iter_new (); | |
if (g_mime_header_list_get_iter (ls, iter) && g_mime_header_iter_first (iter)) | |
{ | |
while (g_mime_header_iter_is_valid (iter)) | |
{ | |
/** | |
* Get the name + value. | |
*/ | |
name = g_mime_header_iter_get_name (iter); | |
value = g_mime_header_iter_get_value (iter); | |
/** | |
* Downcase the name. | |
*/ | |
std::string nm(name); | |
std::transform(nm.begin(), nm.end(), nm.begin(), tolower); | |
/** | |
* Decode the value. | |
*/ | |
char *decoded = g_mime_utils_header_decode_text ( value ); | |
m_header_values[nm] = decoded; | |
if (!g_mime_header_iter_next (iter)) | |
break; | |
} | |
} | |
g_mime_header_iter_free (iter); | |
g_object_unref(m_message); | |
return( m_header_values ); | |
} | |
~CMessage () | |
{ | |
} | |
private: | |
/** | |
* The path on-disk to the message. | |
*/ | |
std::string m_path; | |
}; | |
/** | |
* Binding for CMaildir | |
*/ | |
int | |
l_CMaildir_constructor (lua_State * l) | |
{ | |
const char *name = luaL_checkstring (l, 1); | |
CMaildir **udata = (CMaildir **) lua_newuserdata (l, sizeof (CMaildir *)); | |
*udata = new CMaildir (name); | |
luaL_getmetatable (l, "luaL_CMaildir"); | |
lua_setmetatable (l, -2); | |
return 1; | |
} | |
CMaildir * | |
l_CheckCMaildir (lua_State * l, int n) | |
{ | |
return *(CMaildir **) luaL_checkudata (l, n, "luaL_CMaildir"); | |
} | |
int | |
l_CMaildir_path (lua_State * l) | |
{ | |
CMaildir *foo = l_CheckCMaildir (l, 1); | |
lua_pushstring (l, foo->path ().c_str ()); | |
return 1; | |
} | |
int | |
l_CMaildir_count (lua_State * l) | |
{ | |
CMaildir *foo = l_CheckCMaildir (l, 1); | |
lua_pushinteger (l, foo->count() ); | |
return 1; | |
} | |
/** | |
* Does the maildir exist? | |
*/ | |
int | |
l_CMaildir_exists(lua_State *l ) | |
{ | |
CMaildir *foo = l_CheckCMaildir (l, 1); | |
if ( foo->exists() ) | |
lua_pushboolean(l, 1 ); | |
else | |
lua_pushboolean(l, 0 ); | |
return 1; | |
} | |
/** | |
* Return a C++ CMessage object for each message. | |
* | |
* Suspect this is broken - but it seems to work. | |
*/ | |
int | |
l_CMaildir_messages(lua_State *l ) | |
{ | |
CMaildir *m = l_CheckCMaildir (l, 1); | |
std::vector<std::string> tmp = m->messages(); | |
lua_createtable(l, tmp.size(), 0); | |
int i=0; | |
for (std::vector < std::string >::iterator it = tmp.begin (); | |
it != tmp.end (); ++it) | |
{ | |
CMessage **udata = (CMessage **) lua_newuserdata (l, sizeof (CMessage *)); | |
*udata = new CMessage (*it); | |
luaL_getmetatable(l, "luaL_CMessage"); | |
lua_setmetatable(l, -2); | |
lua_rawseti(l, -2, i+1); | |
i++; | |
} | |
return 1; | |
} | |
int | |
l_CMaildir_destructor (lua_State * l) | |
{ | |
CMaildir *foo = l_CheckCMaildir (l, 1); | |
delete foo; | |
return 0; | |
} | |
void InitMaildir (lua_State * l) | |
{ | |
luaL_Reg sFooRegs[] = { | |
{"new", l_CMaildir_constructor}, | |
{"path", l_CMaildir_path}, | |
{"count", l_CMaildir_count}, | |
{"messages", l_CMaildir_messages}, | |
{"exists", l_CMaildir_exists}, | |
{"__gc", l_CMaildir_destructor}, | |
{NULL, NULL} | |
}; | |
luaL_newmetatable (l, "luaL_CMaildir"); | |
#if LUA_VERSION_NUM == 501 | |
luaL_register (l, NULL, sFooRegs); | |
#elif LUA_VERSION_NUM == 502 | |
luaL_setfuncs (l, sFooRegs, 0); | |
#else | |
#error unsupported Lua version | |
#endif | |
lua_pushvalue (l, -1); | |
lua_setfield (l, -1, "__index"); | |
lua_setglobal (l, "Maildir"); | |
} | |
/** | |
* Binding for CMessage | |
*/ | |
int | |
l_CMessage_constructor (lua_State * l) | |
{ | |
const char *name = luaL_checkstring (l, 1); | |
CMessage **udata = (CMessage **) lua_newuserdata (l, sizeof (CMessage *)); | |
*udata = new CMessage (name); | |
luaL_getmetatable (l, "luaL_CMessage"); | |
lua_setmetatable (l, -2); | |
return 1; | |
} | |
CMessage * | |
l_CheckCMessage (lua_State * l, int n) | |
{ | |
return *(CMessage **) luaL_checkudata (l, n, "luaL_CMessage"); | |
} | |
int | |
l_CMessage_path (lua_State * l) | |
{ | |
CMessage *foo = l_CheckCMessage (l, 1); | |
lua_pushstring (l, foo->path ().c_str ()); | |
return 1; | |
} | |
int | |
l_CMessage_headers (lua_State * l) | |
{ | |
/** | |
* Get the headers. | |
*/ | |
CMessage *foo = l_CheckCMessage (l, 1); | |
std::unordered_map<std::string, std::string> headers = foo->headers(); | |
/** | |
* Create the table. | |
*/ | |
lua_newtable(l); | |
for ( auto it = headers.begin(); it != headers.end(); ++it ) | |
{ | |
std::string name = it->first; | |
std::string value = it->second; | |
lua_pushstring(l,name.c_str() ); | |
if ( ! value.empty() ) | |
lua_pushstring(l,value.c_str()); | |
else | |
lua_pushstring(l, "[EMPTY]" ); | |
lua_settable(l,-3); | |
} | |
return(1); | |
} | |
int | |
l_CMessage_destructor (lua_State * l) | |
{ | |
CMessage *foo = l_CheckCMessage (l, 1); | |
delete foo; | |
return 0; | |
} | |
void InitMessage( lua_State * l) | |
{ | |
luaL_Reg sFooRegs[] = { | |
{"new", l_CMessage_constructor}, | |
{"path", l_CMessage_path}, | |
{"headers", l_CMessage_headers}, | |
{"__gc", l_CMaildir_destructor}, | |
{NULL, NULL} | |
}; | |
luaL_newmetatable (l, "luaL_CMessage"); | |
#if LUA_VERSION_NUM == 501 | |
luaL_register (l, NULL, sFooRegs); | |
#elif LUA_VERSION_NUM == 502 | |
luaL_setfuncs (l, sFooRegs, 0); | |
#else | |
#error unsupported Lua version | |
#endif | |
lua_pushvalue (l, -1); | |
lua_setfield (l, -1, "__index"); | |
lua_setglobal (l, "Message"); | |
} | |
/** | |
* The entry point to our code. | |
*/ | |
int main( int argc, char *argv[]) | |
{ | |
/** | |
* Initi mime. | |
*/ | |
g_mime_init(0); | |
/** | |
* Setup Lua | |
*/ | |
lua_State *l = luaL_newstate (); | |
luaL_openlibs (l); | |
/** | |
* Setup our objects. | |
*/ | |
InitMaildir(l); | |
InitMessage(l); | |
/** | |
* Load the script. | |
*/ | |
int erred = luaL_dofile (l, "driver.lua"); | |
if (erred) | |
std::cout << "Lua error: " << luaL_checkstring (l, -1) << std::endl; | |
/** | |
* All done. | |
*/ | |
lua_close (l); | |
g_mime_shutdown(); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- | |
-- Parse a message | |
-- | |
msg = Message.new( "/home/skx/Maildir/.people.blaydtwo/cur/1178920394.000024.mbox:2,S" ); | |
-- | |
-- Output the headers it has. | |
-- | |
for k,v in pairs( msg:headers() ) do | |
print( "Header " .. k .. " has value '" .. v .. "'" ) | |
end | |
-- | |
-- Now we'll switch to operating on messages in folders. | |
-- | |
c = Maildir.new( "/home/skx/Maildir/.steve.org.uk" ) | |
print( "There are " .. c:count() .. " messages in " .. c:path() ) | |
-- | |
-- Now we want to get all the messages | |
-- | |
tmp = c:messages() | |
for k,v in ipairs(tmp) do | |
print( k ) | |
print( "\tPATH: " .. v:path() ) | |
local headers = v:headers() | |
if ( headers['subject'] ) then | |
print( "\tSubject: " .. v:headers()['subject'] ) | |
end | |
if ( headers['from'] ) then | |
print( "\tSender: " .. v:headers()['from'] ) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Build the driver. | |
# | |
all: driver | |
clean: | |
rm -f *.o core driver || true | |
# | |
# Build the driver | |
# | |
driver: Makefile driver.cc | |
g++ -ggdb -std=c++11 -Wall -Werror -o driver driver.cc $(shell pkg-config --cflags --libs lua5.2) $(shell pkg-config --libs --cflags gmime-2.6) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment