taro/taro-ctl.cr

327 lines
8.3 KiB
Crystal

require "process"
require "io"
require "socket"
require "./config"
at_exit { GC.collect }
module Taro
enum MsgType : UInt8
MBOX_LIST
GET_MBOX
MAIL_LIST
MARK_ALL_READ
SEARCH_MAIL = 5
REFILE_MAIL = 7
TRASH_MAIL = 9
READ_MAIL = 11
COMPOSE_MAIL = 13
UPDATE_UI = 15
end
MSG_SIZES = {
MsgType::MBOX_LIST => 4096_u16,
MsgType::MAIL_LIST => 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
enum WinType
LIST
READER
COMPOSE
end
class Mesg
@type : MsgType
@data : Slice(UInt8)
def type
@type
end
def data
@data
end
def initialize(@type, @data)
end
end
class ChildWindow
include Taro
@@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
case w
when WinType::LIST then
spawn do
Process.run(
command: "#{TARO_LIB}/#{UXN_EMU}",
args: [ "taro-ls" ],
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(MsgType::UPDATE_UI, Slice(UInt8).new(0)))
end
when WinType::COMPOSE then
spawn do
Process.run(command: COMPOSE_PROG, shell: true)
@@msg.send(Mesg.new(MsgType::UPDATE_UI, Slice(UInt8).new(0)))
end
end
end
def lifetime
@lifetime
end
def write_msg(msgtype : MsgType, data : Slice)
msgsz = UInt16.new(data.size > MSG_SIZES[msgtype] ? MSG_SIZES[msgtype] : data.size)
msgtype.value.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 : MsgType = MsgType::MBOX_LIST
data : Slice(UInt8) = Slice(UInt8).new(0)
loop do
if @stdout_r.closed?
@decoding = false
@sizing = false
@szshort = 0
break
end
if !@decoding
msgType = MsgType.new(@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
return Mesg.new(msgType, data)
end
end
class CappedIO < IO::Memory
def write(slice : Bytes)
if slice.size >= MSG_SIZES[MsgType::MAIL_LIST]
super(slice[0..MSG_SIZES[MsgType::MAIL_LIST] - 1])
else
super(slice)
end
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) : IO::Memory
io = CappedIO.new
Process.run(cmdtxt, shell: true, output: io)
return io
end
def list_mail : IO::Memory
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 : IO::Memory
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) : IO::Memory
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
include Taro
@mblaze : MblazeProxy
@lsWin : ChildWindow
@socket : UNIXServer
def initialize
@lsWin = ChildWindow.new
@mblaze = MblazeProxy.new
File.delete?("#{TARO_LIB}/taro.sock")
@socket = UNIXServer.new("#{TARO_LIB}/taro.sock")
list = @mblaze.list_mail
mboxes = @mblaze.list_mboxes
@lsWin.write_msg(MsgType::MBOX_LIST, mboxes.to_slice)
@lsWin.write_msg(MsgType::MAIL_LIST, list.to_slice)
end
def run
spawn do
while client = @socket.accept?
spawn do
b = Slice(UInt8).new(1)
client.read(b)
ChildWindow.msg.send(Mesg.new(MsgType::UPDATE_UI, Slice(UInt8).new(0)))
end
end
end
loop do
select
when @lsWin.lifetime.receive?
@socket.close
exit
when m = ChildWindow.msg.receive
case m.type
when MsgType::GET_MBOX then
@mblaze.set_mbox(String.new(m.data))
@lsWin.write_msg(MsgType::MAIL_LIST, @mblaze.list_mail.to_slice)
when MsgType::MARK_ALL_READ then
@mblaze.mark_all_read
@lsWin.write_msg(MsgType::MAIL_LIST, @mblaze.list_mail.to_slice)
when MsgType::SEARCH_MAIL then
search_results = @mblaze.search_mail(String.new(m.data), false, false)
@lsWin.write_msg(MsgType::MAIL_LIST, search_results.to_slice)
when MsgType::REFILE_MAIL then
range_start = u8arr_tou16(m.data[0..1])
range_end = u8arr_tou16(m.data[2..3])
dest_mbox = String.new(m.data[4..])
@mblaze.refile_mail(range_start, range_end, dest_mbox)
@lsWin.write_msg(MsgType::MAIL_LIST, @mblaze.list_mail.to_slice)
when MsgType::TRASH_MAIL then
range_start = u8arr_tou16(m.data[0..1])
range_end = u8arr_tou16(m.data[2..3])
@mblaze.trash_mail(range_start, range_end)
@lsWin.write_msg(MsgType::MAIL_LIST, @mblaze.list_mail.to_slice)
when MsgType::READ_MAIL then
mmsg = u8arr_tou16(m.data[0..1])
ChildWindow.new(WinType::READER, mmsg.to_s)
when MsgType::COMPOSE_MAIL then
ChildWindow.new(WinType::COMPOSE)
when MsgType::UPDATE_UI then
@lsWin.write_msg(MsgType::MAIL_LIST, @mblaze.list_mail.to_slice)
end
end
end
end
end
end
taro = Taro::TaroCtl.new
taro.run