Skip to content

Instantly share code, notes, and snippets.

@skx
Last active August 29, 2015 14:10
Show Gist options
  • Save skx/4f0de38a21f4488ba52c to your computer and use it in GitHub Desktop.
Save skx/4f0de38a21f4488ba52c to your computer and use it in GitHub Desktop.
Proof of concept binding for lumail 2.x
/**
* 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;
}
--
-- 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
#
# 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