185 lines
4.6 KiB
Go
185 lines
4.6 KiB
Go
package sessionlog
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Entry представляет один лог-файл сессии.
|
|
type Entry struct {
|
|
Name string // имя файла без расширения
|
|
Path string // полный путь
|
|
ModTime time.Time // время изменения
|
|
}
|
|
|
|
// Dir возвращает директорию логов (~/.ztorrent/logs).
|
|
func Dir() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
dir := filepath.Join(home, ".ztorrent", "logs")
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return "", err
|
|
}
|
|
return dir, nil
|
|
}
|
|
|
|
// NewWriter создаёт новый md-файл для текущей сессии и возвращает:
|
|
// - путь к файлу
|
|
// - io.WriteCloser для записи
|
|
// - ошибку
|
|
func NewWriter(torrentName string) (string, io.WriteCloser, error) {
|
|
dir, err := Dir()
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
ts := time.Now().Format("2006-01-02_15-04-05")
|
|
safe := sanitize(torrentName)
|
|
if safe == "" {
|
|
safe = "session"
|
|
}
|
|
fname := fmt.Sprintf("%s_%s.md", ts, safe)
|
|
path := filepath.Join(dir, fname)
|
|
|
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
// Записываем заголовок
|
|
header := fmt.Sprintf("# Сессия: %s\n\n**Начало:** %s \n**Торрент:** %s\n\n---\n\n```\n",
|
|
fname, time.Now().Format("02.01.2006 15:04:05"), torrentName)
|
|
_, _ = f.WriteString(header)
|
|
|
|
return path, f, nil
|
|
}
|
|
|
|
// sanitize убирает из имени файла недопустимые символы.
|
|
func sanitize(s string) string {
|
|
replacer := strings.NewReplacer(
|
|
"/", "_", "\\", "_", ":", "_", "*", "_",
|
|
"?", "_", "\"", "_", "<", "_", ">", "_", "|", "_",
|
|
" ", "_",
|
|
)
|
|
s = replacer.Replace(s)
|
|
if len(s) > 40 {
|
|
s = s[:40]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// List возвращает список лог-файлов, отсортированных от новых к старым.
|
|
func List() ([]Entry, error) {
|
|
dir, err := Dir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result []Entry
|
|
for _, e := range entries {
|
|
if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
|
|
continue
|
|
}
|
|
info, err := e.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
result = append(result, Entry{
|
|
Name: strings.TrimSuffix(e.Name(), ".md"),
|
|
Path: filepath.Join(dir, e.Name()),
|
|
ModTime: info.ModTime(),
|
|
})
|
|
}
|
|
|
|
// Сортировка от новых к старым
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return result[i].ModTime.After(result[j].ModTime)
|
|
})
|
|
return result, nil
|
|
}
|
|
|
|
// Read читает содержимое лог-файла.
|
|
func Read(path string) (string, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// Delete удаляет лог-файл.
|
|
func Delete(path string) error {
|
|
return os.Remove(path)
|
|
}
|
|
|
|
// LogWriter оборачивает os.File и при записи:
|
|
// - пишет в файл
|
|
// - пишет в стандартный log (если enable)
|
|
type LogWriter struct {
|
|
file *os.File
|
|
path string
|
|
closed bool
|
|
}
|
|
|
|
// NewLogWriter создаёт LogWriter, настраивает стандартный log на запись в файл.
|
|
func NewLogWriter(torrentName string) (*LogWriter, error) {
|
|
dir, err := Dir()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ts := time.Now().Format("2006-01-02_15-04-05")
|
|
safe := sanitize(torrentName)
|
|
if safe == "" {
|
|
safe = "session"
|
|
}
|
|
fname := fmt.Sprintf("%s_%s.md", ts, safe)
|
|
path := filepath.Join(dir, fname)
|
|
|
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
header := fmt.Sprintf("# Сессия: %s\n\n**Начало:** %s \n**Торрент:** %s\n\n---\n\n```\n",
|
|
fname, time.Now().Format("02.01.2006 15:04:05"), torrentName)
|
|
_, _ = f.WriteString(header)
|
|
|
|
lw := &LogWriter{file: f, path: path}
|
|
log.SetOutput(lw)
|
|
return lw, nil
|
|
}
|
|
|
|
// Write реализует io.Writer.
|
|
func (lw *LogWriter) Write(p []byte) (n int, err error) {
|
|
if lw.file == nil {
|
|
return len(p), nil
|
|
}
|
|
return lw.file.Write(p)
|
|
}
|
|
|
|
// Path возвращает путь к файлу лога.
|
|
func (lw *LogWriter) Path() string {
|
|
return lw.path
|
|
}
|
|
|
|
// Close закрывает файл и дописывает завершающий блок.
|
|
func (lw *LogWriter) Close() error {
|
|
if lw.closed || lw.file == nil {
|
|
return nil
|
|
}
|
|
lw.closed = true
|
|
_, _ = lw.file.WriteString("```\n\n---\n\n*Сессия завершена: " + time.Now().Format("02.01.2006 15:04:05") + "*\n")
|
|
return lw.file.Close()
|
|
}
|