Compare commits

...

16 Commits

Author SHA1 Message Date
Ralph Slooten
c6f1c8213b Merge branch 'release/0.0.3' 2022-07-30 22:03:12 +12:00
Ralph Slooten
38da162cd9 0.0.3 2022-07-30 22:02:42 +12:00
Ralph Slooten
56449dd30e Bugfix: Update to clover-v2.0.0-alpha.2 to fix sorting 2022-07-30 22:01:30 +12:00
Ralph Slooten
a810bdae24 Merge tag '0.0.2' into develop
Release 0.0.2
2022-07-30 20:01:32 +12:00
Ralph Slooten
f966b6a756 Merge branch 'release/0.0.2' 2022-07-30 20:01:30 +12:00
Ralph Slooten
7cbe3a04ef 0.0.2 2022-07-30 20:01:00 +12:00
Ralph Slooten
ce23e0616e Merge branch 'feature/unread' into develop 2022-07-30 19:58:42 +12:00
Ralph Slooten
a85a74bb9a Feature: Unread statistics 2022-07-30 19:58:31 +12:00
Ralph Slooten
335b0f3876 Typo 2022-07-30 19:57:44 +12:00
Ralph Slooten
324de3d99f Include mailbox name in error message 2022-07-30 10:10:46 +12:00
Ralph Slooten
8d6d48c59e Add asset building to tests 2022-07-30 09:04:25 +12:00
Ralph Slooten
8e819aca56 Update package-lock.json 2022-07-30 09:03:30 +12:00
Ralph Slooten
4e4fc22cb5 Add test workflow 2022-07-30 08:43:49 +12:00
Ralph Slooten
2e5752f693 Switch to ostafen/clover/v2 2022-07-30 08:39:58 +12:00
Ralph Slooten
cf12b3968b Default listen on 0.0.0.0 2022-07-30 00:17:56 +12:00
Ralph Slooten
0fa58dffbd Merge tag '0.0.1-beta' into develop
Release 0.0.1-beta
2022-07-30 00:06:04 +12:00
12 changed files with 1530 additions and 191 deletions

28
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Test
on:
pull_request:
branches: [ develop ]
push:
branches: [ develop ]
jobs:
test:
strategy:
matrix:
go-version: [1.18.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- uses: actions/checkout@v3
- run: go test ./storage -v
- run: go test ./storage -bench=.
# build the assets
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
- run: npm install
- run: npm run package

15
CHANGELOG.md Normal file
View File

@@ -0,0 +1,15 @@
# Changelog
## [0.0.3]
- Bugfix: Update to clover-v2.0.0-alpha.2 to fix sorting
## [0.0.2]
- Unread message statistics & updates
## [0.0.1-beta]
- First release

View File

@@ -10,9 +10,9 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster.
## Features
- Runs completely on a single binary
- SMTP server (default `127.0.0.1:1025`)
- Web UI to view emails (HTML format, text, source and MIME attachments, default `127.0.0.1:8025`)
- Real-time web UI updates using websockets for new mail
- SMTP server (default `0.0.0.0:1025`)
- Web UI to view emails (HTML format, text, source and MIME attachments, default `0.0.0.0:8025`)
- Real-time web UI updates using web sockets for new mail
- Email storage in either memory or disk (using [CloverDB](https://github.com/ostafen/clover)) - note that in-memory has a physical limit of 1MB per email
- Configurable automatic email pruning (default keeps the most recent 500 emails)
- Fast SMTP processing & storing - approximately 300-600 emails per second depending on CPU, network speed & email size

View File

@@ -16,3 +16,9 @@ type WebsocketNotification struct {
Type string
Data interface{}
}
// MailboxStats struct for quick mailbox total/read lookups
type MailboxStats struct {
Total int
Unread int
}

10
go.mod
View File

@@ -9,7 +9,7 @@ require (
github.com/jhillyerd/enmime v0.10.0
github.com/k3a/html2text v1.0.8
github.com/mhale/smtpd v0.8.0
github.com/ostafen/clover v1.2.1-0.20220728200552-0b95f72b304c
github.com/ostafen/clover/v2 v2.0.0-alpha.2
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5
@@ -38,15 +38,15 @@ require (
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.3.0 // indirect
github.com/rivo/uniseg v0.3.1 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/testify v1.7.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20220726230323-06994584191e // indirect
golang.org/x/sys v0.0.0-20220727055044-e65921a090b8 // indirect
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)

24
go.sum
View File

@@ -107,7 +107,6 @@ github.com/k3a/html2text v1.0.8/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIB
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -129,8 +128,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/ostafen/clover v1.2.1-0.20220728200552-0b95f72b304c h1:hFGWRJPoIP3e73jFTdeMTyG1kwoe7r5Ayf1o9Wqyqh8=
github.com/ostafen/clover v1.2.1-0.20220728200552-0b95f72b304c/go.mod h1:KVMcjgoq15v0S/I0GGAZPPtwO6+w6rYM0ZW/6XSO2Ic=
github.com/ostafen/clover/v2 v2.0.0-alpha.2 h1:PgOWohvpj4qNCyASJ7Q8Ke8ld/wsoi+dQJ05b1ebwus=
github.com/ostafen/clover/v2 v2.0.0-alpha.2/go.mod h1:7UyIG46NglzTDRKB4LJiS/enXpuo67Lj05eM8mdhL6M=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -139,8 +138,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.3.0 h1:eyC18g7xB83Dv/xlJXLgNkRidVoR7nqFZBJvqo/K188=
github.com/rivo/uniseg v0.3.0/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.3.1 h1:SDPP7SHNl1L7KrEFCSJslJ/DM9DT02Nq2C61XrfHMmk=
github.com/rivo/uniseg v0.3.1/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -207,9 +206,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220726230323-06994584191e h1:wOQNKh1uuDGRnmgF0jDxh7ctgGy/3P4rYWQRVJD4/Yg=
golang.org/x/net v0.0.0-20220726230323-06994584191e/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 h1:UreQrH7DbFXSi9ZFox6FNT3WBooWmdANpU+IfkT1T4I=
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -227,11 +225,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220727055044-e65921a090b8 h1:dyU22nBWzrmTQxtNrr4dzVOvaw35nUYE279vF9UmsI8=
golang.org/x/sys v0.0.0-20220727055044-e65921a090b8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -275,8 +271,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=

1428
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,10 +12,11 @@ import (
)
type messagesResult struct {
Total int `json:"total"`
Count int `json:"count"`
Start int `json:"start"`
Items []data.Summary `json:"items"`
Total int `json:"total"`
Unread int `json:"unread"`
Count int `json:"count"`
Start int `json:"start"`
Items []data.Summary `json:"items"`
}
// Return a list of available mailboxes
@@ -49,18 +50,15 @@ func apiListMailbox(w http.ResponseWriter, r *http.Request) {
return
}
total, err := storage.Count(mailbox)
if err != nil {
httpError(w, err.Error())
return
}
stats := storage.StatsGet(mailbox)
var res messagesResult
res.Start = start
res.Items = messages
res.Count = len(res.Items)
res.Total = total
res.Total = stats.Total
res.Unread = stats.Unread
bytes, _ := json.Marshal(res)
w.Header().Add("Content-Type", "application/json")
@@ -92,24 +90,15 @@ func apiSearchMailbox(w http.ResponseWriter, r *http.Request) {
return
}
total, err := storage.Count(mailbox)
if err != nil {
httpError(w, err.Error())
return
}
// total := limit
// count := len(messages)
// if total > count {
// total = count
// }
stats := storage.StatsGet(mailbox)
var res messagesResult
res.Start = start
res.Items = messages
res.Count = len(messages)
res.Total = total
res.Total = stats.Total
res.Unread = stats.Unread
bytes, _ := json.Marshal(res)
w.Header().Add("Content-Type", "application/json")

View File

@@ -15,6 +15,7 @@ export default {
items: [],
limit: 50,
total: 0,
unread: 0,
start: 0,
search: "",
searching: false,
@@ -71,6 +72,7 @@ export default {
self.get(uri, params, function(response){
self.total = response.data.total;
self.unread = response.data.unread;
self.count = response.data.count;
self.start = response.data.start;
self.items = response.data.items;
@@ -119,7 +121,10 @@ export default {
self.get(uri, params, function(response) {
for (let i in self.items) {
if (self.items[i].ID == self.currentPath) {
self.items[i].Read = true;
if (!self.items[i].Read) {
self.items[i].Read = true;
self.unread--;
}
}
}
let d = response.data;
@@ -208,6 +213,7 @@ export default {
}
}
self.total++;
self.unread++;
} else if (response.Type == "prune") {
// messages have been deleted, reload messages to adjust
self.scrollInPlace = true;
@@ -323,8 +329,8 @@ export default {
<i class="bi bi-envelope me-1" v-if="isConnected"></i>
<i class="bi bi-arrow-clockwise me-1" v-else></i>
Inbox
<span class="position-absolute mt-2 ms-4 start-100 translate-middle badge rounded-pill text-bg-secondary" v-if="total">
{{ formatNumber(total) }}
<span class="position-absolute mt-2 ms-4 start-100 translate-middle badge rounded-pill text-bg-secondary" title="Unread messages" v-if="unread">
{{ formatNumber(unread) }}
</span>
</a>
</li>

View File

@@ -8,6 +8,7 @@ import (
"os"
"os/signal"
"regexp"
"strings"
"syscall"
"time"
@@ -16,7 +17,7 @@ import (
"github.com/axllent/mailpit/logger"
"github.com/axllent/mailpit/server/websockets"
"github.com/jhillyerd/enmime"
"github.com/ostafen/clover"
"github.com/ostafen/clover/v2"
)
var (
@@ -99,24 +100,20 @@ func ListMailboxes() ([]data.MailboxSummary, error) {
results := []data.MailboxSummary{}
for _, m := range mailboxes {
total, err := Count(m)
if err != nil {
return nil, err
// ignore *_data collections
if strings.HasSuffix(m, "_data") {
continue
}
unread, err := CountUnread(m)
if err != nil {
return nil, err
}
stats := StatsGet(m)
mb := data.MailboxSummary{}
mb.Name = m
mb.Slug = m
mb.Total = total
mb.Unread = unread
mb.Total = stats.Total
mb.Unread = stats.Unread
if total > 0 {
if mb.Total > 0 {
q, err := db.FindFirst(
clover.NewQuery(m).Sort(clover.SortOption{Field: "Created", Direction: -1}),
)
@@ -172,7 +169,7 @@ func CreateMailbox(name string) error {
}
}
return nil
return statsRefresh(name)
}
// Store will store a message in the database and return the unique ID
@@ -223,6 +220,8 @@ func Store(mailbox string, b []byte) (string, error) {
return "", err
}
statsAddNewMessage(mailbox)
count++
if count%100 == 0 {
logger.Log().Infof("%d messages added (%s per 100)", count, time.Since(per100start))
@@ -441,11 +440,16 @@ func GetMessage(mailbox, id string) (*data.Message, error) {
obj.HTML = html
updates := make(map[string]interface{})
updates["Read"] = true
msg, err := db.FindById(mailbox, id)
if err == nil && !msg.Get("Read").(bool) {
updates := make(map[string]interface{})
updates["Read"] = true
if err := db.UpdateById(mailbox, id, updates); err != nil {
return nil, err
if err := db.UpdateById(mailbox, id, updates); err != nil {
return nil, err
}
statsReadOneMessage(mailbox)
}
return &obj, nil
@@ -507,6 +511,8 @@ func UnreadMessage(mailbox, id string) error {
updates := make(map[string]interface{})
updates["Read"] = false
statsUnreadOneMessage(mailbox)
return db.UpdateById(mailbox, id, updates)
}
@@ -516,6 +522,8 @@ func DeleteOneMessage(mailbox, id string) error {
return err
}
statsDeleteOneMessage(mailbox)
return db.DeleteById(mailbox+"_data", id)
}
@@ -545,13 +553,8 @@ func DeleteAllMessages(mailbox string) error {
}
}
// if err := db.Delete(clover.NewQuery(mailbox)); err != nil {
// return err
// }
// if err := db.Delete(clover.NewQuery(mailbox + "_data")); err != nil {
// return err
// }
// resets stats for mailbox
statsRefresh(mailbox)
elapsed := time.Since(totalStart)
logger.Log().Infof("Deleted %d messages from %s in %s", totalMessages, mailbox, elapsed)

103
storage/stats.go Normal file
View File

@@ -0,0 +1,103 @@
package storage
import (
"sync"
"github.com/axllent/mailpit/data"
"github.com/axllent/mailpit/logger"
"github.com/ostafen/clover/v2"
)
var (
mailboxStats = map[string]data.MailboxStats{}
statsLock = sync.RWMutex{}
)
// StatsGet returns the total/unread statistics for a mailbox
func StatsGet(mailbox string) data.MailboxStats {
statsLock.Lock()
defer statsLock.Unlock()
s, ok := mailboxStats[mailbox]
if !ok {
return data.MailboxStats{
Total: 0,
Unread: 0,
}
}
return s
}
// Refresh will completely refresh the existing stats for a given mailbox
func statsRefresh(mailbox string) error {
logger.Log().Debugf("[stats] refreshing stats for %s", mailbox)
total, err := db.Count(clover.NewQuery(mailbox))
if err != nil {
return err
}
unread, err := db.Count(clover.NewQuery(mailbox).Where(clover.Field("Read").IsFalse()))
if err != nil {
return nil
}
statsLock.Lock()
mailboxStats[mailbox] = data.MailboxStats{
Total: total,
Unread: unread,
}
statsLock.Unlock()
return nil
}
func statsAddNewMessage(mailbox string) {
statsLock.Lock()
s, ok := mailboxStats[mailbox]
if ok {
mailboxStats[mailbox] = data.MailboxStats{
Total: s.Total + 1,
Unread: s.Unread + 1,
}
}
statsLock.Unlock()
}
// Deleting one will always mean it was read
func statsDeleteOneMessage(mailbox string) {
statsLock.Lock()
s, ok := mailboxStats[mailbox]
if ok {
mailboxStats[mailbox] = data.MailboxStats{
Total: s.Total - 1,
Unread: s.Unread,
}
}
statsLock.Unlock()
}
// Mark one message as read
func statsReadOneMessage(mailbox string) {
statsLock.Lock()
s, ok := mailboxStats[mailbox]
if ok {
mailboxStats[mailbox] = data.MailboxStats{
Total: s.Total,
Unread: s.Unread - 1,
}
}
statsLock.Unlock()
}
// Mark one message as unread
func statsUnreadOneMessage(mailbox string) {
statsLock.Lock()
s, ok := mailboxStats[mailbox]
if ok {
mailboxStats[mailbox] = data.MailboxStats{
Total: s.Total,
Unread: s.Unread + 1,
}
}
statsLock.Unlock()
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/axllent/mailpit/server/websockets"
"github.com/jhillyerd/enmime"
"github.com/k3a/html2text"
"github.com/ostafen/clover"
"github.com/ostafen/clover/v2"
)
// Return a header field as a []*mail.Address, or "null" is not found/empty
@@ -76,11 +76,12 @@ func pruneCron() {
if err := db.Delete(clover.NewQuery(m).
Sort(clover.SortOption{Field: "Created", Direction: 1}).
Limit(limit)); err != nil {
logger.Log().Warnf("Error pruning: %s", err.Error())
logger.Log().Warnf("Error pruning %s: %s", m, err.Error())
continue
}
elapsed := time.Since(start)
logger.Log().Infof("Pruned %d messages from %s in %s", limit, m, elapsed)
statsRefresh(m)
if !strings.HasSuffix(m, "_data") {
websockets.Broadcast("prune", nil)
}