package db import ( "database/sql" "fmt" _ "github.com/glebarez/go-sqlite" "os" "path/filepath" ) var DBPATH, INBOX, SAVEPATH string func getHomeBox() string { home, err := os.UserHomeDir() if err != nil || home == "" { fmt.Fprintln(os.Stderr, err) os.Exit(1) } return filepath.Join(home, ".mailbox") } func getConf() { var mailbox string if len(os.Args) > 2 && os.Args[1] == "-m" { mailbox = os.Args[2] } else { mailbox = getHomeBox() } 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 readRows(rows *sql.Rows) ([]EmailMeta, error) { var metas []EmailMeta var meta EmailMeta var err error for rows.Next() { err = rows.Scan(&meta.Id, &meta.Subject, &meta.To, &meta.From, &meta.Date) if err != nil { return metas, err } metas = append(metas, meta) } return metas, nil } var db *sql.DB func openDB() error { var err error db, err = sql.Open("sqlite", DBPATH) if err != nil { return err } if db == nil { return fmt.Errorf("unknown reason") } _, err = db.Exec("CREATE TABLE IF NOT EXISTS emails (id TEXT PRIMARY KEY, subject TEXT, toaddr TEXT, fromaddr TEXT, date TEXT)") if err != nil { return err } return nil } func Open() { getConf() err := openDB() if err != nil { fmt.Fprintln(os.Stderr, "error opening database: ", err) os.Exit(1) } err = parseNewMail() if err != nil { fmt.Fprintln(os.Stderr, "error parsing new mail: ", err) os.Exit(1) return } } func Close() { if db != nil { if err := db.Close(); err != nil { fmt.Fprintln(os.Stderr, "error closing database: ", err) } db = nil } } type Query struct { rows []EmailMeta page int pageSize int totalRows int totalPages int where string } func (r *Query) Page() int { return r.page + 1 } func (r *Query) PageSize() int { return r.pageSize } func (r *Query) TotalRows() int { return r.totalRows } func (r *Query) TotalPages() int { return r.totalPages + 1 } func (r *Query) Rows() []EmailMeta { return r.rows } func (r *Query) Row(i int) (EmailMeta, error) { if i < 0 || i >= len(r.rows) { return EmailMeta{}, fmt.Errorf("index out of range: %d", i) } return r.rows[i], nil } func (r *Query) whereClause(q *string) { if r.where != "" { *q += " WHERE " + r.where } } func (r *Query) executeQuery() error { if db == nil { return fmt.Errorf("database not opened") } q := `select count(id) from emails` r.whereClause(&q) row := db.QueryRow(q) if row == nil { return fmt.Errorf("query error: no rows returned") } if err := row.Scan(&r.totalRows); err != nil { return fmt.Errorf("query error: %w", err) } if r.pageSize <= 0 { r.pageSize = 5 } else if r.pageSize > 100 { r.pageSize = 100 } if r.totalRows == 0 { r.rows = []EmailMeta{} r.page = 0 r.totalPages = 0 return nil } r.totalPages = (r.totalRows - 1) / r.pageSize if r.page < 0 { r.page = 0 } else if r.page > r.totalPages { r.page = r.totalPages } q = `SELECT * FROM emails` r.whereClause(&q) q += fmt.Sprintf(" ORDER BY date DESC LIMIT %d OFFSET %d", r.pageSize, r.page*r.pageSize) rows, err := db.Query(q) if err != nil { return fmt.Errorf("query error: %w", err) } r.rows, err = readRows(rows) if err != nil { return fmt.Errorf("error reading rows: %w", err) } return nil } func (r *Query) Next() error { if r.page >= r.totalPages { return nil } r.page++ return r.executeQuery() } func (r *Query) Prev() error { if r.page <= 0 { return nil } r.page-- return r.executeQuery() } func (r *Query) SetWhere(where string) error { r.where = where r.page = 0 return r.executeQuery() } func (r *Query) GetWhere() string { return r.where } func (r *Query) SetPage(page int) error { r.page = page return r.executeQuery() } func (r *Query) SetPageSize(size int) error { if size <= 0 { return fmt.Errorf("page size must be greater than zero") } if size > 100 { size = 100 } r.pageSize = size return r.executeQuery() } func NewQuery(pageSize int) (*Query, error) { r := &Query{ page: 0, pageSize: pageSize, where: "", } if err := r.executeQuery(); err != nil { return nil, fmt.Errorf("error executing query: %w", err) } return r, nil }