package core import ( "context" "errors" "github.com/google/uuid" "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" "sync" "time" ) type OpMode uint8 const ( STOPPED OpMode = iota LIVE RECORDING REPLAY ) type Service interface { AllTrackings() ([]TrackingMetadata, error) StartPipeline(cols ...CollectorType) (string, error) StartRecord() (string, error) StopRecord() (*TrackingMetadata, error) StopAll() (*TrackingMetadata, error) LoadTracking(trackingId uuid.UUID) (*Tracking, error) DeleteTracking(trackingId uuid.UUID) StartReplay() PauseReplay() StopReplay() } type Tracker interface { Put(data SensorData) Recorder } type Recorder interface { SetRecording(s bool) (ok bool) IsRecording() bool } type trackingService struct { opMode OpMode tracking *Tracking pipeline *pipelineRecord collectors []Collector store Storer publisher Publisher config *Configuration recSem *semaphore.Weighted mu *sync.RWMutex } func TrackingService(c *Configuration, s Storer, p Publisher) *trackingService { t := &Tracking{} ts := &trackingService{ tracking: t, opMode: STOPPED, collectors: nil, recSem: semaphore.NewWeighted(1), mu: &sync.RWMutex{}, config: c, store: s, publisher: p, } // first initialize of tcp collector to to open tcp port NewCollector(c, TCP) return ts } func (t *trackingService) Put(data SensorData) { if !t.IsRecording() { return } t.mu.Lock() t.tracking.Data = append(t.tracking.Data, data) t.tracking.Size++ logrus.Traceln("raw data points: len->", len(t.tracking.Data)) t.mu.Unlock() } func (t *trackingService) SetRecording(s bool) (ok bool) { if okay := t.recSem.TryAcquire(1); okay && s { // if i want to turn on and can get semaphore then return success return true } else if !s && !okay { // if i want to turn off and cant get semaphore, i can safely turn off by releasing semaphore and return success t.recSem.Release(1) return true } return false } func (t *trackingService) IsRecording() bool { if t.recSem.TryAcquire(1) { t.recSem.Release(1) return false } return true } func (t *trackingService) StartPipeline(cols ...CollectorType) (string, error) { logrus.Info("SERVICE: NEW PIPELINE") if t.opMode == RECORDING { txt := "trackingservice: please stop recording before resetting pipeline" logrus.Warn(txt) return "RECORDING", errors.New(txt) } if t.opMode == LIVE { txt := "trackingservice: stop tracking running stream before creating new one" logrus.Warnln(txt) //t.StopAll() //time.Sleep(1000 * time.Millisecond) return "record already running since: " + t.tracking.TimeCreated.String(), errors.New(txt) } logrus.Debugln("new tracking:", cols) t.opMode = LIVE t.collectors = nil var tcp, ser chan interface{} for _, col := range cols { c := NewCollector(t.config, col) t.collectors = append(t.collectors, c) if col == TCP { tcp = c.OutChannel() } if col == SERIAL { ser = c.OutChannel() } c.Collect() } t.safelyReplaceTracking(newTracking()) t.tracking.Collectors = cols NewRecordPipeline(t.publisher, t, tcp, ser) t.publisher.SetStreaming(true) //time.Sleep(3 * time.Second) return "LIVE", nil } func (t *trackingService) AllTrackings() ([]TrackingMetadata, error) { logrus.Info("SERVICE: GET ALL TRACKINGS") data, err := t.store.LoadAll() return data, err } func (t *trackingService) StartRecord() (string, error) { logrus.Info("SERVICE: START RECORD") if t.opMode != LIVE { if t.opMode == RECORDING { txt := "trackingservice: already recording" logrus.Warn(txt) return "record already running since: " + t.tracking.TimeCreated.String(), errors.New(txt) } else { txt := "trackingservice: start collector pipeline to record data" logrus.Warn(txt) return "record already running since: " + t.tracking.TimeCreated.String(), errors.New(txt) } } t.opMode = RECORDING t.tracking.TimeCreated = time.Now() t.SetRecording(true) return "record started at: " + t.tracking.TimeCreated.String(), nil } func (t *trackingService) StopRecord() (*TrackingMetadata, error) { logrus.Info("SERVICE: STOP RECORD") if t.opMode != RECORDING { txt := "trackingservice: couldn't stop. not recording" logrus.Info(txt) return nil, errors.New(txt) } t.SetRecording(false) err := t.store.Save(*t.tracking) if err != nil { logrus.Error(err) } t.opMode = LIVE //time.Sleep(20 * time.Millisecond) tm := t.tracking.TrackingMetadata t.safelyReplaceTracking(newTracking()) t.tracking.Collectors = tm.Collectors return &tm, err } func (t *trackingService) StopAll() (*TrackingMetadata, error) { logrus.Info("SERVICE: STOP ALL") var tm *TrackingMetadata = nil var err error for _, e := range t.collectors { e.Stop() } // let buffer run empty after collectors stopped time.Sleep(time.Millisecond * 5) t.publisher.SetStreaming(false) if t.opMode == RECORDING { logrus.Info("trackingservice: gracefully stop recording ") tm, err = t.StopRecord() } t.opMode = STOPPED return tm, err } func (t *trackingService) LoadTracking(trackingId uuid.UUID) (*Tracking, error) { if !(t.opMode == REPLAY || t.opMode == STOPPED) { t.StopAll() } logrus.Info("LOAD TRACKING from database") tracking, err := t.store.Load(trackingId) if err != nil { return nil, err } t.safelyReplaceTracking(*tracking) NewReplayPipeline(t.publisher, t.tracking) t.publisher.SetStreaming(true) t.opMode = REPLAY return t.tracking, nil } func (t *trackingService) DeleteTracking(trackingId uuid.UUID) { panic("implement me") } func (t *trackingService) StartReplay() { panic("implement me") } func (t *trackingService) PauseReplay() { panic("implement me") } func (t *trackingService) StopReplay() { panic("implement me") } func (t *trackingService) safelyReplaceTracking(tr Tracking) { t.recSem.Acquire(context.Background(), 1) *t.tracking = tr t.recSem.Release(1) }