2023-03-15 05:39:19 +00:00
|
|
|
require "process"
|
|
|
|
require "io"
|
2023-03-26 17:29:07 +00:00
|
|
|
require "socket"
|
2023-03-15 05:39:19 +00:00
|
|
|
|
2023-03-24 05:36:20 +00:00
|
|
|
require "./config"
|
2023-03-23 13:44:41 +00:00
|
|
|
|
2023-03-29 05:55:06 +00:00
|
|
|
at_exit { GC.collect }
|
2023-03-23 13:44:41 +00:00
|
|
|
|
2023-03-29 05:55:06 +00:00
|
|
|
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
|
2023-03-23 02:11:08 +00:00
|
|
|
end
|
|
|
|
|
2023-03-15 05:39:19 +00:00
|
|
|
enum WinType
|
|
|
|
LIST
|
|
|
|
READER
|
|
|
|
COMPOSE
|
|
|
|
end
|
2023-03-16 05:26:31 +00:00
|
|
|
|
|
|
|
class Mesg
|
2023-03-29 05:55:06 +00:00
|
|
|
@type : MsgType
|
2023-03-16 05:26:31 +00:00
|
|
|
@data : Slice(UInt8)
|
|
|
|
|
|
|
|
def type
|
|
|
|
@type
|
|
|
|
end
|
|
|
|
|
|
|
|
def data
|
|
|
|
@data
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(@type, @data)
|
|
|
|
end
|
|
|
|
end
|
2023-03-15 05:39:19 +00:00
|
|
|
|
|
|
|
class ChildWindow
|
2023-03-29 05:55:06 +00:00
|
|
|
include Taro
|
|
|
|
|
2023-03-22 05:06:29 +00:00
|
|
|
@@msg : Channel(Mesg) = Channel(Mesg).new
|
|
|
|
|
|
|
|
def self.msg
|
|
|
|
@@msg
|
|
|
|
end
|
2023-03-24 05:36:20 +00:00
|
|
|
|
2023-03-16 05:26:31 +00:00
|
|
|
@lifetime : Channel(UInt8)
|
2023-03-15 05:39:19 +00:00
|
|
|
@stdout_w : IO::FileDescriptor
|
|
|
|
@stdout_r : IO::FileDescriptor
|
|
|
|
@stdin_w : IO::FileDescriptor
|
|
|
|
@stdin_r : IO::FileDescriptor
|
2023-03-16 05:26:31 +00:00
|
|
|
@decoding : Bool = false
|
2023-03-18 07:18:02 +00:00
|
|
|
@sizing : Bool = false
|
2023-03-16 05:26:31 +00:00
|
|
|
@szshort : UInt16 = 0
|
2023-03-15 05:39:19 +00:00
|
|
|
|
2023-03-23 13:44:41 +00:00
|
|
|
def initialize(w : WinType = WinType::LIST, arg : String = "")
|
2023-03-15 05:39:19 +00:00
|
|
|
@stdout_r, @stdout_w = IO.pipe
|
|
|
|
@stdin_r, @stdin_w = IO.pipe
|
2023-03-16 05:26:31 +00:00
|
|
|
@lifetime = Channel(UInt8).new
|
2023-03-15 05:39:19 +00:00
|
|
|
|
2023-03-23 13:44:41 +00:00
|
|
|
case w
|
|
|
|
when WinType::LIST then
|
|
|
|
spawn do
|
2023-03-24 05:36:20 +00:00
|
|
|
Process.run(
|
2023-03-29 18:11:47 +00:00
|
|
|
command: "#{TARO_LIB}/#{UXN_EMU}",
|
2023-07-24 05:12:17 +00:00
|
|
|
args: [ "taro-ls" ],
|
2023-03-24 05:36:20 +00:00
|
|
|
chdir: TARO_LIB,
|
|
|
|
input: @stdin_r,
|
|
|
|
output: @stdout_w,
|
|
|
|
error: Process::Redirect::Inherit)
|
2023-03-23 13:44:41 +00:00
|
|
|
@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)
|
2023-03-29 05:55:06 +00:00
|
|
|
@@msg.send(Mesg.new(MsgType::UPDATE_UI, Slice(UInt8).new(0)))
|
2023-03-23 13:44:41 +00:00
|
|
|
end
|
|
|
|
when WinType::COMPOSE then
|
|
|
|
spawn do
|
|
|
|
Process.run(command: COMPOSE_PROG, shell: true)
|
2023-03-29 05:55:06 +00:00
|
|
|
@@msg.send(Mesg.new(MsgType::UPDATE_UI, Slice(UInt8).new(0)))
|
2023-03-16 05:26:31 +00:00
|
|
|
end
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
2023-03-23 13:44:41 +00:00
|
|
|
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
|
|
|
|
2023-03-22 05:06:29 +00:00
|
|
|
def lifetime
|
|
|
|
@lifetime
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
|
|
|
|
2023-03-29 05:55:06 +00:00
|
|
|
def write_msg(msgtype : MsgType, data : Slice)
|
2023-03-23 13:44:41 +00:00
|
|
|
msgsz = UInt16.new(data.size > MSG_SIZES[msgtype] ? MSG_SIZES[msgtype] : data.size)
|
2023-03-29 05:55:06 +00:00
|
|
|
msgtype.value.to_io(@stdin_w, IO::ByteFormat::BigEndian)
|
2023-03-21 05:32:49 +00:00
|
|
|
msgsz.to_io(@stdin_w, IO::ByteFormat::BigEndian)
|
2023-03-15 05:39:19 +00:00
|
|
|
|
2023-03-23 13:44:41 +00:00
|
|
|
if msgsz == MSG_SIZES[msgtype]
|
2023-03-15 05:39:19 +00:00
|
|
|
@stdin_w.write(data[0..msgsz - 2])
|
|
|
|
10_u8.to_io(@stdin_w, IO::ByteFormat::BigEndian)
|
2023-03-18 07:18:02 +00:00
|
|
|
elsif msgsz != 0
|
2023-03-15 05:39:19 +00:00
|
|
|
@stdin_w.write(data[0..msgsz - 1])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-16 05:26:31 +00:00
|
|
|
def read_msg
|
2023-03-29 05:55:06 +00:00
|
|
|
msgType : MsgType = MsgType::MBOX_LIST
|
2023-03-16 05:26:31 +00:00
|
|
|
data : Slice(UInt8) = Slice(UInt8).new(0)
|
|
|
|
loop do
|
|
|
|
if @stdout_r.closed?
|
2023-03-18 07:18:02 +00:00
|
|
|
@decoding = false
|
|
|
|
@sizing = false
|
|
|
|
@szshort = 0
|
2023-03-16 05:26:31 +00:00
|
|
|
break
|
|
|
|
end
|
|
|
|
if !@decoding
|
2023-03-29 05:55:06 +00:00
|
|
|
msgType = MsgType.new(@stdout_r.read_byte || 0_u8)
|
2023-03-16 05:26:31 +00:00
|
|
|
@decoding = true
|
2023-03-18 07:18:02 +00:00
|
|
|
@sizing = true
|
|
|
|
elsif @sizing
|
2023-03-16 05:26:31 +00:00
|
|
|
szslice = Slice(UInt8).new(2)
|
|
|
|
@stdout_r.read_fully(szslice)
|
2023-03-23 02:11:08 +00:00
|
|
|
@szshort = u8arr_tou16(szslice)
|
2023-03-18 07:18:02 +00:00
|
|
|
@sizing = false
|
2023-03-21 04:32:11 +00:00
|
|
|
if @szshort == 0
|
|
|
|
@decoding = false
|
|
|
|
break
|
|
|
|
end
|
2023-03-16 05:26:31 +00:00
|
|
|
else
|
|
|
|
slc = Slice(UInt8).new(@szshort)
|
|
|
|
@stdout_r.read_fully(slc)
|
|
|
|
data = slc
|
2023-03-21 04:32:11 +00:00
|
|
|
@decoding = false
|
|
|
|
@szshort = 0
|
2023-03-16 05:26:31 +00:00
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return Mesg.new(msgType, data)
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
2023-03-16 05:26:31 +00:00
|
|
|
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
|
|
|
|
2023-03-31 04:33:12 +00:00
|
|
|
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
|
|
|
|
|
2023-03-15 05:39:19 +00:00
|
|
|
class MblazeProxy
|
2023-03-23 13:44:41 +00:00
|
|
|
@@mailbox : String = "INBOX"
|
2023-03-25 17:25:50 +00:00
|
|
|
@@search_regex : String = ""
|
2023-03-17 04:44:39 +00:00
|
|
|
|
2023-03-23 13:44:41 +00:00
|
|
|
def self.mailbox
|
|
|
|
@@mailbox
|
2023-03-17 04:44:39 +00:00
|
|
|
end
|
2023-03-18 07:18:02 +00:00
|
|
|
|
2023-03-25 17:25:50 +00:00
|
|
|
def self.search_regex
|
|
|
|
@@search_regex
|
|
|
|
end
|
|
|
|
|
2023-03-31 03:13:35 +00:00
|
|
|
private def run_cmd(cmdtxt : String) : IO::Memory
|
2023-03-31 04:33:12 +00:00
|
|
|
io = CappedIO.new
|
2023-03-18 07:18:02 +00:00
|
|
|
Process.run(cmdtxt, shell: true, output: io)
|
2023-03-31 03:13:35 +00:00
|
|
|
return io
|
2023-03-18 07:18:02 +00:00
|
|
|
end
|
2023-03-17 04:44:39 +00:00
|
|
|
|
2023-03-31 03:13:35 +00:00
|
|
|
def list_mail : IO::Memory
|
2023-03-25 17:25:50 +00:00
|
|
|
if @@search_regex != ""
|
|
|
|
return search_mail(@@search_regex, false, false)
|
|
|
|
end
|
2023-03-21 04:52:51 +00:00
|
|
|
cmd = "
|
2023-03-25 17:25:50 +00:00
|
|
|
mbox=#{MBOX_ROOT}/#{@@mailbox}
|
2023-03-15 05:39:19 +00:00
|
|
|
|
|
|
|
mdirs ${mbox} | xargs minc > /dev/null
|
|
|
|
mlist ${mbox} | msort -dr | mseq -S | mscan"
|
|
|
|
|
2023-03-21 04:52:51 +00:00
|
|
|
return run_cmd(cmd)
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
2023-03-16 05:26:31 +00:00
|
|
|
|
2023-03-31 03:13:35 +00:00
|
|
|
def list_mboxes : IO::Memory
|
2023-03-25 17:25:50 +00:00
|
|
|
cmd = "echo 'INBOX' ; for x in $(mdirs -a #{MBOX_ROOT} | sort | grep -v INBOX); do echo ${x##{MBOX_ROOT}/}; done"
|
2023-03-16 05:26:31 +00:00
|
|
|
|
2023-03-21 04:52:51 +00:00
|
|
|
return run_cmd(cmd)
|
2023-03-16 05:26:31 +00:00
|
|
|
end
|
2023-03-17 04:44:39 +00:00
|
|
|
|
2023-03-19 20:10:38 +00:00
|
|
|
def mark_all_read
|
2023-03-21 04:52:51 +00:00
|
|
|
cmd = "mflag -S :"
|
|
|
|
run_cmd(cmd)
|
2023-03-19 20:10:38 +00:00
|
|
|
end
|
|
|
|
|
2023-03-17 04:44:39 +00:00
|
|
|
def set_mbox(box : String)
|
2023-03-23 13:44:41 +00:00
|
|
|
@@mailbox = box
|
2023-03-25 17:25:50 +00:00
|
|
|
@@search_regex = ""
|
2023-03-17 04:44:39 +00:00
|
|
|
end
|
2023-03-21 04:52:51 +00:00
|
|
|
|
2023-03-23 02:11:08 +00:00
|
|
|
def trash_mail(range_start : UInt16, range_end : UInt16)
|
2023-03-25 17:25:50 +00:00
|
|
|
cmd = "mrefile #{range_start}:#{range_end} #{MBOX_ROOT}/Trash"
|
2023-03-22 05:06:29 +00:00
|
|
|
|
2023-03-23 02:11:08 +00:00
|
|
|
# destroy mail if we are trashing what's already in the trash!
|
2023-03-23 13:44:41 +00:00
|
|
|
if @@mailbox == "Trash"
|
2023-03-23 02:11:08 +00:00
|
|
|
cmd = "for x in $(mseq #{range_start}:#{range_end}); do rm $x; done"
|
|
|
|
end
|
2023-03-21 04:52:51 +00:00
|
|
|
run_cmd(cmd)
|
|
|
|
end
|
|
|
|
|
2023-03-23 02:11:08 +00:00
|
|
|
def refile_mail(range_start : UInt16, range_end : UInt16, to_mbox : String)
|
2023-03-26 17:29:07 +00:00
|
|
|
to_mbox = to_mbox.gsub("'", "\'").gsub("../", "./").gsub("..", "")
|
|
|
|
cmd = "mrefile #{range_start}:#{range_end} '#{MBOX_ROOT}/#{to_mbox}'"
|
2023-03-21 04:52:51 +00:00
|
|
|
run_cmd(cmd)
|
|
|
|
end
|
|
|
|
|
2023-03-31 03:13:35 +00:00
|
|
|
def search_mail(query : String, body : Bool, case_sensitive : Bool) : IO::Memory
|
2023-03-26 17:29:07 +00:00
|
|
|
query = query.gsub("'", "\'")
|
2023-03-25 17:25:50 +00:00
|
|
|
@@search_regex = query
|
2023-03-26 17:29:07 +00:00
|
|
|
cmd = "mlist #{MBOX_ROOT}/#{@@mailbox} | magrep #{case_sensitive ? "" : "-i"} #{body ? "/" : "*"}:'#{query}' | msort -dr | uniq | mseq -S | mscan"
|
2023-03-21 04:52:51 +00:00
|
|
|
return run_cmd(cmd)
|
|
|
|
end
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class TaroCtl
|
|
|
|
|
2023-03-29 05:55:06 +00:00
|
|
|
include Taro
|
|
|
|
|
2023-03-15 05:39:19 +00:00
|
|
|
@mblaze : MblazeProxy
|
|
|
|
@lsWin : ChildWindow
|
2023-03-29 05:55:06 +00:00
|
|
|
@socket : UNIXServer
|
2023-03-15 05:39:19 +00:00
|
|
|
|
|
|
|
def initialize
|
|
|
|
@lsWin = ChildWindow.new
|
|
|
|
@mblaze = MblazeProxy.new
|
2023-03-29 05:55:06 +00:00
|
|
|
|
|
|
|
File.delete?("#{TARO_LIB}/taro.sock")
|
|
|
|
@socket = UNIXServer.new("#{TARO_LIB}/taro.sock")
|
|
|
|
|
2023-03-15 05:39:19 +00:00
|
|
|
list = @mblaze.list_mail
|
2023-03-18 07:18:02 +00:00
|
|
|
mboxes = @mblaze.list_mboxes
|
2023-03-29 05:55:06 +00:00
|
|
|
@lsWin.write_msg(MsgType::MBOX_LIST, mboxes.to_slice)
|
|
|
|
@lsWin.write_msg(MsgType::MAIL_LIST, list.to_slice)
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
|
|
|
|
2023-03-29 05:55:06 +00:00
|
|
|
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
|
2023-03-16 06:13:05 +00:00
|
|
|
end
|
2023-03-29 05:55:06 +00:00
|
|
|
|
2023-03-15 05:39:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
taro = Taro::TaroCtl.new
|
2023-03-29 05:55:06 +00:00
|
|
|
taro.run
|
2023-03-26 21:36:05 +00:00
|
|
|
|