github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/stream/stream.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package stream
    20  
    21  import (
    22  	"context"
    23  	"sync"
    24  
    25  	"github.com/google/uuid"
    26  	"github.com/gorilla/websocket"
    27  	"go.uber.org/fx"
    28  
    29  	"github.com/e154/smart-home/common/events"
    30  	"github.com/e154/smart-home/common/logger"
    31  	m "github.com/e154/smart-home/models"
    32  	"github.com/e154/smart-home/system/bus"
    33  )
    34  
    35  var (
    36  	log = logger.MustGetLogger("stream")
    37  )
    38  
    39  var (
    40  	upgrader = websocket.Upgrader{}
    41  )
    42  
    43  // Stream ...
    44  type Stream struct {
    45  	*eventHandler
    46  	subMx       sync.Mutex
    47  	subscribers map[string]func(client IStreamClient, id string, msg []byte)
    48  	sessions    sync.Map
    49  	eventBus    bus.Bus
    50  }
    51  
    52  // NewStreamService ...
    53  func NewStreamService(lc fx.Lifecycle,
    54  	eventBus bus.Bus) (s *Stream) {
    55  	s = &Stream{
    56  		subscribers: make(map[string]func(client IStreamClient, id string, msg []byte)),
    57  		sessions:    sync.Map{},
    58  		eventBus:    eventBus,
    59  	}
    60  
    61  	s.eventHandler = NewEventHandler(s.Broadcast, s.DirectMessage)
    62  
    63  	lc.Append(fx.Hook{
    64  		OnStart: func(ctx context.Context) (err error) {
    65  			return s.Start(ctx)
    66  		},
    67  		OnStop: func(ctx context.Context) (err error) {
    68  			return s.Shutdown(ctx)
    69  		},
    70  	})
    71  
    72  	return
    73  }
    74  
    75  // Start ...
    76  func (s *Stream) Start(_ context.Context) error {
    77  	_ = s.eventBus.Subscribe("system/#", s.eventHandler.eventHandler)
    78  	s.eventBus.Publish("system/services/stream", events.EventServiceStarted{Service: "Stream"})
    79  	return nil
    80  }
    81  
    82  // Shutdown ...
    83  func (s *Stream) Shutdown(_ context.Context) error {
    84  	_ = s.eventBus.Unsubscribe("system/#", s.eventHandler.eventHandler)
    85  	_ = s.eventBus.Unsubscribe("system/dashboard", s.eventHandler.eventHandler)
    86  	s.sessions.Range(func(key, value interface{}) bool {
    87  		cli := value.(*Client)
    88  		cli.Close()
    89  		return true
    90  	})
    91  	s.eventBus.Publish("system/services/stream", events.EventServiceStopped{Service: "Stream"})
    92  	return nil
    93  }
    94  
    95  // Broadcast ...
    96  func (s *Stream) Broadcast(query string, message []byte) {
    97  	s.sessions.Range(func(key, value interface{}) bool {
    98  		cli := value.(*Client)
    99  		_ = cli.Send(uuid.NewString(), query, message)
   100  		return true
   101  	})
   102  }
   103  
   104  // DirectMessage ...
   105  func (s *Stream) DirectMessage(userID int64, sessionID string, query string, message []byte) {
   106  	s.sessions.Range(func(key, value interface{}) bool {
   107  		cli, ok := value.(*Client)
   108  		if !ok {
   109  			return false
   110  		}
   111  		if sessionID != "" && cli.SessionID() == sessionID {
   112  			_ = cli.Send(uuid.NewString(), query, message)
   113  			return true
   114  		}
   115  		if sessionID == "" && cli.user.Id == userID {
   116  			_ = cli.Send(uuid.NewString(), query, message)
   117  		}
   118  		return true
   119  	})
   120  }
   121  
   122  // Subscribe ...
   123  func (s *Stream) Subscribe(command string, f func(IStreamClient, string, []byte)) {
   124  	log.Infof("subscribe %s", command)
   125  	s.subMx.Lock()
   126  	defer s.subMx.Unlock()
   127  	if s.subscribers[command] != nil {
   128  		delete(s.subscribers, command)
   129  	}
   130  	s.subscribers[command] = f
   131  
   132  }
   133  
   134  // UnSubscribe ...
   135  func (s *Stream) UnSubscribe(command string) {
   136  	log.Infof("unsubscribe %s", command)
   137  	s.subMx.Lock()
   138  	defer s.subMx.Unlock()
   139  	if s.subscribers[command] != nil {
   140  		delete(s.subscribers, command)
   141  	}
   142  }
   143  
   144  // NewConnection ...
   145  func (s *Stream) NewConnection(ws *websocket.Conn, user *m.User) {
   146  
   147  	id := uuid.NewString()
   148  	client := NewClient(ws, user, id)
   149  	defer func() {
   150  		log.Infof("websocket session closed, email: '%s'", user.Email)
   151  		s.sessions.Delete(id)
   152  	}()
   153  
   154  	s.sessions.Store(id, client)
   155  	log.Infof("new websocket session established, email: '%s'", user.Email)
   156  
   157  	client.WritePump(s.Recv)
   158  }
   159  
   160  // Recv ...
   161  func (s *Stream) Recv(client *Client, id, query string, b []byte) {
   162  	//log.Debugf("id: %s, query: %s, body: %s", id, query, string(b))
   163  	s.subMx.Lock()
   164  	f, ok := s.subscribers[query]
   165  	s.subMx.Unlock()
   166  	if ok {
   167  		f(client, id, b)
   168  	}
   169  }