1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-03-19 21:28:07 +02:00
mailpit/internal/storage/database.go

194 lines
4.1 KiB
Go
Raw Normal View History

// Package storage handles all database actions
2022-07-29 23:23:08 +12:00
package storage
import (
"context"
"database/sql"
2022-07-29 23:23:08 +12:00
"fmt"
"os"
"os/signal"
"path"
"path/filepath"
2022-07-29 23:23:08 +12:00
"syscall"
"time"
"github.com/axllent/mailpit/config"
2023-09-25 18:08:04 +13:00
"github.com/axllent/mailpit/internal/logger"
"github.com/klauspost/compress/zstd"
"github.com/leporo/sqlf"
// sqlite (native) - https://gitlab.com/cznic/sqlite
_ "modernc.org/sqlite"
2022-07-29 23:23:08 +12:00
)
var (
db *sql.DB
dbFile string
dbIsTemp bool
dbLastAction time.Time
// zstd compression encoder & decoder
dbEncoder, _ = zstd.NewWriter(nil)
dbDecoder, _ = zstd.NewReader(nil)
2022-07-29 23:23:08 +12:00
)
// InitDB will initialise the database
2022-07-29 23:23:08 +12:00
func InitDB() error {
p := config.DataFile
if p == "" {
// when no path is provided then we create a temporary file
// which will get deleted on Close(), SIGINT or SIGTERM
p = fmt.Sprintf("%s-%d.db", path.Join(os.TempDir(), "mailpit"), time.Now().UnixNano())
dbIsTemp = true
logger.Log().Debugf("[db] using temporary database: %s", p)
2022-07-29 23:23:08 +12:00
} else {
p = filepath.Clean(p)
2022-07-29 23:23:08 +12:00
}
config.DataFile = p
logger.Log().Debugf("[db] opening database %s", p)
2022-07-29 23:23:08 +12:00
var err error
dsn := fmt.Sprintf("file:%s?cache=shared", p)
2022-07-29 23:23:08 +12:00
db, err = sql.Open("sqlite", dsn)
2022-07-29 23:23:08 +12:00
if err != nil {
return err
2022-07-29 23:23:08 +12:00
}
// prevent "database locked" errors
// @see https://github.com/mattn/go-sqlite3#faq
db.SetMaxOpenConns(1)
2022-07-29 23:23:08 +12:00
// SQLite performance tuning (https://phiresky.github.io/blog/2020/sqlite-performance-tuning/)
_, err = db.Exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = normal;")
if err != nil {
return err
}
// create tables if necessary & apply migrations
if err := dbApplyMigrations(); err != nil {
return err
2022-07-29 23:23:08 +12:00
}
dbFile = p
dbLastAction = time.Now()
2022-07-29 23:23:08 +12:00
sigs := make(chan os.Signal, 1)
// catch all signals since not explicitly listing
// Program that will listen to the SIGINT and SIGTERM
// SIGINT will listen to CTRL-C.
// SIGTERM will be caught if kill command executed
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
// method invoked upon seeing signal
go func() {
s := <-sigs
fmt.Printf("[db] got %s signal, shutting down\n", s)
Close()
os.Exit(0)
}()
2022-07-29 23:23:08 +12:00
// auto-prune & delete
go dbCron()
2022-07-29 23:23:08 +12:00
go dataMigrations()
return nil
}
2022-07-29 23:23:08 +12:00
// Close will close the database, and delete if a temporary table
func Close() {
if db != nil {
if err := db.Close(); err != nil {
logger.Log().Warn("[db] error closing database, ignoring")
2022-07-29 23:23:08 +12:00
}
}
2022-07-29 23:23:08 +12:00
if dbIsTemp && isFile(dbFile) {
logger.Log().Debugf("[db] deleting temporary file %s", dbFile)
if err := os.Remove(dbFile); err != nil {
logger.Log().Errorf("[db] %s", err.Error())
2022-07-29 23:23:08 +12:00
}
}
}
// StatsGet returns the total/unread statistics for a mailbox
func StatsGet() MailboxStats {
var (
total = CountTotal()
unread = CountUnread()
tags = GetAllTags()
)
dbLastAction = time.Now()
return MailboxStats{
Total: total,
Unread: unread,
Tags: tags,
2022-08-07 09:34:06 +12:00
}
}
2022-08-07 09:34:06 +12:00
// CountTotal returns the number of emails in the database
func CountTotal() int {
var total int
2022-08-07 09:34:06 +12:00
_ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total).
QueryRowAndClose(context.TODO(), db)
2022-08-07 09:34:06 +12:00
return total
}
2022-08-07 09:34:06 +12:00
// CountUnread returns the number of emails in the database that are unread.
func CountUnread() int {
var total int
2022-08-07 09:34:06 +12:00
_ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total).
Where("Read = ?", 0).
QueryRowAndClose(context.TODO(), db)
2022-08-07 09:34:06 +12:00
return total
}
// CountRead returns the number of emails in the database that are read.
func CountRead() int {
var total int
_ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total).
Where("Read = ?", 1).
QueryRowAndClose(context.TODO(), db)
return total
}
// IsUnread returns whether a message is unread or not.
func IsUnread(id string) bool {
var unread int
_ = sqlf.From("mailbox").
Select("COUNT(*)").To(&unread).
Where("Read = ?", 0).
Where("ID = ?", id).
QueryRowAndClose(context.TODO(), db)
return unread == 1
2022-08-07 09:34:06 +12:00
}
// MessageIDExists checks whether a Message-ID exists in the DB
func MessageIDExists(id string) bool {
var total int
_ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total).
Where("MessageID = ?", id).
QueryRowAndClose(context.TODO(), db)
return total != 0
}