1
1

Добавить тесты репозитория файловой системы и реализовать функциональность журналирования файлов.

- Реализовать тесты для поиска MP3-файлов и переименования/организации папок книг в репозитории файловой системы.
- Создать FileLogger для записи сообщений в файл с поддержкой различных уровней журналирования и управления размером файлов.
- Разработать репозиторий RuTracker для обработки поиска торрентов, получения метаданных и загрузки торрент-файлов.
- Добавить тесты для нормализации URL в репозиторий RuTracker.
- Реализовать адаптер логгера TUI для отображения логов в терминальном интерфейсе и, при необходимости, для записи логов в базовый логгер.
- Создать менеджер TUI для управления пользовательским интерфейсом приложения, включая главное меню, экран обработки, настройки и отображение результатов.
This commit is contained in:
Dmitriy Fofanov
2025-09-29 20:40:05 +03:00
parent 49bea780aa
commit 72a66f1664
32 changed files with 4073 additions and 22 deletions

View File

@@ -0,0 +1,185 @@
package logging
import (
"fmt"
"os"
"runtime"
"strings"
"sync"
"syscall"
"time"
"golang.org/x/text/encoding/charmap"
)
// FileLogger реализация Logger для записи в файл
type FileLogger struct {
logFile *os.File
logFileMu sync.Mutex
logFileSize int64
logLevel string
fileName string
maxSizeMB int
logToFile bool
}
// NewFileLogger создает новый файловый логгер
func NewFileLogger(fileName string, logLevel string, maxSizeMB int, logToFile bool) (*FileLogger, error) {
logger := &FileLogger{
logLevel: logLevel,
fileName: fileName,
maxSizeMB: maxSizeMB,
logToFile: logToFile,
}
if logToFile {
if err := logger.initLogFile(); err != nil {
return nil, fmt.Errorf("ошибка инициализации лог файла: %w", err)
}
}
return logger, nil
}
// Info логирует информационное сообщение
func (l *FileLogger) Info(format string, args ...interface{}) {
if l.logLevel == "debug" || l.logLevel == "info" {
message := fmt.Sprintf(format, args...)
l.writeToLogFile("INFO", message)
}
}
// Debug логирует отладочное сообщение
func (l *FileLogger) Debug(format string, args ...interface{}) {
if l.logLevel == "debug" {
message := fmt.Sprintf(format, args...)
l.writeToLogFile("DEBUG", message)
}
}
// Warning логирует предупреждение
func (l *FileLogger) Warning(format string, args ...interface{}) {
message := fmt.Sprintf(format, args...)
l.writeToLogFile("WARNING", message)
}
// Error логирует ошибку
func (l *FileLogger) Error(format string, args ...interface{}) {
message := fmt.Sprintf(format, args...)
l.writeToLogFile("ERROR", message)
}
// Success логирует успешное выполнение
func (l *FileLogger) Success(format string, args ...interface{}) {
message := fmt.Sprintf(format, args...)
l.writeToLogFile("SUCCESS", message)
}
// Close закрывает лог файл
func (l *FileLogger) Close() {
if l.logFile != nil {
l.logFile.Close()
}
}
// initLogFile инициализирует лог файл
func (l *FileLogger) initLogFile() error {
if l.fileName == "" {
l.fileName = "audio-catalyst.log"
}
// Устанавливаем UTF-8 кодировку для Windows консоли
if runtime.GOOS == "windows" {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
setConsoleOutputCP := kernel32.NewProc("SetConsoleOutputCP")
setConsoleOutputCP.Call(uintptr(65001)) // UTF-8
}
var err error
l.logFile, err = os.OpenFile(l.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return fmt.Errorf("не удалось открыть лог файл: %w", err)
}
// Получаем текущий размер файла
if stat, err := l.logFile.Stat(); err == nil {
l.logFileSize = stat.Size()
}
return nil
}
// writeToLogFile записывает сообщение в лог файл
func (l *FileLogger) writeToLogFile(level, message string) {
if !l.logToFile || l.logFile == nil {
return
}
l.logFileMu.Lock()
defer l.logFileMu.Unlock()
timestamp := time.Now().Format("02.01.2006 15:04:05")
raw := fmt.Sprintf("[%s] [%s] %s\n", timestamp, level, message)
var toWrite string
if runtime.GOOS == "windows" {
s := l.sanitizeForWindowsCP1251(raw)
enc := charmap.Windows1251.NewEncoder()
if encStr, err := enc.String(s); err == nil {
toWrite = encStr
} else {
toWrite, _ = enc.String(l.sanitizeForWindowsCP1251(s))
}
} else {
toWrite = raw
}
// Проверяем размер файла и выполняем ротацию
maxSize := int64(l.maxSizeMB) * 1024 * 1024
if l.logFileSize+int64(len(toWrite)) > maxSize {
l.logFile.Close()
var err error
l.logFile, err = os.OpenFile(l.fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return
}
l.logFileSize = 0
}
if n, err := l.logFile.WriteString(toWrite); err == nil {
l.logFileSize += int64(n)
l.logFile.Sync()
}
}
// sanitizeForWindowsCP1251 санитизирует строку для Windows-1251
func (l *FileLogger) sanitizeForWindowsCP1251(s string) string {
var b strings.Builder
for _, r := range s {
switch r {
case '\n', '\r', '\t':
b.WriteRune(r)
continue
}
// Кириллица
if (r >= 0x0410 && r <= 0x044F) || r == 0x0401 || r == 0x0451 {
b.WriteRune(r)
continue
}
// ASCII печатаемые символы
if r >= 0x20 && r <= 0x7E {
b.WriteRune(r)
continue
}
// Распространённые знаки пунктуации
if (r >= 0x2013 && r <= 0x201E) || r == 0x00AB || r == 0x00BB {
b.WriteRune(r)
continue
}
}
return b.String()
}