Files
go-worm/lib/clipmon/writer.go
T

154 lines
3.0 KiB
Go

package clipmon
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
const maxLoggedText = 1 << 16 // ponytail: 64KB cap per clipboard text entry
type Writer struct {
dir string
imagesDir string
file *os.File
bucket string
}
func NewWriter(dir string) (*Writer, error) {
imagesDir := filepath.Join(dir, "images")
if err := os.MkdirAll(imagesDir, 0o700); err != nil {
return nil, err
}
return &Writer{dir: dir, imagesDir: imagesDir}, nil
}
func (w *Writer) Write(event Event) error {
bucket := HourBucket(event.Time)
if bucket != w.bucket {
if err := w.rotate(bucket); err != nil {
return err
}
}
_, err := w.file.WriteString(formatLine(event))
return err
}
func (w *Writer) SavePNG(data []byte) (string, error) {
name, err := randomName(".png")
if err != nil {
return "", err
}
path := filepath.Join(w.imagesDir, name)
if err := os.WriteFile(path, data, 0o600); err != nil {
return "", fmt.Errorf("write image: %w", err)
}
return filepath.ToSlash(filepath.Join("images", name)), nil
}
func (w *Writer) rotate(bucket string) error {
if w.file != nil {
if err := w.file.Close(); err != nil {
return err
}
w.file = nil
}
path := filepath.Join(w.dir, LogFilename(bucket))
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return err
}
w.file = file
w.bucket = bucket
return nil
}
func (w *Writer) Close() error {
if w.file == nil {
return nil
}
err := w.file.Close()
w.file = nil
w.bucket = ""
return err
}
func TrimText(text string) string {
if len(text) <= maxLoggedText {
return text
}
return text[:maxLoggedText] + "\n...(truncated)"
}
func randomName(ext string) (string, error) {
var buf [16]byte
if _, err := rand.Read(buf[:]); err != nil {
return "", err
}
if !strings.HasPrefix(ext, ".") {
ext = "." + ext
}
return hex.EncodeToString(buf[:]) + ext, nil
}
func PruneOld(dir string, retentionDays int) error {
if retentionDays <= 0 {
return nil
}
cutoff := time.Now().AddDate(0, 0, -retentionDays)
if err := pruneLogs(dir, cutoff); err != nil {
return err
}
return pruneImages(filepath.Join(dir, "images"), cutoff)
}
func pruneLogs(dir string, cutoff time.Time) error {
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
for _, entry := range entries {
if entry.IsDir() || !ValidLogFilename(entry.Name()) {
continue
}
info, err := entry.Info()
if err != nil {
continue
}
if info.ModTime().Before(cutoff) {
_ = os.Remove(filepath.Join(dir, entry.Name()))
}
}
return nil
}
func pruneImages(dir string, cutoff time.Time) error {
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
info, err := entry.Info()
if err != nil {
continue
}
if info.ModTime().Before(cutoff) {
_ = os.Remove(filepath.Join(dir, entry.Name()))
}
}
return nil
}