mirror of
https://github.com/axllent/mailpit.git
synced 2024-12-28 23:06:43 +02:00
Feature: Add reindex
subcommand to reindex all messages
This commit is contained in:
parent
a3f83ea5ce
commit
2b18b1bee1
40
cmd/reindex.go
Normal file
40
cmd/reindex.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2022-Now() Ralph Slooten
|
||||||
|
This file is part of a CLI application.
|
||||||
|
*/
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/axllent/mailpit/config"
|
||||||
|
"github.com/axllent/mailpit/internal/logger"
|
||||||
|
"github.com/axllent/mailpit/internal/storage"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// reindexCmd represents the reindex command
|
||||||
|
var reindexCmd = &cobra.Command{
|
||||||
|
Use: "reindex <database>",
|
||||||
|
Short: "Reindex the database",
|
||||||
|
Long: `This will reindex all messages in the entire database.
|
||||||
|
|
||||||
|
If you have several thousand messages in your mailbox, then it is advised to shut down
|
||||||
|
Mailpit while you reindex as this process will likely result in database locking issues.`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
config.DataFile = args[0]
|
||||||
|
config.MaxMessages = 0
|
||||||
|
|
||||||
|
if err := storage.InitDB(); err != nil {
|
||||||
|
logger.Log().Error(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.ReindexAll()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(reindexCmd)
|
||||||
|
}
|
184
internal/storage/reindex.go
Normal file
184
internal/storage/reindex.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/axllent/mailpit/internal/logger"
|
||||||
|
"github.com/axllent/mailpit/internal/tools"
|
||||||
|
"github.com/jhillyerd/enmime"
|
||||||
|
"github.com/leporo/sqlf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReindexAll will regenerate the search text and snippet for a message
|
||||||
|
// and update the database.
|
||||||
|
func ReindexAll() {
|
||||||
|
ids := []string{}
|
||||||
|
var i string
|
||||||
|
chunkSize := 1000
|
||||||
|
|
||||||
|
finished := 0
|
||||||
|
|
||||||
|
err := sqlf.Select("ID").To(&i).
|
||||||
|
From("mailbox").
|
||||||
|
OrderBy("Created DESC").
|
||||||
|
QueryAndClose(nil, db, func(row *sql.Rows) {
|
||||||
|
ids = append(ids, i)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Log().Error(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
total := len(ids)
|
||||||
|
|
||||||
|
chunks := chunkBy(ids, chunkSize)
|
||||||
|
|
||||||
|
logger.Log().Infof("Reindexing %d messages", total)
|
||||||
|
|
||||||
|
// fmt.Println(len(ids), " = ", len(chunks), "chunks")
|
||||||
|
|
||||||
|
type updateStruct struct {
|
||||||
|
ID string
|
||||||
|
SearchText string
|
||||||
|
Snippet string
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ids := range chunks {
|
||||||
|
updates := []updateStruct{}
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
raw, err := GetMessageRaw(id)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log().Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(raw)
|
||||||
|
|
||||||
|
env, err := enmime.ReadEnvelope(r)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log().Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
searchText := createSearchText(env)
|
||||||
|
snippet := tools.CreateSnippet(env.Text, env.HTML)
|
||||||
|
|
||||||
|
u := updateStruct{}
|
||||||
|
u.ID = id
|
||||||
|
u.SearchText = searchText
|
||||||
|
u.Snippet = snippet
|
||||||
|
|
||||||
|
updates = append(updates, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log().Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// roll back if it fails
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// insert mail summary data
|
||||||
|
for _, u := range updates {
|
||||||
|
_, err = tx.Exec("UPDATE mailbox SET SearchText = ?, Snippet = ? WHERE ID = ?", u.SearchText, u.Snippet, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log().Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
logger.Log().Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
finished += len(updates)
|
||||||
|
|
||||||
|
logger.Log().Printf("Reindexed: %d / %d (%d%%)", finished, total, finished*100/total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reindex will regenerate the search text and snippet for a message
|
||||||
|
// and update the database.
|
||||||
|
func Reindex(id string) error {
|
||||||
|
// ids := []string{}
|
||||||
|
// var i string
|
||||||
|
// // chunkSize := 100
|
||||||
|
|
||||||
|
// err := sqlf.Select("ID").To(&i).From("mailbox_data").QueryAndClose(nil, db, func(row *sql.Rows) {
|
||||||
|
// ids = append(ids, id)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// chunks := chunkBy(ids, 100)
|
||||||
|
|
||||||
|
// fmt.Println(len(ids), " = ", len(chunks), "chunks")
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
|
||||||
|
raw, err := GetMessageRaw(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(raw)
|
||||||
|
|
||||||
|
env, err := enmime.ReadEnvelope(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
searchText := createSearchText(env)
|
||||||
|
snippet := tools.CreateSnippet(env.Text, env.HTML)
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
|
||||||
|
// ctx := context.Background()
|
||||||
|
// tx, err := db.BeginTx(ctx, nil)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // roll back if it fails
|
||||||
|
// defer tx.Rollback()
|
||||||
|
|
||||||
|
// // insert mail summary data
|
||||||
|
// _, err = tx.Exec("UPDATE mailbox SET SearchText = ?, Snippet = ? WHERE ID = ?", searchText, snippet, id)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return tx.Commit()
|
||||||
|
|
||||||
|
_, err = sqlf.Update("mailbox").
|
||||||
|
Set("SearchText", searchText).
|
||||||
|
Set("Snippet", snippet).
|
||||||
|
Where("ID = ?", id).
|
||||||
|
ExecAndClose(context.Background(), db)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ctx := context.Background()
|
||||||
|
// tx, err := db.BeginTx(ctx, nil)
|
||||||
|
// if err != nil {
|
||||||
|
// return "", err
|
||||||
|
// }
|
||||||
|
|
||||||
|
func chunkBy[T any](items []T, chunkSize int) (chunks [][]T) {
|
||||||
|
for chunkSize < len(items) {
|
||||||
|
items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
|
||||||
|
}
|
||||||
|
return append(chunks, items)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user