eh idk lols
sophuwu sophie@skisiel.com
Tue, 23 Jul 2024 16:39:01 +0200
D
db.go
@@ -1,157 +0,0 @@
-package main - -import ( - "bytes" - "encoding/json" - "github.com/asdine/storm" - "github.com/asdine/storm/q" - "go.etcd.io/bbolt" - "net" - "os" - "os/signal" -) - -func DB() error { - bb, err := storm.Open(DBPATH, storm.BoltOptions(0600, nil)) - if err != nil { - return err - } - var e EmailMeta - err = bb.Init(&e) - if err != nil { - return err - } - err = bb.Bolt.Update(func(tx *bbolt.Tx) error { - _, err = tx.CreateBucketIfNotExists([]byte("data")) - return err - }) - if err != nil { - return err - } - db = bb - return nil -} - -var db *storm.DB - -func Listen() { - err := DB() - FtlLog(err) - _ = os.Remove(SOCK) - var sock *net.UnixListener - sock, err = net.ListenUnix("unix", &net.UnixAddr{SOCK, "socket"}) - FtlLog(err) - - // handle signals - go func() { - chn := make(chan os.Signal, 1) - signal.Notify(chn, os.Kill, os.Interrupt) - <-chn - db.Close() - sock.Close() - os.Remove(SOCK) - FtlLog(err) - os.Exit(0) - }() - var conn *net.UnixConn - for { - conn, err = sock.AcceptUnix() - if err != nil { - continue - } - go Handle(conn) - } -} - -type Req struct { - CMD string `json:"CMD"` - Data []byte `json:"Data"` -} - -func sendToSock(req Req) []byte { - var conn *net.UnixConn - var err error - conn, err = net.DialUnix("unix", &net.UnixAddr{ - Name: SOCK, - Net: "local", - }, &net.UnixAddr{Name: SOCK, Net: "socket"}) - FtlLog(err) - b, err := json.Marshal(req) - FtlLog(err) - var n int - conn.WriteTo(b, &net.UnixAddr{Name: SOCK, Net: "socket"}) - n, err = conn.Write(b) - FtlLog(err) - println(n) - conn.ReadFrom(b) - - return b -} - -func Search(req Req) []byte { - var err error - var search string - if err = json.Unmarshal(req.Data, &search); err != nil { - return []byte("error unmarshalling request") - } - println(search) - search += "*" - var emails []EmailMeta - query := db.Select(q.Or(q.Re("Subject", search), q.Re("From", search), q.Re("To", search))) - _ = query.Find(&emails) - var b []byte - b, _ = json.Marshal(emails) - return b -} - -func Handle(conn *net.UnixConn) { - var b bytes.Buffer - var buf = make([]byte, 1024) - var n int - var err error - for { - buf = make([]byte, 1024) - n, err = conn.Read(buf) - if err != nil || n == 0 { - break - } - b.Write(buf[:n]) - } - var req Req - err = json.Unmarshal(b.Bytes(), &req) - if err != nil { - return - } - var bb []byte - switch req.CMD { - case "SEARCH": - bb = Search(req) - break - case "PUT": - bb = Put(req) - break - } - n, err = conn.Write(bb) - if err != nil { - return - } - println(n) - conn.Close() -} - -type PUT struct { - M EmailMeta `json:"M"` - D []byte `json:"D"` -} - -func Put(req Req) []byte { - var put PUT - if json.Unmarshal(req.Data, &put) != nil { - return []byte("ERR") - } - db.Save(&put.M) - db.Update(func(tx *bbolt.Tx) error { - return tx.Bucket([]byte("data")).Put(put.M.Id, put.D) - }) - return []byte("OK") -}
M
go.mod
→
go.mod
@@ -9,7 +9,14 @@ golang.org/x/net v0.27.0
) require ( - github.com/asdine/storm v2.1.2+incompatible // indirect - go.etcd.io/bbolt v1.3.10 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/glebarez/go-sqlite v1.22.0 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/sys v0.22.0 // indirect + modernc.org/libc v1.37.6 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/sqlite v1.28.0 // indirect )
M
go.sum
→
go.sum
@@ -2,11 +2,26 @@ github.com/DusanKasan/parsemail v1.2.0 h1:CrzTL1nuPLxB41aO4zE/Tzc9GVD8jjifUftlbTKQQl4=
github.com/DusanKasan/parsemail v1.2.0/go.mod h1:B9lfMbpVe4DMqPImAOCGti7KEwasnRTrKKn66iQefVs= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/asdine/storm v2.1.2+incompatible h1:dczuIkyqwY2LrtXPz8ixMrU/OFgZp71kbKTHGrXYt/Q= -github.com/asdine/storm v2.1.2+incompatible/go.mod h1:RarYDc9hq1UPLImuiXK3BIWPJLdIygvV3PsInK0FbVQ= -go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= -go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
M
main.go
→
main.go
@@ -1,63 +1,73 @@
package main import ( - "encoding/json" + "database/sql" "fmt" + _ "github.com/glebarez/go-sqlite" "os" "path/filepath" ) -var DBPATH, SOCK, LOG string +var DBPATH, INBOX, SAVEPATH string -func init() { +func getHomeBox() string { home, err := os.UserHomeDir() - FtlLog(err) - if home == "" { + if err != nil || home == "" { + fmt.Fprintln(os.Stderr, err) os.Exit(1) } - if _, err := os.Stat(filepath.Join(home, ".mailbox")); os.IsNotExist(err) { - os.Mkdir(filepath.Join(home, ".mailbox"), 0755) - } - DBPATH = filepath.Join(home, ".mailbox", "mail.storm") - SOCK = filepath.Join(home, ".mailbox", "mail.sock") - LOG = filepath.Join(home, ".mailbox", "box.log") + return filepath.Join(home, ".mailbox") } -func FtlLog(e error) { - if e == nil { - return +func init() { + var mailbox string + if len(os.Args) > 2 && os.Args[1] == "-m" { + mailbox = os.Args[2] + } else { + mailbox = getHomeBox() } - log, _ := os.OpenFile(LOG, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664) - fmt.Fprintln(log, "Fatal: ", e) - log.Close() - os.Exit(1) + var err error + if _, err = os.Stat(mailbox); os.IsNotExist(err) { + os.MkdirAll(mailbox, 0700) + } + DBPATH = filepath.Join(mailbox, "mailbox.sqlite") + INBOX = filepath.Join(mailbox, "inbox", "new") + if _, err = os.Stat(INBOX); os.IsNotExist(err) { + os.MkdirAll(INBOX, 0700) + } + SAVEPATH = filepath.Join(mailbox, "saved") + if _, err = os.Stat(SAVEPATH); os.IsNotExist(err) { + os.MkdirAll(SAVEPATH, 0700) + } } func main() { - if (len(os.Args) > 0 && os.Args[0] == "mailbox-parser") || (len(os.Args) > 1 && os.Args[1] == "parse") { - meta, filebr := Parse() - var put PUT - put.M = meta - put.D = filebr.Bytes() - b, err := json.Marshal(put) - FtlLog(err) - var r Req - r.CMD = "PUT" - r.Data = b - FtlLog(err) - b = sendToSock(r) - fmt.Println(string(b)) + db, err := sql.Open("sqlite", DBPATH) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + _, err = db.Exec("CREATE TABLE IF NOT EXISTS emails (id TEXT PRIMARY KEY, subject TEXT, toaddr TEXT, fromaddr TEXT, date TEXT)") + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + defer func() { + db.Exec("COMMIT") + db.Close() + }() + var newEmails []EmailMeta + err = ScanDir(&newEmails) + for _, em := range newEmails { + _, err = db.Exec("INSERT INTO emails (id, subject, toaddr, fromaddr, date) VALUES (?, ?, ?, ?, ?)", em.Id, em.Subject, em.To, em.From, em.Date) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } } - if (len(os.Args) > 0 && os.Args[0] == "mailbox-db") || (len(os.Args) > 1 && os.Args[1] == "db") { - Listen() + if err != nil { + fmt.Fprintln(os.Stderr, err) return - } else if len(os.Args) > 1 && os.Args[1] == "search" { - var r Req - r.CMD = "SEARCH" - b, _ := json.Marshal(os.Args[2]) - r.Data = b - fmt.Println(string(sendToSock(r))) - } }
M
parse.go
→
parse.go
@@ -6,12 +6,14 @@ "crypto/sha1"
_ "encoding/json" "fmt" "github.com/andybalholm/brotli" - _ "github.com/asdine/storm" "mime" "net/mail" "os" + "path/filepath" "time" ) + +const TimeFormat = "2006-01-02 15:04:05 -0700" func Brotli(buf *bytes.Buffer) error { data := buf.Bytes()@@ -24,29 +26,73 @@ }
return writer.Close() } -func Parse() (EmailMeta, bytes.Buffer) { +func SaveEmail(em EmailMeta, email bytes.Buffer) error { + path := filepath.Join(SAVEPATH, em.Id+".br") + err := os.WriteFile(path, email.Bytes(), 0600) + return err +} + +func NewEntry(f os.DirEntry) (EmailMeta, error) { + if !f.Type().IsRegular() { + return EmailMeta{}, fmt.Errorf("unsupported file type in directory") + } + meta, email, err := Parse(filepath.Join(INBOX, f.Name())) + if err != nil { + return meta, err + } + err = SaveEmail(meta, email) + if err != nil { + return meta, err + } + err = os.Remove(filepath.Join(INBOX, f.Name())) + return meta, err +} + +func ScanDir(newEmails *[]EmailMeta) error { + dir, err := os.ReadDir(INBOX) + if err != nil { + return err + } + var meta EmailMeta + for _, f := range dir { + meta, err = NewEntry(f) + if err != nil { + return err + } + *newEmails = append(*newEmails, meta) + } + return nil +} + +func Parse(path string) (EmailMeta, bytes.Buffer, error) { var email bytes.Buffer - _, err := email.ReadFrom(os.Stdin) - FtlLog(err) - meta, err := GenerateMeta(email) - FtlLog(err) - FtlLog(Brotli(&email)) - return meta, email + var meta EmailMeta + b, err := os.ReadFile(path) + if err != nil { + return meta, email, err + } + email.Write(b) + meta, err = GenerateMeta(email) + if err != nil { + return meta, email, err + } + err = Brotli(&email) + return meta, email, err } -func dateHeader(e *mail.Header) time.Time { +func dateHeader(e *mail.Header) string { var d time.Time var err error d, err = e.Date() if err != nil { d = time.Now().Local() } - return d + return d.Format(TimeFormat) } -func ShaHash(b []byte) []byte { +func ShaHash(b []byte) string { h := sha1.New() h.Write(b) - return h.Sum(nil) + return fmt.Sprintf("%X", h.Sum(nil)) } // GenerateMeta generates the EmailMeta for the EmailData@@ -106,9 +152,9 @@ }
// EmailMeta contains the fields that will be searchable in the database type EmailMeta struct { - From string `json:"From" storm:"index"` - To string `json:"To" storm:"index"` - Subject string `json:"Subject" storm:"index"` - Date time.Time `json:"Date" storm:"index"` - Id []byte `json:"Id" storm:"unique,id"` + From string `json:"From"` + To string `json:"To"` + Subject string `json:"Subject"` + Date string `json:"Date"` + Id string `json:"Id" ` }