Files

204 lines
4.3 KiB
Go

package storage
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"sync"
"time"
"github.com/CyberPigeon/internal/modem"
)
// Message 存储的短信消息
type Message struct {
ID string `json:"id"`
Modem string `json:"modem"` // 调制解调器 IMEI
Number string `json:"number"` // 发送方号码
Text string `json:"text"` // 短信内容
Timestamp time.Time `json:"timestamp"` // 接收时间
Saved time.Time `json:"saved"` // 保存时间
}
// Storage 短信存储
type Storage struct {
path string
mu sync.RWMutex
messages []Message
ids map[string]bool // 用于快速查重
messageHandler func(Message) // 新消息回调
}
// New 创建存储实例
func New(path string) (*Storage, error) {
s := &Storage{
path: path,
messages: []Message{},
ids: make(map[string]bool),
}
// 尝试加载已有数据
if err := s.load(); err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("加载存储文件: %w", err)
}
return s, nil
}
// GenerateID 生成消息唯一 ID
func GenerateID(modemIMEI string, sms *modem.SMS) string {
// 使用 MD5(IMEI + DBusPath + TimestampNano + Number + Text) 作为唯一 ID
// 优先借助 DBus Path 与纳秒时间,减少秒级时间戳导致的误判
data := fmt.Sprintf("%s|%s|%d|%s|%s", modemIMEI, sms.Path(), sms.Timestamp.UnixNano(), sms.Number, sms.Text)
hash := md5.Sum([]byte(data))
return hex.EncodeToString(hash[:])
}
// Save 保存短信
func (s *Storage) Save(modemIMEI string, sms *modem.SMS) error {
s.mu.Lock()
id := GenerateID(modemIMEI, sms)
// 检查是否已存在
if s.ids[id] {
s.mu.Unlock()
return nil // 已存在,跳过
}
msg := Message{
ID: id,
Modem: modemIMEI,
Number: sms.Number,
Text: sms.Text,
Timestamp: sms.Timestamp,
Saved: time.Now(),
}
s.messages = append(s.messages, msg)
s.ids[id] = true
handler := s.messageHandler
// 在锁内完成持久化,与 Delete 保持一致,避免数据竞争
err := s.save()
s.mu.Unlock()
// 触发新消息回调(在解锁后执行,避免阻塞)
if handler != nil {
go handler(msg)
}
return err
}
// Has 检查消息是否存在
func (s *Storage) Has(modemIMEI string, sms *modem.SMS) bool {
s.mu.RLock()
defer s.mu.RUnlock()
id := GenerateID(modemIMEI, sms)
return s.ids[id]
}
// SetMessageHandler 设置新消息处理器
func (s *Storage) SetMessageHandler(handler func(Message)) {
s.mu.Lock()
defer s.mu.Unlock()
s.messageHandler = handler
}
// List 列出所有短信
func (s *Storage) List() []Message {
s.mu.RLock()
defer s.mu.RUnlock()
result := make([]Message, len(s.messages))
copy(result, s.messages)
return result
}
// ListWithPagination 分页获取短信 (按时间倒序)
func (s *Storage) ListWithPagination(limit, offset int) ([]Message, int64) {
s.mu.RLock()
defer s.mu.RUnlock()
total := int64(len(s.messages))
if total == 0 {
return []Message{}, 0
}
// 既然需要倒序(最新的在前),offset=0 代表 slice 的最后一个元素
// index = len - 1 - offset
start := int64(len(s.messages)) - 1 - int64(offset)
if start < 0 {
return []Message{}, total
}
end := start - int64(limit) + 1
if end < 0 {
end = 0
}
// 收集结果
count := start - end + 1
result := make([]Message, 0, count)
for i := start; i >= end; i-- {
result = append(result, s.messages[i])
}
return result, total
}
// Delete 删除短信
func (s *Storage) Delete(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
for i, msg := range s.messages {
if msg.ID == id {
// 删除该消息
s.messages = append(s.messages[:i], s.messages[i+1:]...)
delete(s.ids, id)
return s.save()
}
}
return fmt.Errorf("消息不存在")
}
// load 从文件加载
func (s *Storage) load() error {
data, err := os.ReadFile(s.path)
if err != nil {
return err
}
if err := json.Unmarshal(data, &s.messages); err != nil {
return err
}
// 重建索引
s.ids = make(map[string]bool)
for _, msg := range s.messages {
s.ids[msg.ID] = true
}
return nil
}
// save 保存到文件
func (s *Storage) save() error {
data, err := json.MarshalIndent(s.messages, "", " ")
if err != nil {
return err
}
return os.WriteFile(s.path, data, 0600)
}
// Close 关闭存储
func (s *Storage) Close() error {
return nil
}