1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 09:37:02 +00:00

storage: $ can be used in place of : in filestore path (#449)

This commit is contained in:
James Hillyerd
2023-11-30 19:45:26 -08:00
committed by GitHub
parent f0473c5d65
commit e6e4e0987d
3 changed files with 59 additions and 16 deletions

View File

@@ -6,6 +6,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"sync"
"time"
@@ -23,7 +24,7 @@ const indexFileName = "index.gob"
var (
// countChannel is filled with a sequential numbers (0000..9999), which are
// used by generateID() to generate unique message IDs. It's global
// because we only want one regardless of the number of DataStore objects
// because we only want one regardless of the number of DataStore objects.
countChannel = make(chan int, 10)
)
@@ -32,7 +33,7 @@ func init() {
go countGenerator(countChannel)
}
// Populates the channel with numbers
// Populates the channel with numbers.
func countGenerator(c chan int) {
for i := 0; true; i = (i + 1) % 10000 {
c <- i
@@ -40,7 +41,7 @@ func countGenerator(c chan int) {
}
// Store implements DataStore aand is the root of the mail storage
// hiearchy. It provides access to Mailbox objects
// hiearchy. It provides access to Mailbox objects.
type Store struct {
hashLock storage.HashLock
path string
@@ -50,13 +51,14 @@ type Store struct {
extHost *extension.Host
}
// New creates a new DataStore object using the specified path
// New creates a new DataStore object using the specified path.
func New(cfg config.Storage, extHost *extension.Host) (storage.Store, error) {
path := cfg.Params["path"]
if path == "" {
return nil, fmt.Errorf("'path' parameter not specified")
}
mailPath := filepath.Join(path, "mail")
mailPath := getMailPath(path)
if _, err := os.Stat(mailPath); err != nil {
// Mail datastore does not yet exist, create it.
if err = os.MkdirAll(mailPath, 0770); err != nil {
@@ -88,16 +90,19 @@ func (fs *Store) AddMessage(m storage.Message) (id string, err error) {
if err != nil {
return "", err
}
// Create a new message.
fm, err := mb.newMessage()
if err != nil {
return "", err
}
// Ensure mailbox directory exists.
if err := mb.createDir(); err != nil {
return "", err
}
// Write the message content
// Write the message content.
file, err := os.Create(fm.rawPath())
if err != nil {
return "", err
@@ -105,23 +110,24 @@ func (fs *Store) AddMessage(m storage.Message) (id string, err error) {
w := bufio.NewWriter(file)
size, err := io.Copy(w, r)
if err != nil {
// Try to remove the file
// Try to remove the file.
_ = file.Close()
_ = os.Remove(fm.rawPath())
return "", err
}
_ = r.Close()
if err := w.Flush(); err != nil {
// Try to remove the file
// Try to remove the file.
_ = file.Close()
_ = os.Remove(fm.rawPath())
return "", err
}
if err := file.Close(); err != nil {
// Try to remove the file
// Try to remove the file.
_ = os.Remove(fm.rawPath())
return "", err
}
// Update the index.
fm.Fdate = m.Date()
fm.Ffrom = m.From()
@@ -130,10 +136,11 @@ func (fs *Store) AddMessage(m storage.Message) (id string, err error) {
fm.Fsubject = m.Subject()
mb.messages = append(mb.messages, fm)
if err := mb.writeIndex(); err != nil {
// Try to remove the file
// Try to remove the file.
_ = os.Remove(fm.rawPath())
return "", err
}
return fm.Fid, nil
}
@@ -158,11 +165,13 @@ func (fs *Store) MarkSeen(mailbox, id string) error {
mb := fs.mbox(mailbox)
mb.Lock()
defer mb.Unlock()
if !mb.indexLoaded {
if err := mb.readIndex(); err != nil {
return err
}
}
for _, m := range mb.messages {
if m.Fid == id {
if m.Fseen {
@@ -173,6 +182,7 @@ func (fs *Store) MarkSeen(mailbox, id string) error {
break
}
}
return mb.writeIndex()
}
@@ -210,19 +220,22 @@ func (fs *Store) VisitMailboxes(f func([]storage.Message) (cont bool)) error {
if err != nil {
return err
}
// Loop over level 1 directories
// Loop over level 1 directories.
for _, name1 := range names1 {
names2, err := readDirNames(fs.mailPath, name1)
if err != nil {
return err
}
// Loop over level 2 directories
// Loop over level 2 directories.
for _, name2 := range names2 {
names3, err := readDirNames(fs.mailPath, name1, name2)
if err != nil {
return err
}
// Loop over mailboxes
// Loop over mailboxes.
for _, name3 := range names3 {
mb := fs.mboxFromHash(name3)
mb.RLock()
@@ -247,6 +260,7 @@ func (fs *Store) mbox(mailbox string) *mbox {
s2 := hash[0:6]
path := filepath.Join(fs.mailPath, s1, s2, hash)
indexPath := filepath.Join(path, indexFileName)
return &mbox{
RWMutex: fs.hashLock.Get(hash),
store: fs,
@@ -263,6 +277,7 @@ func (fs *Store) mboxFromHash(hash string) *mbox {
s2 := hash[0:6]
path := filepath.Join(fs.mailPath, s1, s2, hash)
indexPath := filepath.Join(path, indexFileName)
return &mbox{
RWMutex: fs.hashLock.Get(hash),
store: fs,
@@ -297,6 +312,14 @@ func generateID(date time.Time) string {
return generatePrefix(date) + "-" + fmt.Sprintf("%04d", <-countChannel)
}
// getMailPath converts a filestore `path` parameter into the effective mail store path.
// Within the path, '$' is replaced with ':' to support Windows drive letters with our
// env->config map syntax.
func getMailPath(base string) string {
path := strings.ReplaceAll(base, "$", ":")
return filepath.Join(path, "mail")
}
// readDirNames returns a slice of filenames in the specified directory or an error.
func readDirNames(elem ...string) ([]string, error) {
f, err := os.Open(filepath.Join(elem...))