git.sophuwu.com > mailboxxer   
              235
            
             package db

import (
	"bytes"
	"crypto/sha1"
	_ "encoding/json"
	"fmt"
	"mime"
	"net/mail"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const TimeFormat = "2006-01-02 15:04:05 -0700"

func saveEmail(em EmailMeta, files FileList) error {
	path := filepath.Join(SAVEPATH, em.Id)
	var err error
	_ = os.MkdirAll(path, 0700)
	for name, data := range files {
		err = os.WriteFile(filepath.Join(path, name), data, 0600)
		if err != nil {
			return err
		}
	}
	return err
}

func newEntry(f os.DirEntry) error {
	if !f.Type().IsRegular() {
		return fmt.Errorf("unsupported file type in directory")
	}
	meta, files, err := parse(filepath.Join(INBOX, f.Name()))
	if err != nil {
		return fmt.Errorf("error parsing email: %w", err)
	}
	if meta.Date == "" {
		s, _ := f.Info()
		meta.Date = s.ModTime().Format(TimeFormat)
	}
	_, err = db.Exec("INSERT INTO emails (id, subject, toaddr, fromaddr, date) VALUES (?, ?, ?, ?, ?)", meta.Id, meta.Subject, meta.To, meta.From, meta.Date)
	if err != nil {
		return fmt.Errorf("error inserting email into database: %w", err)
	}
	err = saveEmail(meta, files)
	if err != nil {
		return fmt.Errorf("error saving email files: %w", err)
	}
	err = os.Remove(filepath.Join(INBOX, f.Name()))
	if err != nil {
		return fmt.Errorf("error cleaning tmp file: %w", err)
	}
	return nil
}

func parseNewMail() error {
	dir, err := os.ReadDir(INBOX)
	if err != nil {
		return err
	}
	for _, f := range dir {
		err = newEntry(f)
		if err != nil {
			return err
		}
	}
	return nil
}

func parse(path string) (EmailMeta, FileList, error) {
	var email bytes.Buffer
	var meta EmailMeta
	var files FileList
	b, err := os.ReadFile(path)
	if err != nil {
		return meta, files, err
	}
	email.Write(b)
	meta, err = generateMeta(email)
	if err != nil {
		return meta, files, err
	}
	files, err = getFiles(&email)
	if err != nil {
		return meta, files, err
	}
	return meta, files, err
}

func dateHeader(e *mail.Header) string {
	var d time.Time
	var err error
	d, err = e.Date()
	if err != nil {
		return ""
	}
	return d.Format(TimeFormat)
}
func shaHash(b []byte) string {
	h := sha1.New()
	h.Write(b)
	return fmt.Sprintf("%X", h.Sum(nil))
}

func decodeR(s string) string {
	dec := new(mime.WordDecoder)
	decoded, _ := dec.DecodeHeader(s)
	return decoded
}

var decode = func(d mime.WordDecoder) func(s *string) {
	return func(s *string) {
		if ss, ers := d.DecodeHeader(fmt.Sprintf(*s)); ers == nil {
			*s = ss
		}
	}
}(mime.WordDecoder{})

// generateMeta generates the EmailMeta for the EmailData
// This is used to index the email in the database
func generateMeta(email bytes.Buffer) (EmailMeta, error) {
	var em EmailMeta
	em.Id = shaHash(email.Bytes())
	em.Subject = "No Subject"

	e, err := mail.ReadMessage(bytes.NewReader(email.Bytes()))
	if err != nil {
		return em, err
	}

	em.To = func() string {
		if len(e.Header.Get("To")) > 0 {
			return e.Header.Get("To")
		}
		if len(e.Header.Get("X-Original-To")) > 2 {
			return e.Header.Get("X-Original-To")
		}
		if e.Header.Get("Delivered-To") != "" {
			return e.Header.Get("Delivered-To")
		}
		return "Unknown Recipient"
	}()
	decode(&em.To)
	em.From = func() string {
		if len(e.Header.Get("From")) > 0 {
			return e.Header.Get("From")
		}
		if len(e.Header.Get("Return-Path")) > 2 {
			return e.Header.Get("Return-Path")
		}
		if e.Header.Get("Sender") != "" {
			return e.Header.Get("Sender")
		}
		return "Unknown Sender"
	}()
	decode(&em.From)
	if s := e.Header.Get("Subject"); s != "" {
		em.Subject = s
	}
	decode(&em.Subject)
	em.Date = dateHeader(&e.Header)

	return em, nil
}

func TimeStr(s string) string {
	t, err := time.Parse(TimeFormat, s)
	if err != nil {
		return s
	}
	n := time.Now().Local()
	return strings.ReplaceAll(func() string {
		if t.Year() != n.Year() {
			return t.Format("Jan 02th 2006")
		}
		d := time.Since(t)
		if d.Hours() > 24*6 {
			return t.Format("Jan 02th")
		}
		if d.Hours() > 24 {
			return t.Format("Mon 15:04")
		}
		if d.Hours() > 1 {
			return fmt.Sprintf("%d h ago", int(d.Hours()))
		}
		return fmt.Sprintf("%d m ago", int(d.Minutes()))
	}(), "th", (func(day int) string {
		if day/10 == 1 {
			return "th"
		}
		switch day % 10 {
		case 1:
			return "st"
		case 2:
			return "nd"
		case 3:
			return "rd"
		default:
			return "th"
		}
	})(t.Day()))
}

// EmailMeta contains the fields that will be searchable in the database
type EmailMeta struct {
	From    string `json:"From"`
	To      string `json:"To"`
	Subject string `json:"Subject"`
	Date    string `json:"Date"`
	Id      string `json:"Id" `
}

func stdin() {
	var err error
	var b bytes.Buffer
	b.ReadFrom(os.Stdin)
	fl, e := getFiles(&b)
	if e != nil {
		fmt.Fprintln(os.Stderr, e)
		return
	}
	path := filepath.Dir(DBPATH)
	path = filepath.Join(path, "stdin")
	_ = os.MkdirAll(path, 0700)
	for name, data := range fl {
		err = os.WriteFile(filepath.Join(path, name), data, 0600)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			return
		}
	}
	os.Exit(0)
}