662 lines
19 KiB
Go
662 lines
19 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"equipment-analyzer/internal/capture"
|
|
"equipment-analyzer/internal/config"
|
|
"equipment-analyzer/internal/model"
|
|
"equipment-analyzer/internal/optimizer"
|
|
"equipment-analyzer/internal/utils"
|
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
|
)
|
|
|
|
type App struct {
|
|
ctx context.Context
|
|
config *config.Config
|
|
logger *utils.Logger
|
|
captureService *CaptureService
|
|
parserService *ParserService
|
|
database *model.Database
|
|
databaseService *DatabaseService
|
|
}
|
|
|
|
func NewApp(cfg *config.Config, logger *utils.Logger) *App {
|
|
// init database
|
|
database, err := model.NewDatabase()
|
|
if err != nil {
|
|
logger.Error("database init failed", "error", err)
|
|
// allow app to run without db features
|
|
parserService := NewParserService(cfg, logger)
|
|
return &App{
|
|
config: cfg,
|
|
logger: logger,
|
|
captureService: NewCaptureService(cfg, logger, parserService),
|
|
parserService: parserService,
|
|
}
|
|
}
|
|
|
|
databaseService := NewDatabaseService(database, logger)
|
|
|
|
parserService := NewParserService(cfg, logger)
|
|
return &App{
|
|
config: cfg,
|
|
logger: logger,
|
|
captureService: NewCaptureService(cfg, logger, parserService),
|
|
parserService: parserService,
|
|
database: database,
|
|
databaseService: databaseService,
|
|
}
|
|
}
|
|
|
|
func (a *App) Startup(ctx context.Context) {
|
|
a.ctx = ctx
|
|
a.captureService.SetBeforeRemote(func() {
|
|
if a.ctx != nil {
|
|
runtime.EventsEmit(a.ctx, "capture:ready_to_parse")
|
|
}
|
|
})
|
|
a.logger.Info("app startup")
|
|
}
|
|
|
|
func (a *App) DomReady(ctx context.Context) {
|
|
a.logger.Info("dom ready")
|
|
}
|
|
|
|
func (a *App) BeforeClose(ctx context.Context) (prevent bool) {
|
|
a.logger.Info("app closing")
|
|
return false
|
|
}
|
|
|
|
func (a *App) Shutdown(ctx context.Context) {
|
|
a.logger.Info("app shutdown")
|
|
|
|
if a.database != nil {
|
|
if err := a.database.Close(); err != nil {
|
|
a.logger.Error("failed to close database connection", "error", err)
|
|
} else {
|
|
a.logger.Info("database connection closed")
|
|
}
|
|
}
|
|
}
|
|
|
|
// OptimizeBuilds runs the optimizer on latest parsed data.
|
|
func (a *App) OptimizeBuilds(req model.OptimizeRequest) (*model.OptimizeResponse, error) {
|
|
log.Printf("[service] OptimizeBuilds entry heroId=%s", req.HeroID)
|
|
parsedResult, err := a.GetLatestParsedDataFromDatabase()
|
|
if err != nil {
|
|
log.Printf("[service] OptimizeBuilds get data failed: %v", err)
|
|
return nil, err
|
|
}
|
|
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
|
|
log.Printf("[service] OptimizeBuilds no data items=%d heroes=%d", len(parsedResult.Items), len(parsedResult.Heroes))
|
|
return &model.OptimizeResponse{TotalCombos: 0, Results: []model.OptimizeResult{}}, nil
|
|
}
|
|
|
|
items, err := optimizer.ParseItems(parsedResult.Items)
|
|
if err != nil {
|
|
log.Printf("[service] OptimizeBuilds parse items failed: %v", err)
|
|
return nil, fmt.Errorf("parse items failed: %w", err)
|
|
}
|
|
logMissingSetStats(parsedResult.Items)
|
|
heroes, err := optimizer.ParseHeroes(parsedResult.Heroes)
|
|
if err != nil {
|
|
log.Printf("[service] OptimizeBuilds parse heroes failed: %v", err)
|
|
return nil, fmt.Errorf("parse heroes failed: %w", err)
|
|
}
|
|
templates, err := a.parserService.GetHeroTemplates()
|
|
if err != nil {
|
|
log.Printf("[service] OptimizeBuilds hero templates missing: %v", err)
|
|
return nil, fmt.Errorf("未读取到英雄信息数据")
|
|
}
|
|
templateHeroes := make([]model.OptimizeHero, 0, len(templates))
|
|
for _, t := range templates {
|
|
templateHeroes = append(templateHeroes, model.OptimizeHero{
|
|
Code: t.Code,
|
|
Name: t.Name,
|
|
BaseAtk: t.BaseAtk,
|
|
BaseDef: t.BaseDef,
|
|
BaseHp: t.BaseHp,
|
|
BaseSpd: t.BaseSpd,
|
|
BaseCr: t.BaseCr,
|
|
BaseCd: t.BaseCd,
|
|
BaseEff: t.BaseEff,
|
|
BaseRes: t.BaseRes,
|
|
Atk: t.BaseAtk,
|
|
Def: t.BaseDef,
|
|
Hp: t.BaseHp,
|
|
Spd: t.BaseSpd,
|
|
Cr: t.BaseCr,
|
|
Cd: t.BaseCd,
|
|
Eff: t.BaseEff,
|
|
Res: t.BaseRes,
|
|
})
|
|
}
|
|
// Use template heroes for optimizer to match the template-based selection.
|
|
heroes = templateHeroes
|
|
|
|
log.Printf("[service] OptimizeBuilds parsed items=%d heroes=%d", len(items), len(heroes))
|
|
resp, err := optimizer.Optimize(items, heroes, req)
|
|
if err != nil {
|
|
log.Printf("[service] OptimizeBuilds optimize failed: %v", err)
|
|
return nil, err
|
|
}
|
|
log.Printf("[service] OptimizeBuilds done results=%d totalCombos=%d", len(resp.Results), resp.TotalCombos)
|
|
return resp, nil
|
|
}
|
|
|
|
// GetNetworkInterfaces returns available network interfaces.
|
|
func (a *App) GetNetworkInterfaces() ([]model.NetworkInterface, error) {
|
|
interfaces, err := capture.GetNetworkInterfaces()
|
|
if err != nil {
|
|
a.logger.Error("get network interfaces failed", "error", err)
|
|
return nil, err
|
|
}
|
|
return interfaces, nil
|
|
}
|
|
|
|
// GetHeroTemplates returns template heroes from local herodata.json.
|
|
func (a *App) GetHeroTemplates() ([]model.HeroTemplate, error) {
|
|
return a.parserService.GetHeroTemplates()
|
|
}
|
|
|
|
// StartCapture starts capture on the given interface.
|
|
func (a *App) StartCapture(interfaceName string) error {
|
|
if a.captureService.IsCapturing() {
|
|
return fmt.Errorf("capture already running")
|
|
}
|
|
|
|
timeoutMs := a.config.Capture.DefaultTimeout
|
|
if timeoutMs > 500 {
|
|
log.Printf("[service] capture timeout too high (%dms), clamp to 500ms for responsive stop", timeoutMs)
|
|
timeoutMs = 500
|
|
}
|
|
|
|
config := capture.Config{
|
|
InterfaceName: interfaceName,
|
|
Filter: a.config.Capture.DefaultFilter,
|
|
Timeout: time.Duration(timeoutMs) * time.Millisecond,
|
|
BufferSize: a.config.Capture.BufferSize,
|
|
}
|
|
|
|
err := a.captureService.StartCaptureAsync(context.Background(), config, func() {
|
|
if a.ctx != nil {
|
|
runtime.EventsEmit(a.ctx, "capture:started")
|
|
}
|
|
}, func(err error) {
|
|
a.logger.Error("start capture failed", "error", err)
|
|
if a.ctx != nil {
|
|
runtime.EventsEmit(a.ctx, "capture:start_failed", err.Error())
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.logger.Info("capture start requested", "interface", interfaceName)
|
|
return nil
|
|
}
|
|
|
|
// StopCapture stops capture.
|
|
func (a *App) StopCapture() error {
|
|
if !a.captureService.IsCapturing() {
|
|
return fmt.Errorf("capture not running")
|
|
}
|
|
|
|
err := a.captureService.StopCapture()
|
|
if err != nil {
|
|
a.logger.Error("stop capture failed", "error", err)
|
|
return err
|
|
}
|
|
|
|
a.captureService.ProcessAllData()
|
|
|
|
a.logger.Info("capture stopped")
|
|
return nil
|
|
}
|
|
|
|
// GetCapturedData returns raw captured data.
|
|
func (a *App) GetCapturedData() ([]string, error) {
|
|
return a.captureService.GetCapturedData(), nil
|
|
}
|
|
|
|
// ParseData parses captured data to JSON (remote parser).
|
|
func (a *App) ParseData(hexDataList []string) (string, error) {
|
|
_, rawJson, err := a.parserService.ParseHexData(hexDataList)
|
|
if err != nil {
|
|
a.logger.Error("parse data failed", "error", err)
|
|
return "", err
|
|
}
|
|
return rawJson, nil
|
|
}
|
|
|
|
// ExportData exports data to a file.
|
|
func (a *App) ExportData(hexDataList []string, filename string) error {
|
|
result, rawJson, err := a.parserService.ParseHexData(hexDataList)
|
|
if err != nil {
|
|
a.logger.Error("parse data failed", "error", err)
|
|
return err
|
|
}
|
|
|
|
a.logger.Info("export data", "filename", filename, "count", len(result.Items))
|
|
err = utils.WriteFile(filename, []byte(rawJson))
|
|
if err != nil {
|
|
a.logger.Error("write file failed", "error", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExportCurrentData exports latest data from database to a file.
|
|
func (a *App) ExportCurrentData(filename string) error {
|
|
if a.databaseService == nil {
|
|
return fmt.Errorf("database service not initialized")
|
|
}
|
|
|
|
parsedResult, err := a.GetLatestParsedDataFromDatabase()
|
|
if err != nil {
|
|
a.logger.Error("failed to load data from database", "error", err)
|
|
return err
|
|
}
|
|
|
|
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
|
|
return fmt.Errorf("no data to export")
|
|
}
|
|
|
|
exportData := map[string]interface{}{
|
|
"items": parsedResult.Items,
|
|
"heroes": parsedResult.Heroes,
|
|
}
|
|
|
|
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
|
if err != nil {
|
|
a.logger.Error("failed to marshal data", "error", err)
|
|
return err
|
|
}
|
|
|
|
err = utils.WriteFile(filename, jsonData)
|
|
if err != nil {
|
|
a.logger.Error("write file failed", "error", err)
|
|
return err
|
|
}
|
|
|
|
a.logger.Info("export current data succeeded", "filename", filename, "items_count", len(parsedResult.Items), "heroes_count", len(parsedResult.Heroes))
|
|
return nil
|
|
}
|
|
|
|
// GetCurrentDataForExport returns latest data from database as JSON string.
|
|
func (a *App) GetCurrentDataForExport() (string, error) {
|
|
if a.databaseService == nil {
|
|
return "", fmt.Errorf("database service not initialized")
|
|
}
|
|
|
|
parsedResult, err := a.GetLatestParsedDataFromDatabase()
|
|
if err != nil {
|
|
a.logger.Error("failed to load data from database", "error", err)
|
|
return "", err
|
|
}
|
|
|
|
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
|
|
return "", fmt.Errorf("no data to export")
|
|
}
|
|
|
|
exportData := map[string]interface{}{
|
|
"items": parsedResult.Items,
|
|
"heroes": parsedResult.Heroes,
|
|
}
|
|
|
|
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
|
if err != nil {
|
|
a.logger.Error("failed to marshal data", "error", err)
|
|
return "", err
|
|
}
|
|
|
|
return string(jsonData), nil
|
|
}
|
|
|
|
// GetCaptureStatus returns capture status.
|
|
func (a *App) GetCaptureStatus() model.CaptureStatus {
|
|
return model.CaptureStatus{
|
|
IsCapturing: a.captureService.IsCapturing(),
|
|
Status: a.getStatusMessage(),
|
|
}
|
|
}
|
|
|
|
func (a *App) getStatusMessage() string {
|
|
if a.captureService.IsCapturing() {
|
|
return "capturing"
|
|
}
|
|
if a.captureService.IsStarting() {
|
|
return "starting"
|
|
}
|
|
return "ready"
|
|
}
|
|
|
|
// ReadRawJsonFile is deprecated; use GetLatestParsedDataFromDatabase.
|
|
func (a *App) ReadRawJsonFile() (*model.ParsedResult, error) {
|
|
return a.GetLatestParsedDataFromDatabase()
|
|
}
|
|
|
|
// StopAndParseCapture stops capture and parses data.
|
|
func (a *App) StopAndParseCapture() (*model.ParsedResult, error) {
|
|
log.Printf("[service] StopAndParseCapture entry")
|
|
result, err := a.captureService.StopAndParseCapture()
|
|
if err != nil {
|
|
a.logger.Error("stop and parse capture failed", "error", err)
|
|
return nil, err
|
|
}
|
|
|
|
if a.databaseService != nil && result != nil {
|
|
itemsJSON := "[]"
|
|
if result.Items != nil {
|
|
if jsonData, err := json.Marshal(result.Items); err == nil {
|
|
itemsJSON = string(jsonData)
|
|
}
|
|
}
|
|
|
|
heroesJSON := "[]"
|
|
if result.Heroes != nil {
|
|
if jsonData, err := json.Marshal(result.Heroes); err == nil {
|
|
heroesJSON = string(jsonData)
|
|
}
|
|
}
|
|
|
|
sessionName := fmt.Sprintf("capture_%d", time.Now().Unix())
|
|
if err := a.databaseService.SaveParsedDataToDatabase(sessionName, itemsJSON, heroesJSON, result.GearTxt); err != nil {
|
|
a.logger.Error("save parsed data failed", "error", err)
|
|
} else {
|
|
a.logger.Info("parsed data saved", "session_name", sessionName)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// SaveParsedDataToDatabase saves parsed data.
|
|
func (a *App) SaveParsedDataToDatabase(sessionName string, itemsJSON, heroesJSON, gearTxt string) error {
|
|
if a.databaseService == nil {
|
|
return fmt.Errorf("database service not initialized")
|
|
}
|
|
return a.databaseService.SaveParsedDataToDatabase(sessionName, itemsJSON, heroesJSON, gearTxt)
|
|
}
|
|
|
|
// ParseAndSaveRawJson parses raw gear json and saves to database.
|
|
func (a *App) ParseAndSaveRawJson(sessionName string, rawJson string) (*model.ParsedResult, error) {
|
|
if a.databaseService == nil {
|
|
return nil, fmt.Errorf("database service not initialized")
|
|
}
|
|
if sessionName == "" {
|
|
return nil, fmt.Errorf("session name cannot be empty")
|
|
}
|
|
result, err := a.parserService.ReadRawJsonFile(rawJson)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
itemsJSON := "[]"
|
|
if result.Items != nil {
|
|
if jsonData, err := json.Marshal(result.Items); err == nil {
|
|
itemsJSON = string(jsonData)
|
|
}
|
|
}
|
|
heroesJSON := "[]"
|
|
if result.Heroes != nil {
|
|
if jsonData, err := json.Marshal(result.Heroes); err == nil {
|
|
heroesJSON = string(jsonData)
|
|
}
|
|
}
|
|
if err := a.databaseService.SaveParsedDataToDatabase(sessionName, itemsJSON, heroesJSON, result.GearTxt); err != nil {
|
|
a.logger.Error("save parsed data failed", "error", err, "session_name", sessionName)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// GetLatestParsedDataFromDatabase returns latest parsed data from database.
|
|
func (a *App) GetLatestParsedDataFromDatabase() (*model.ParsedResult, error) {
|
|
if a.databaseService == nil {
|
|
return nil, fmt.Errorf("database service not initialized")
|
|
}
|
|
|
|
itemsJSON, heroesJSON, gearTxt, err := a.databaseService.GetLatestParsedDataFromDatabase()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var items []interface{}
|
|
if itemsJSON != "" {
|
|
if err := json.Unmarshal([]byte(itemsJSON), &items); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal items: %w", err)
|
|
}
|
|
}
|
|
logMissingSetStats(items)
|
|
|
|
var heroes []interface{}
|
|
if heroesJSON != "" {
|
|
if err := json.Unmarshal([]byte(heroesJSON), &heroes); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal heroes: %w", err)
|
|
}
|
|
}
|
|
|
|
return &model.ParsedResult{
|
|
Items: items,
|
|
Heroes: heroes,
|
|
GearTxt: gearTxt,
|
|
}, nil
|
|
}
|
|
|
|
// GetParsedSessions returns all parsed sessions.
|
|
func (a *App) GetParsedSessions() ([]model.ParsedSession, error) {
|
|
if a.databaseService == nil {
|
|
return nil, fmt.Errorf("database service not initialized")
|
|
}
|
|
return a.databaseService.GetParsedSessions()
|
|
}
|
|
|
|
// GetParsedDataByID returns parsed data by session id.
|
|
func (a *App) GetParsedDataByID(id int64) (*model.ParsedResult, error) {
|
|
if a.databaseService == nil {
|
|
return nil, fmt.Errorf("database service not initialized")
|
|
}
|
|
itemsJSON, heroesJSON, gearTxt, err := a.databaseService.GetParsedDataByID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var items []interface{}
|
|
if itemsJSON != "" {
|
|
if err := json.Unmarshal([]byte(itemsJSON), &items); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal items: %w", err)
|
|
}
|
|
}
|
|
|
|
var heroes []interface{}
|
|
if heroesJSON != "" {
|
|
if err := json.Unmarshal([]byte(heroesJSON), &heroes); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal heroes: %w", err)
|
|
}
|
|
}
|
|
|
|
return &model.ParsedResult{
|
|
Items: items,
|
|
Heroes: heroes,
|
|
GearTxt: gearTxt,
|
|
}, nil
|
|
}
|
|
|
|
// UpdateParsedSessionName updates session name.
|
|
func (a *App) UpdateParsedSessionName(id int64, name string) error {
|
|
if a.databaseService == nil {
|
|
return fmt.Errorf("database service not initialized")
|
|
}
|
|
if name == "" {
|
|
return fmt.Errorf("session name cannot be empty")
|
|
}
|
|
return a.databaseService.UpdateParsedSessionName(id, name)
|
|
}
|
|
|
|
// DeleteParsedSession deletes a parsed session by id.
|
|
func (a *App) DeleteParsedSession(id int64) error {
|
|
if a.databaseService == nil {
|
|
return fmt.Errorf("database service not initialized")
|
|
}
|
|
return a.databaseService.DeleteParsedSession(id)
|
|
}
|
|
|
|
// SaveAppSetting saves app setting.
|
|
func (a *App) SaveAppSetting(key, value string) error {
|
|
if a.databaseService == nil {
|
|
return fmt.Errorf("database service not initialized")
|
|
}
|
|
return a.databaseService.SaveAppSetting(key, value)
|
|
}
|
|
|
|
// GetAppSetting gets app setting.
|
|
func (a *App) GetAppSetting(key string) (string, error) {
|
|
if a.databaseService == nil {
|
|
return "", fmt.Errorf("database service not initialized")
|
|
}
|
|
return a.databaseService.GetAppSetting(key)
|
|
}
|
|
|
|
// GetAllAppSettings gets all app settings.
|
|
func (a *App) GetAllAppSettings() (map[string]string, error) {
|
|
if a.databaseService == nil {
|
|
return nil, fmt.Errorf("database service not initialized")
|
|
}
|
|
return a.databaseService.GetAllAppSettings()
|
|
}
|
|
|
|
func logMissingSetStats(items []interface{}) {
|
|
if len(items) == 0 {
|
|
return
|
|
}
|
|
var total int
|
|
var emptyF int
|
|
var emptySet int
|
|
var healthF int
|
|
var healthSet int
|
|
var mapHp int
|
|
setCounts := map[string]int{}
|
|
uncategorized := 0
|
|
emptySetByF := map[string]int{}
|
|
emptySetByGear := map[string]int{}
|
|
ingameSetMap := map[string]string{
|
|
"set_acc": "HitSet",
|
|
"set_att": "AttackSet",
|
|
"set_coop": "UnitySet",
|
|
"set_counter": "CounterSet",
|
|
"set_cri_dmg": "DestructionSet",
|
|
"set_cri": "CriticalSet",
|
|
"set_def": "DefenseSet",
|
|
"set_immune": "ImmunitySet",
|
|
"set_max_hp": "HealthSet",
|
|
"set_penetrate": "PenetrationSet",
|
|
"set_rage": "RageSet",
|
|
"set_res": "ResistSet",
|
|
"set_revenge": "RevengeSet",
|
|
"set_scar": "InjurySet",
|
|
"set_speed": "SpeedSet",
|
|
"set_vampire": "LifestealSet",
|
|
"set_shield": "ProtectionSet",
|
|
"set_torrent": "TorrentSet",
|
|
"set_revenant": "ReversalSet",
|
|
"set_riposte": "RiposteSet",
|
|
"set_chase": "PursuitSet",
|
|
"set_opener": "WarfareSet",
|
|
}
|
|
for _, raw := range items {
|
|
item, ok := raw.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
total++
|
|
if f, ok := item["f"]; !ok || f == nil || f == "" {
|
|
emptyF++
|
|
} else {
|
|
if f == "set_max_hp" {
|
|
healthF++
|
|
}
|
|
}
|
|
setValue, _ := item["set"].(string)
|
|
if setValue == "" {
|
|
emptySet++
|
|
fValue, _ := item["f"].(string)
|
|
if fValue != "" {
|
|
emptySetByF[fValue] = emptySetByF[fValue] + 1
|
|
}
|
|
if gear, ok := item["gear"].(string); ok && gear != "" {
|
|
emptySetByGear[gear] = emptySetByGear[gear] + 1
|
|
}
|
|
if fValue != "" {
|
|
if mapped, ok := ingameSetMap[fValue]; ok {
|
|
setCounts[mapped] = setCounts[mapped] + 1
|
|
} else {
|
|
uncategorized++
|
|
}
|
|
} else {
|
|
uncategorized++
|
|
}
|
|
} else {
|
|
setCounts[setValue] = setCounts[setValue] + 1
|
|
}
|
|
if set, ok := item["set"].(string); ok && set == "HealthSet" {
|
|
healthSet++
|
|
}
|
|
if f, ok := item["f"]; ok && f == "set_max_hp" {
|
|
mapHp++
|
|
}
|
|
}
|
|
log.Printf("[service] set stats: total=%d empty_f=%d empty_set=%d set_max_hp=%d HealthSet=%d mapped_hp=%d", total, emptyF, emptySet, healthF, healthSet, mapHp)
|
|
log.Printf("[service] set counts: %+v", setCounts)
|
|
log.Printf("[service] uncategorized items: %d", uncategorized)
|
|
if emptySet > 0 {
|
|
log.Printf("[service] empty set by f: %+v", emptySetByF)
|
|
log.Printf("[service] empty set by gear: %+v", emptySetByGear)
|
|
}
|
|
}
|
|
|
|
// StartCaptureWithFilter allows frontend to provide a custom BPF filter.
|
|
func (a *App) StartCaptureWithFilter(interfaceName string, filter string) error {
|
|
a.logger.Info("StartCaptureWithFilter requested", "interface", interfaceName, "filter", filter)
|
|
if a.captureService.IsCapturing() {
|
|
return fmt.Errorf("capture already running")
|
|
}
|
|
|
|
useFilter := filter
|
|
if useFilter == "" {
|
|
useFilter = a.config.Capture.DefaultFilter
|
|
}
|
|
|
|
timeoutMs := a.config.Capture.DefaultTimeout
|
|
if timeoutMs > 500 {
|
|
log.Printf("[service] capture timeout too high (%dms), clamp to 500ms for responsive stop", timeoutMs)
|
|
timeoutMs = 500
|
|
}
|
|
|
|
config := capture.Config{
|
|
InterfaceName: interfaceName,
|
|
Filter: useFilter,
|
|
Timeout: time.Duration(timeoutMs) * time.Millisecond,
|
|
BufferSize: a.config.Capture.BufferSize,
|
|
}
|
|
|
|
err := a.captureService.StartCaptureAsync(context.Background(), config, func() {
|
|
if a.ctx != nil {
|
|
runtime.EventsEmit(a.ctx, "capture:started")
|
|
}
|
|
}, func(err error) {
|
|
a.logger.Error("start capture failed", "error", err)
|
|
if a.ctx != nil {
|
|
runtime.EventsEmit(a.ctx, "capture:start_failed", err.Error())
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.logger.Info("capture start requested", "interface", interfaceName, "filter", useFilter)
|
|
return nil
|
|
}
|