v 0.0.62 (исправленна SMB отправка изменения пароля)
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogEntry struct {
|
||||
ID int `json:"id"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Level string `json:"level"`
|
||||
Category string `json:"category"`
|
||||
Message string `json:"message"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
logs []LogEntry
|
||||
logsMu sync.RWMutex
|
||||
nextID = 1
|
||||
logFile *os.File
|
||||
)
|
||||
|
||||
func Init() {
|
||||
var err error
|
||||
logFile, err = os.OpenFile("logs.json", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
log.Printf("Failed to open log file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(logFile)
|
||||
for decoder.More() {
|
||||
var entry LogEntry
|
||||
if err := decoder.Decode(&entry); err == nil {
|
||||
logs = append(logs, entry)
|
||||
if entry.ID >= nextID {
|
||||
nextID = entry.ID + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Add(level, category, message string, details ...string) {
|
||||
logsMu.Lock()
|
||||
defer logsMu.Unlock()
|
||||
|
||||
entry := LogEntry{
|
||||
ID: nextID,
|
||||
Timestamp: time.Now(),
|
||||
Level: level,
|
||||
Category: category,
|
||||
Message: message,
|
||||
}
|
||||
if len(details) > 0 {
|
||||
entry.Details = details[0]
|
||||
}
|
||||
|
||||
logs = append(logs, entry)
|
||||
nextID++
|
||||
|
||||
if logFile != nil {
|
||||
data, _ := json.Marshal(entry)
|
||||
logFile.Write(append(data, '\n'))
|
||||
}
|
||||
|
||||
log.Printf("[%s][%s] %s", level, category, message)
|
||||
}
|
||||
|
||||
func AddRecording(channelID, filename string, duration int, size int64, status string) {
|
||||
msg := "Запись: " + status + " | Канал: " + channelID + " | Файл: " + filename
|
||||
if duration > 0 {
|
||||
msg += " | Длительность: " + formatDuration(duration)
|
||||
}
|
||||
if size > 0 {
|
||||
msg += " | Размер: " + formatBytes(size)
|
||||
}
|
||||
Add("INFO", "RECORDING", msg)
|
||||
}
|
||||
|
||||
func AddSMB(filename string, success bool, errorMsg string) {
|
||||
status := "УСПЕШНО"
|
||||
if !success {
|
||||
status = "ОШИБКА"
|
||||
}
|
||||
msg := "SMB отправка: " + status + " | Файл: " + filename
|
||||
if errorMsg != "" {
|
||||
msg += " | Ошибка: " + errorMsg
|
||||
}
|
||||
Add("INFO", "SMB", msg)
|
||||
}
|
||||
|
||||
func AddSettings(channelID, setting, value string) {
|
||||
msg := "Изменение настроек | Канал: " + channelID + " | " + setting + " = " + value
|
||||
Add("SETTINGS", "SETTINGS", msg)
|
||||
}
|
||||
|
||||
func AddError(category, message string, err error) {
|
||||
msg := message
|
||||
if err != nil {
|
||||
msg += ": " + err.Error()
|
||||
}
|
||||
Add("ERROR", category, msg)
|
||||
}
|
||||
|
||||
func GetLogs(limit int) []LogEntry {
|
||||
logsMu.RLock()
|
||||
defer logsMu.RUnlock()
|
||||
|
||||
start := 0
|
||||
if len(logs) > limit {
|
||||
start = len(logs) - limit
|
||||
}
|
||||
result := make([]LogEntry, len(logs)-start)
|
||||
copy(result, logs[start:])
|
||||
|
||||
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
||||
result[i], result[j] = result[j], result[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetLogsByCategory(category string, limit int) []LogEntry {
|
||||
logsMu.RLock()
|
||||
defer logsMu.RUnlock()
|
||||
|
||||
var filtered []LogEntry
|
||||
for i := len(logs) - 1; i >= 0 && len(filtered) < limit; i-- {
|
||||
if logs[i].Category == category {
|
||||
filtered = append(filtered, logs[i])
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func ClearLogs() {
|
||||
logsMu.Lock()
|
||||
defer logsMu.Unlock()
|
||||
logs = []LogEntry{}
|
||||
if logFile != nil {
|
||||
logFile.Truncate(0)
|
||||
logFile.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func formatDuration(seconds int) string {
|
||||
if seconds < 60 {
|
||||
return strconv.Itoa(seconds) + "с"
|
||||
}
|
||||
minutes := seconds / 60
|
||||
secs := seconds % 60
|
||||
if minutes < 60 {
|
||||
return strconv.Itoa(minutes) + "м " + strconv.Itoa(secs) + "с"
|
||||
}
|
||||
hours := minutes / 60
|
||||
minutes = minutes % 60
|
||||
return strconv.Itoa(hours) + "ч " + strconv.Itoa(minutes) + "м"
|
||||
}
|
||||
|
||||
func formatBytes(bytes int64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return strconv.FormatInt(bytes, 10) + " B"
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return strconv.FormatInt(bytes/div, 10) + " " + []string{"KB", "MB", "GB"}[exp]
|
||||
}
|
||||
+67
-17
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/logger"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
@@ -168,6 +169,8 @@ func (ch *Channel) StartRecording(outputDir string, testDeviceFunc func(string)
|
||||
log.Printf("Warning: Failed to save recording to DB: %v", err)
|
||||
}
|
||||
|
||||
logger.AddRecording(ch.ID, filename, 0, 0, "НАЧАЛО")
|
||||
|
||||
if testDeviceFunc != nil {
|
||||
if working, msg := testDeviceFunc(ch.Device); !working {
|
||||
return fmt.Errorf("device %s not working: %s", ch.Device, msg)
|
||||
@@ -269,9 +272,7 @@ func (ch *Channel) StartRecording(outputDir string, testDeviceFunc func(string)
|
||||
if recording.ID > 0 {
|
||||
db.UpdateRecordingStatus(recording.ID, "completed")
|
||||
if fileInfo, err := os.Stat(currentPath); err == nil {
|
||||
// Используем ch.Duration который был установлен при старте
|
||||
log.Printf("Recording completed: %s, duration=%d seconds, size=%d bytes",
|
||||
currentFile, ch.Duration, fileInfo.Size())
|
||||
logger.AddRecording(ch.ID, currentFile, ch.Duration, fileInfo.Size(), "ЗАВЕРШЕНА")
|
||||
db.UpdateRecordingStats(recording.ID, fileInfo.Size(), ch.Duration)
|
||||
}
|
||||
}
|
||||
@@ -309,7 +310,32 @@ func (ch *Channel) StopRecording() {
|
||||
ch.stopOnce.Do(func() {
|
||||
log.Printf("=== STOPPING RECORDING for channel %s ===", ch.ID)
|
||||
|
||||
// Сохраняем информацию о файле до остановки
|
||||
currentFile := ch.CurrentFile
|
||||
currentPath := ""
|
||||
smbFolder := ch.SMBFolder
|
||||
|
||||
log.Printf("DEBUG: currentFile=%s, smbFolder=%s", currentFile, smbFolder)
|
||||
|
||||
ch.mu.Lock()
|
||||
if ch.cmd != nil && ch.cmd.Process != nil {
|
||||
for _, arg := range ch.cmd.Args {
|
||||
if strings.HasSuffix(arg, ".wav") {
|
||||
currentPath = arg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if currentPath == "" && currentFile != "" {
|
||||
db := database.GetDB()
|
||||
recordingsDir, _ := db.GetSetting("recordings_dir")
|
||||
if recordingsDir == "" {
|
||||
recordingsDir = ch.OutputDir
|
||||
}
|
||||
currentPath = filepath.Join(recordingsDir, ch.ID, currentFile)
|
||||
}
|
||||
log.Printf("DEBUG: currentPath=%s", currentPath)
|
||||
ch.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ch.stopChan:
|
||||
@@ -330,22 +356,17 @@ func (ch *Channel) StopRecording() {
|
||||
log.Printf("Process PID for channel %s: %d", ch.ID, pid)
|
||||
}
|
||||
|
||||
ch.mu.Unlock()
|
||||
|
||||
if pid > 0 {
|
||||
log.Printf("Killing process %d for channel %s", pid, ch.ID)
|
||||
|
||||
if err := syscall.Kill(pid, syscall.SIGKILL); err != nil {
|
||||
log.Printf("syscall.Kill error: %v", err)
|
||||
}
|
||||
|
||||
killCmd := exec.Command("kill", "-9", strconv.Itoa(pid))
|
||||
if output, err := killCmd.CombinedOutput(); err != nil {
|
||||
log.Printf("kill -9 error: %v, output: %s", err, string(output))
|
||||
} else {
|
||||
log.Printf("kill -9 sent successfully to PID %d", pid)
|
||||
}
|
||||
|
||||
exec.Command("pkill", "-9", "-P", strconv.Itoa(pid)).Run()
|
||||
} else {
|
||||
log.Printf("No PID found, killing all arecord processes")
|
||||
@@ -370,6 +391,41 @@ func (ch *Channel) StopRecording() {
|
||||
|
||||
ch.saveStateToDB()
|
||||
|
||||
// Проверяем существует ли файл
|
||||
fileExists := false
|
||||
if currentPath != "" {
|
||||
if _, err := os.Stat(currentPath); err == nil {
|
||||
fileExists = true
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("DEBUG: fileExists=%v, currentPath=%s, smbFolder=%s, onRecordingCompleted=%v",
|
||||
fileExists, currentPath, smbFolder, onRecordingCompleted != nil)
|
||||
|
||||
// Отправляем файл на SMB если есть файл и папка SMB указана
|
||||
if fileExists && currentPath != "" && smbFolder != "" && onRecordingCompleted != nil {
|
||||
log.Printf("✅ Sending file to SMB: %s, folder: %s, path: %s", currentFile, smbFolder, currentPath)
|
||||
|
||||
db := database.GetDB()
|
||||
if currentFile != "" {
|
||||
recordings, _ := db.GetRecordingsByChannel(ch.ID, 1000)
|
||||
for _, rec := range recordings {
|
||||
if rec.Filename == currentFile {
|
||||
if fileInfo, err := os.Stat(currentPath); err == nil {
|
||||
logger.AddRecording(ch.ID, currentFile, ch.Duration, fileInfo.Size(), "ЗАВЕРШЕНА")
|
||||
db.UpdateRecordingStats(rec.ID, fileInfo.Size(), ch.Duration)
|
||||
}
|
||||
db.UpdateRecordingStatus(rec.ID, "completed")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
onRecordingCompleted(currentFile, currentPath, smbFolder)
|
||||
} else {
|
||||
log.Printf("❌ SMB отправка НЕ выполнена: fileExists=%v, currentPath=%s, smbFolder=%s, callback=%v",
|
||||
fileExists, currentPath, smbFolder, onRecordingCompleted != nil)
|
||||
}
|
||||
|
||||
log.Printf("=== RECORDING STOPPED for channel %s ===", ch.ID)
|
||||
})
|
||||
}
|
||||
@@ -418,6 +474,8 @@ func (ch *Channel) UpdateConfig(config ChannelConfig) {
|
||||
ch.AutoRestart = config.AutoRestart
|
||||
ch.SMBFolder = config.SMBFolder
|
||||
|
||||
logger.AddSettings(ch.ID, "настройки канала", "сохранены")
|
||||
|
||||
log.Printf("Updated channel %s config: device=%s, duration=%d, sample_rate=%d, format=%s, smb_folder=%s",
|
||||
ch.ID, ch.Device, ch.Duration, ch.SampleRate, ch.Format, ch.SMBFolder)
|
||||
|
||||
@@ -453,7 +511,6 @@ func (ch *Channel) GetConfig() ChannelConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// StartSchedule запускает планировщик для канала
|
||||
func (ch *Channel) StartSchedule() {
|
||||
ch.mu.Lock()
|
||||
defer ch.mu.Unlock()
|
||||
@@ -539,12 +596,8 @@ func (ch *Channel) StartSchedule() {
|
||||
db := database.GetDB()
|
||||
db.SetChannelState(ch.ID, true)
|
||||
|
||||
dm := GetDeviceManager()
|
||||
testFunc := func(device string) (bool, string) {
|
||||
if dm != nil {
|
||||
return true, "Device check skipped"
|
||||
}
|
||||
return true, "No device manager"
|
||||
return true, "Device check skipped"
|
||||
}
|
||||
|
||||
if err := ch.StartRecording(ch.OutputDir, testFunc); err != nil {
|
||||
@@ -570,7 +623,6 @@ func (ch *Channel) StartSchedule() {
|
||||
})
|
||||
}
|
||||
|
||||
// StopSchedule останавливает планировщик
|
||||
func (ch *Channel) StopSchedule() {
|
||||
ch.mu.Lock()
|
||||
defer ch.mu.Unlock()
|
||||
@@ -581,7 +633,6 @@ func (ch *Channel) StopSchedule() {
|
||||
}
|
||||
}
|
||||
|
||||
// getAudioCommandWithContext - изменённый для правильной записи WAV
|
||||
func getAudioCommandWithContext(ctx context.Context, device string, sampleRate, channels int, format, filepath string) *exec.Cmd {
|
||||
args := []string{}
|
||||
|
||||
@@ -605,7 +656,6 @@ func getAudioCommandWithContext(ctx context.Context, device string, sampleRate,
|
||||
args = append(args, "-f", "S16_LE")
|
||||
}
|
||||
|
||||
// Используем формат wav - arecord сам правильно заполнит заголовок
|
||||
args = append(args, "-t", "wav")
|
||||
args = append(args, filepath)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user