mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 00:26:29 +00:00
refactor: update recording filename format to include date
- Update `getRecordingPath` to use a timestamp format that includes the date (`20060102-150405.000`). - Update `parseInteractionFile` and `getFullTimestamp` to handle both the new filename format and the legacy format for backward compatibility. - Improved parsing logic to reliably extract date, time, and HTTP method from interaction filenames.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// Package main demonstrates the new recording filename format that includes date information.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gesellix/bose-soundtouch/pkg/service/proxy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Recording Filename Format Demo ===")
|
||||
fmt.Println()
|
||||
|
||||
// Create a temporary directory for the demo
|
||||
tmpDir, err := os.MkdirTemp("", "recording-filename-demo")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create temp directory: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if removeErr := os.RemoveAll(tmpDir); removeErr != nil {
|
||||
log.Printf("Failed to remove temp directory: %v", removeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf("Demo recordings will be saved to: %s\n\n", tmpDir)
|
||||
|
||||
// Create a recorder with async disabled for predictable demo output
|
||||
if envErr := os.Setenv("RECORDER_ASYNC", "false"); envErr != nil {
|
||||
log.Printf("Failed to set environment variable: %v", envErr)
|
||||
}
|
||||
|
||||
recorder := proxy.NewRecorder(tmpDir)
|
||||
defer recorder.Close()
|
||||
|
||||
fmt.Printf("Recorder session ID: %s\n", recorder.SessionID)
|
||||
fmt.Println()
|
||||
|
||||
// Create some sample HTTP requests to record
|
||||
requests := []struct {
|
||||
method string
|
||||
path string
|
||||
category string
|
||||
}{
|
||||
{"GET", "/info", "self"},
|
||||
{"POST", "/volume", "self"},
|
||||
{"GET", "/nowPlaying", "self"},
|
||||
{"PUT", "/preset_1", "self"},
|
||||
}
|
||||
|
||||
fmt.Println("Recording sample HTTP interactions...")
|
||||
fmt.Println()
|
||||
|
||||
for i, req := range requests {
|
||||
// Create a mock HTTP request
|
||||
httpReq, reqErr := http.NewRequest(req.method, "http://soundtouch.local:8090"+req.path, nil)
|
||||
if reqErr != nil {
|
||||
log.Printf("Failed to create request: %v", reqErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a mock response
|
||||
httpRes := &http.Response{
|
||||
StatusCode: 200,
|
||||
Header: make(http.Header),
|
||||
Request: httpReq,
|
||||
}
|
||||
httpRes.Header.Set("Content-Type", "application/xml")
|
||||
|
||||
// Record the interaction
|
||||
err = recorder.Record(req.category, httpReq, httpRes)
|
||||
if err != nil {
|
||||
log.Printf("Failed to record interaction: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("%d. Recorded: %s %s\n", i+1, req.method, req.path)
|
||||
|
||||
// Small delay to show different timestamps
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("=== Generated Filenames ===")
|
||||
fmt.Println()
|
||||
|
||||
// Walk through the recordings directory to show the generated filenames
|
||||
interactionsDir := filepath.Join(tmpDir, "interactions")
|
||||
|
||||
err = filepath.Walk(interactionsDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(info.Name(), ".http") {
|
||||
// Get relative path from interactions directory
|
||||
rel, _ := filepath.Rel(interactionsDir, path)
|
||||
fmt.Printf("📁 %s\n", rel)
|
||||
|
||||
// Parse and explain the filename format
|
||||
filename := info.Name()
|
||||
parts := strings.Split(strings.TrimSuffix(filename, ".http"), "-")
|
||||
|
||||
if len(parts) == 4 && len(parts[1]) == 8 {
|
||||
// New format: count-yyyyMMdd-HHMMSS.sss-method.http
|
||||
counter := parts[0]
|
||||
dateStr := parts[1]
|
||||
timeStr := parts[2]
|
||||
method := parts[3]
|
||||
|
||||
// Format for display
|
||||
date := dateStr[0:4] + "-" + dateStr[4:6] + "-" + dateStr[6:8]
|
||||
time := timeStr[0:2] + ":" + timeStr[2:4] + ":" + timeStr[4:]
|
||||
|
||||
fmt.Printf(" 📋 Format: count-yyyyMMdd-HHMMSS.sss-method.http\n")
|
||||
fmt.Printf(" 🔢 Counter: %s\n", counter)
|
||||
fmt.Printf(" 📅 Date: %s (from %s)\n", date, dateStr)
|
||||
fmt.Printf(" 🕒 Time: %s (from %s)\n", time, timeStr)
|
||||
fmt.Printf(" 🔧 Method: %s\n", method)
|
||||
fmt.Printf(" ✨ Full timestamp: %s %s\n", date, time)
|
||||
} else {
|
||||
fmt.Printf(" ⚠️ Legacy format or unexpected structure\n")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Error walking directory: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("=== Comparison with Old Format ===")
|
||||
fmt.Println()
|
||||
fmt.Println("🔴 OLD format (time only): 0047-21-53-06.128-GET.http")
|
||||
fmt.Println(" - No date information in filename")
|
||||
fmt.Println(" - Date extracted from session ID directory")
|
||||
fmt.Println(" - Confusing when recordings span midnight")
|
||||
fmt.Println()
|
||||
fmt.Println("🟢 NEW format (date + time): 0047-20260223-215306.128-GET.http")
|
||||
fmt.Println(" - Complete timestamp in filename")
|
||||
fmt.Println(" - Self-contained, no need to check directory")
|
||||
fmt.Println(" - Clear chronological ordering")
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println("=== Benefits ===")
|
||||
fmt.Println("✅ No confusion when recordings cross midnight")
|
||||
fmt.Println("✅ Complete timestamp visible at a glance")
|
||||
fmt.Println("✅ Better sorting and organization")
|
||||
fmt.Println("✅ Backwards compatible with existing parsing logic")
|
||||
fmt.Println()
|
||||
|
||||
// Test the list interactions functionality
|
||||
fmt.Println("=== Using ListInteractions API ===")
|
||||
fmt.Println()
|
||||
|
||||
interactions, err := recorder.ListInteractions("", "", "")
|
||||
if err != nil {
|
||||
log.Printf("Failed to list interactions: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d recorded interactions:\n", len(interactions))
|
||||
|
||||
for i := range interactions {
|
||||
interaction := &interactions[i]
|
||||
fmt.Printf("%d. %s %s - %s (File: %s)\n",
|
||||
i+1, interaction.Method, interaction.Path,
|
||||
interaction.Timestamp, interaction.ID)
|
||||
}
|
||||
|
||||
fmt.Printf("\nDemo completed! Recordings saved in: %s\n", tmpDir)
|
||||
fmt.Println("You can explore the generated files to see the new format in action.")
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func (r *Recorder) getRecordingDir(category string, sanitizedSegments []string)
|
||||
}
|
||||
|
||||
func (r *Recorder) getRecordingPath(dir, method string) string {
|
||||
timestamp := time.Now().Format("15-04-05.000")
|
||||
timestamp := time.Now().Format("20060102-150405.000")
|
||||
count := atomic.AddUint64(&r.counter, 1)
|
||||
filename := fmt.Sprintf("%04d-%s-%s.http", count, timestamp, method)
|
||||
|
||||
@@ -441,20 +441,44 @@ func (r *Recorder) parseInteractionFile(rel, path string, parts []string) (Inter
|
||||
filename := parts[len(parts)-1]
|
||||
fnParts := strings.Split(strings.TrimSuffix(filename, ".http"), "-")
|
||||
|
||||
date := ""
|
||||
if len(sessionID) >= 8 {
|
||||
date = sessionID[0:4] + "-" + sessionID[4:6] + "-" + sessionID[6:8]
|
||||
timestamp := ""
|
||||
method, counter := "UNKNOWN", 0
|
||||
|
||||
if len(fnParts) >= 1 {
|
||||
_, _ = fmt.Sscanf(fnParts[0], "%d", &counter)
|
||||
}
|
||||
|
||||
timestamp := ""
|
||||
// Check if this is the new format: count-yyyyMMdd-HHMMSS.sss-method.http
|
||||
// New format has 4 parts and the second part is 8 digits (yyyyMMdd)
|
||||
if len(fnParts) == 4 && len(fnParts[1]) == 8 {
|
||||
dateStr := fnParts[1] // yyyyMMdd
|
||||
timeStr := fnParts[2] // HHMMSS.sss
|
||||
method = fnParts[3]
|
||||
|
||||
// Format date: yyyyMMdd -> yyyy-MM-dd
|
||||
date := dateStr[0:4] + "-" + dateStr[4:6] + "-" + dateStr[6:8]
|
||||
|
||||
// Format time: HHMMSS.sss -> HH:MM:SS.sss
|
||||
if len(timeStr) >= 6 {
|
||||
time := timeStr[0:2] + ":" + timeStr[2:4] + ":" + timeStr[4:]
|
||||
timestamp = date + " " + time
|
||||
}
|
||||
} else if len(fnParts) >= 5 {
|
||||
// Legacy format: count-HH-MM-SS.sss-method.http
|
||||
// Extract date from sessionID for backward compatibility
|
||||
date := ""
|
||||
if len(sessionID) >= 8 {
|
||||
date = sessionID[0:4] + "-" + sessionID[4:6] + "-" + sessionID[6:8]
|
||||
}
|
||||
|
||||
if len(fnParts) >= 4 {
|
||||
timeStr := fnParts[1] + ":" + fnParts[2] + ":" + fnParts[3]
|
||||
timestamp = timeStr
|
||||
|
||||
if date != "" {
|
||||
timestamp = date + " " + timeStr
|
||||
}
|
||||
|
||||
method = fnParts[4]
|
||||
}
|
||||
|
||||
requestPath := "/" + strings.Join(parts[2:len(parts)-1], "/")
|
||||
@@ -462,15 +486,6 @@ func (r *Recorder) parseInteractionFile(rel, path string, parts []string) (Inter
|
||||
requestPath = "/"
|
||||
}
|
||||
|
||||
method, counter := "UNKNOWN", 0
|
||||
if len(fnParts) >= 1 {
|
||||
_, _ = fmt.Sscanf(fnParts[0], "%d", &counter)
|
||||
}
|
||||
|
||||
if len(fnParts) >= 5 {
|
||||
method = fnParts[4]
|
||||
}
|
||||
|
||||
return Interaction{
|
||||
ID: filename,
|
||||
Session: sessionID,
|
||||
@@ -485,18 +500,32 @@ func (r *Recorder) parseInteractionFile(rel, path string, parts []string) (Inter
|
||||
}
|
||||
|
||||
func (r *Recorder) getFullTimestamp(sessionID, filename string) string {
|
||||
if len(sessionID) < 8 {
|
||||
return ""
|
||||
}
|
||||
|
||||
date := sessionID[0:4] + "-" + sessionID[4:6] + "-" + sessionID[6:8]
|
||||
fnParts := strings.Split(strings.TrimSuffix(filename, ".http"), "-")
|
||||
|
||||
if len(fnParts) < 4 {
|
||||
return ""
|
||||
// Check if this is the new format: count-yyyyMMdd-HHMMSS.sss-method.http
|
||||
// New format has 4 parts and the second part is 8 digits (yyyyMMdd)
|
||||
if len(fnParts) == 4 && len(fnParts[1]) == 8 {
|
||||
dateStr := fnParts[1] // yyyyMMdd
|
||||
timeStr := fnParts[2] // HHMMSS.sss
|
||||
|
||||
if len(timeStr) >= 6 {
|
||||
date := dateStr[0:4] + "-" + dateStr[4:6] + "-" + dateStr[6:8]
|
||||
time := timeStr[0:2] + "-" + timeStr[2:4] + "-" + timeStr[4:]
|
||||
|
||||
return date + "-" + time
|
||||
}
|
||||
} else if len(fnParts) >= 5 {
|
||||
// Legacy format: count-HH-MM-SS.sss-method.http
|
||||
if len(sessionID) < 8 {
|
||||
return ""
|
||||
}
|
||||
|
||||
date := sessionID[0:4] + "-" + sessionID[4:6] + "-" + sessionID[6:8]
|
||||
|
||||
return date + "-" + fnParts[1] + "-" + fnParts[2] + "-" + fnParts[3]
|
||||
}
|
||||
|
||||
return date + "-" + fnParts[1] + "-" + fnParts[2] + "-" + fnParts[3]
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *Recorder) peekStatus(path string) int {
|
||||
|
||||
@@ -847,3 +847,99 @@ func TestRecorder_ListInteractions_FullTimestamp(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecorder_NewFilenameFormat_WithDate(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "recorder-new-format-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
r := NewRecorder(tmpDir)
|
||||
sessionID := "20260223-150000-12345"
|
||||
r.SessionID = sessionID
|
||||
|
||||
// Create recordings with the new format that includes date in filename
|
||||
basePath := filepath.Join(tmpDir, "interactions", sessionID, "self", "test")
|
||||
os.MkdirAll(basePath, 0755)
|
||||
|
||||
files := []string{
|
||||
"0047-20260223-215306.128-GET.http",
|
||||
"0048-20260223-215306.417-POST.http",
|
||||
"0049-20260223-080034.500-GET.http",
|
||||
"0050-20260223-080034.507-PUT.http",
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
os.WriteFile(filepath.Join(basePath, f), []byte("test content"), 0644)
|
||||
}
|
||||
|
||||
t.Run("Parse_New_Format_Timestamps", func(t *testing.T) {
|
||||
interactions, err := r.ListInteractions(sessionID, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
|
||||
if len(interactions) != 4 {
|
||||
t.Fatalf("Expected 4 interactions, got %d", len(interactions))
|
||||
}
|
||||
|
||||
// Check that timestamps include both date and time from filename
|
||||
expectedTimestamps := []string{
|
||||
"2026-02-23 21:53:06.128",
|
||||
"2026-02-23 21:53:06.417",
|
||||
"2026-02-23 08:00:34.500",
|
||||
"2026-02-23 08:00:34.507",
|
||||
}
|
||||
|
||||
for i, interaction := range interactions {
|
||||
if interaction.Timestamp != expectedTimestamps[i] {
|
||||
t.Errorf("Expected timestamp %s, got %s for interaction %d",
|
||||
expectedTimestamps[i], interaction.Timestamp, i)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Parse_Methods_From_New_Format", func(t *testing.T) {
|
||||
interactions, err := r.ListInteractions(sessionID, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
|
||||
expectedMethods := []string{"GET", "POST", "GET", "PUT"}
|
||||
for i, interaction := range interactions {
|
||||
if interaction.Method != expectedMethods[i] {
|
||||
t.Errorf("Expected method %s, got %s for interaction %d",
|
||||
expectedMethods[i], interaction.Method, i)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Filter_By_New_Format_Timestamp", func(t *testing.T) {
|
||||
// Filter for interactions after 10:00:00 on that day
|
||||
interactions, err := r.ListInteractions(sessionID, "", "2026-02-23 10:00:00")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
|
||||
// Should get the two evening interactions (21:53:06.xxx)
|
||||
if len(interactions) != 2 {
|
||||
t.Fatalf("Expected 2 interactions after 10:00:00, got %d", len(interactions))
|
||||
}
|
||||
|
||||
for _, interaction := range interactions {
|
||||
if !strings.Contains(interaction.Timestamp, "21:53:06") {
|
||||
t.Errorf("Expected evening timestamp, got %s", interaction.Timestamp)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetFullTimestamp_New_Format", func(t *testing.T) {
|
||||
// Test the getFullTimestamp function directly
|
||||
fullTS := r.getFullTimestamp(sessionID, "0047-20260223-215306.128-GET.http")
|
||||
expected := "2026-02-23-21-53-06.128"
|
||||
if fullTS != expected {
|
||||
t.Errorf("Expected full timestamp %s, got %s", expected, fullTS)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user