require "process" require "io" require "socket" require "./config" MSG_SIZES = { 0_u8 => 4096_u16, 2_u8 => 32768_u16, } def u8arr_tou16(s : Slice(UInt8)) : UInt16 if s.size < 2 return 0_u16 else low : UInt16 = UInt16.new(s[0])*256 high : UInt16 = UInt16.new(s[1]) return low + high end end module Taro enum WinType LIST READER COMPOSE end class Mesg @type : UInt8 @data : Slice(UInt8) def type @type end def data @data end def initialize(@type, @data) end end class ChildWindow @@msg : Channel(Mesg) = Channel(Mesg).new def self.msg @@msg end @lifetime : Channel(UInt8) @stdout_w : IO::FileDescriptor @stdout_r : IO::FileDescriptor @stdin_w : IO::FileDescriptor @stdin_r : IO::FileDescriptor @decoding : Bool = false @sizing : Bool = false @szshort : UInt16 = 0 def initialize(w : WinType = WinType::LIST, arg : String = "") @stdout_r, @stdout_w = IO.pipe @stdin_r, @stdin_w = IO.pipe @lifetime = Channel(UInt8).new uxnrom : String = "" uxnargs = ["-s", "1", "#{TARO_LIB}/#{uxnrom}.rom"] case w when WinType::LIST then spawn do Process.run( command: UXN_EMU, args: [ "-s", "1", "taro-ls" ], shell: true, chdir: TARO_LIB, input: @stdin_r, output: @stdout_w, error: Process::Redirect::Inherit) @lifetime.send(0) end spawn do loop do @@msg.send(read_msg) end end when WinType::READER then spawn do Process.run(command: READER_PROG + " " + arg, shell: true) @@msg.send(Mesg.new(15_u8, Slice(UInt8).new(0))) end when WinType::COMPOSE then spawn do Process.run(command: COMPOSE_PROG, shell: true) @@msg.send(Mesg.new(15_u8, Slice(UInt8).new(0))) end end end def lifetime @lifetime end def write_msg(msgtype : UInt8, data : Slice) puts "original message size: " + data.size.to_s msgsz = UInt16.new(data.size > MSG_SIZES[msgtype] ? MSG_SIZES[msgtype] : data.size) puts "truncated message size: " + msgsz.to_s msgtype.to_io(@stdin_w, IO::ByteFormat::BigEndian) msgsz.to_io(@stdin_w, IO::ByteFormat::BigEndian) if msgsz == MSG_SIZES[msgtype] @stdin_w.write(data[0..msgsz - 2]) 10_u8.to_io(@stdin_w, IO::ByteFormat::BigEndian) elsif msgsz != 0 @stdin_w.write(data[0..msgsz - 1]) end end def read_msg msgType : UInt8 = 0 data : Slice(UInt8) = Slice(UInt8).new(0) loop do if @stdout_r.closed? @decoding = false @sizing = false @szshort = 0 break end if !@decoding msgType = @stdout_r.read_byte || 0_u8 @decoding = true @sizing = true elsif @sizing szslice = Slice(UInt8).new(2) @stdout_r.read_fully(szslice) @szshort = u8arr_tou16(szslice) @sizing = false if @szshort == 0 @decoding = false break end else slc = Slice(UInt8).new(@szshort) @stdout_r.read_fully(slc) data = slc @decoding = false @szshort = 0 break end end puts data return Mesg.new(msgType, data) end end class MblazeProxy @@mailbox : String = "INBOX" @@search_regex : String = "" def self.mailbox @@mailbox end def self.search_regex @@search_regex end private def run_cmd(cmdtxt : String) : String puts cmdtxt io = IO::Memory.new Process.run(cmdtxt, shell: true, output: io) return io.to_s end def list_mail : String if @@search_regex != "" return search_mail(@@search_regex, false, false) end cmd = " mbox=#{MBOX_ROOT}/#{@@mailbox} mdirs ${mbox} | xargs minc > /dev/null mlist ${mbox} | msort -dr | mseq -S | mscan" return run_cmd(cmd) end def list_mboxes : String cmd = "echo 'INBOX' ; for x in $(mdirs -a #{MBOX_ROOT} | sort | grep -v INBOX); do echo ${x##{MBOX_ROOT}/}; done" return run_cmd(cmd) end def mark_all_read cmd = "mflag -S :" run_cmd(cmd) end def set_mbox(box : String) @@mailbox = box @@search_regex = "" end def trash_mail(range_start : UInt16, range_end : UInt16) cmd = "mrefile #{range_start}:#{range_end} #{MBOX_ROOT}/Trash" # destroy mail if we are trashing what's already in the trash! if @@mailbox == "Trash" cmd = "for x in $(mseq #{range_start}:#{range_end}); do rm $x; done" end run_cmd(cmd) end def refile_mail(range_start : UInt16, range_end : UInt16, to_mbox : String) to_mbox = to_mbox.gsub("'", "\'").gsub("../", "./").gsub("..", "") cmd = "mrefile #{range_start}:#{range_end} '#{MBOX_ROOT}/#{to_mbox}'" run_cmd(cmd) end def search_mail(query : String, body : Bool, case_sensitive : Bool) : String query = query.gsub("'", "\'") @@search_regex = query cmd = "mlist #{MBOX_ROOT}/#{@@mailbox} | magrep #{case_sensitive ? "" : "-i"} #{body ? "/" : "*"}:'#{query}' | msort -dr | uniq | mseq -S | mscan" return run_cmd(cmd) end end class TaroCtl @mblaze : MblazeProxy @lsWin : ChildWindow @readWins : Array(ChildWindow) @composeWins : Array(ChildWindow) def initialize @lsWin = ChildWindow.new @readWins = Array(ChildWindow).new @composeWins = Array(ChildWindow).new @mblaze = MblazeProxy.new list = @mblaze.list_mail mboxes = @mblaze.list_mboxes @lsWin.write_msg(0_u8, mboxes.to_slice) @lsWin.write_msg(2_u8, list.to_slice) end def mblaze @mblaze end def mainWindow @lsWin end def readWins @readWins end def composeWins @composeWins end end end taro = Taro::TaroCtl.new srv = UNIXServer.new("#{TARO_LIB}/taro.sock") spawn do while client = srv.accept? spawn do b = Slice(UInt8).new(1) client.read(b) Taro::ChildWindow.msg.send(Taro::Mesg.new(15_u8, Slice(UInt8).new(0))) end end end loop do select when taro.mainWindow.lifetime.receive? srv.close exit when m = Taro::ChildWindow.msg.receive case m.type when 1 then taro.mblaze.set_mbox(String.new(m.data)) taro.mainWindow.write_msg(2_u8, taro.mblaze.list_mail.to_slice) when 3 then taro.mblaze.mark_all_read taro.mainWindow.write_msg(2_u8, taro.mblaze.list_mail.to_slice) when 5 then search_results = taro.mblaze.search_mail(String.new(m.data), false, false) taro.mainWindow.write_msg(2_u8, search_results.to_slice) when 7 then range_start = u8arr_tou16(m.data[0..1]) range_end = u8arr_tou16(m.data[2..3]) dest_mbox = String.new(m.data[4..]) taro.mblaze.refile_mail(range_start, range_end, dest_mbox) taro.mainWindow.write_msg(2_u8, taro.mblaze.list_mail.to_slice) when 9 then range_start = u8arr_tou16(m.data[0..1]) range_end = u8arr_tou16(m.data[2..3]) taro.mblaze.trash_mail(range_start, range_end) taro.mainWindow.write_msg(2_u8, taro.mblaze.list_mail.to_slice) when 11 then mmsg = u8arr_tou16(m.data[0..1]) Taro::ChildWindow.new(Taro::WinType::READER, mmsg.to_s) when 13 then Taro::ChildWindow.new(Taro::WinType::COMPOSE) when 15 then taro.mainWindow.write_msg(2_u8, taro.mblaze.list_mail.to_slice) end end end