-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
maildir.go
139 lines (111 loc) · 3.55 KB
/
maildir.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package maildir
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/emersion/go-imap/utf7"
"github.com/onozaty/maildir-stats/user"
)
type mailInfo struct {
size int64
time time.Time
}
func AggregateUsers(users []user.User, maildirName string, inboxFolderName string, aggregator Aggregator) error {
for _, user := range users {
// ユーザのhomeディレクトリにメールディレクトリがあった場合のみ対象に
userMailFolderPath := filepath.Join(user.HomeDir, maildirName)
if file, err := os.Stat(userMailFolderPath); err != nil || !file.IsDir() {
continue
}
aggregator.StartUser(user.Name)
if err := AggregateMailFolders(userMailFolderPath, inboxFolderName, aggregator); err != nil {
return err
}
}
return nil
}
func AggregateMailFolders(rootMailFolderPath string, inboxFolderName string, aggregator Aggregator) error {
// ルート(INBOX)
aggregator.StartMailFolder(inboxFolderName)
if err := aggregateMailFolder(rootMailFolderPath, false, aggregator); err != nil {
return err
}
// その他メールフォルダ
entries, err := os.ReadDir(rootMailFolderPath)
if err != nil {
return err
}
for _, entry := range entries {
// ディレクトリの先頭が"."になっているものがメールフォルダ
if entry.IsDir() && strings.HasPrefix(entry.Name(), ".") {
mailFolderName, err := decodeFolderName(entry.Name()[1:])
if err != nil {
return err
}
aggregator.StartMailFolder(mailFolderName)
// その他メールフォルダは作成直後にcurフォルダなどが無いことがあるので無かったらスキップするように設定
if err := aggregateMailFolder(filepath.Join(rootMailFolderPath, entry.Name()), true, aggregator); err != nil {
return err
}
}
}
return nil
}
func aggregateMailFolder(mailFolderPath string, skipSubdirMissing bool, aggregator Aggregator) error {
// tmpにあるのは配送中のものなので対象から除いておく
for _, subName := range []string{"new", "cur"} {
subDir := filepath.Join(mailFolderPath, subName)
if _, err := os.Stat(subDir); os.IsNotExist(err) && skipSubdirMissing {
// サブディレクトリが無いことを無視する場合はスキップ
continue
}
if err := aggregateMails(filepath.Join(mailFolderPath, subName), aggregator); err != nil {
return err
}
}
return nil
}
func aggregateMails(dirPath string, aggregator Aggregator) error {
entries, err := os.ReadDir(dirPath)
if err != nil {
return err
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
info, err := entry.Info()
if err != nil {
return err
}
aggregator.Aggregate(mailInfoOf(info))
}
return nil
}
func decodeFolderName(encodedName string) (string, error) {
decoder := utf7.Encoding.NewDecoder()
decodedName, err := decoder.String(encodedName)
if err != nil {
return "", fmt.Errorf("%s is invalid folder name: %w", encodedName, err)
}
return decodedName, nil
}
func mailInfoOf(fileInfo fs.FileInfo) mailInfo {
// ファイル名の先頭部分がUnix時間
// 例: 1674617693.M958571P8888.localhost.localdomain,S=545,W=562:2,S
// -> 1674617693 がUnix時間
unixtimePart := strings.Split(fileInfo.Name(), ".")[0]
unixtime, err := strconv.ParseInt(unixtimePart, 10, 64)
if err != nil {
unixtime = 0
}
time := time.Unix(unixtime, 0).UTC() // UTCで扱う
return mailInfo{
time: time,
size: fileInfo.Size(),
}
}