From 09508638ce1bb8941eaff64b14fbd35bb8e700e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 27 Mar 2023 18:33:51 +0200 Subject: [PATCH] session save & load from a file. (#38) --- dev/start | 1 + internal/config/session.go | 9 ++++ internal/session/manager.go | 9 ++++ internal/session/serialize.go | 97 +++++++++++++++++++++++++++++++++++ pkg/types/session.go | 6 +++ 5 files changed, 122 insertions(+) create mode 100644 internal/session/serialize.go diff --git a/dev/start b/dev/start index ad57f8a2..9aaf5c60 100755 --- a/dev/start +++ b/dev/start @@ -40,6 +40,7 @@ docker run --rm -it \ -e "NEKO_WEBRTC_UDPMUX=${NEKO_MUX}" \ -e "NEKO_WEBRTC_TCPMUX=${NEKO_MUX}" \ -e "NEKO_WEBRTC_NAT1TO1=${NEKO_NAT1TO1}" \ + -e "NEKO_SESSION_FILE=/home/neko/sessions.txt" \ -v "${PWD}/runtime/config.yml:/etc/neko/neko.yml" \ -e "NEKO_DEBUG=1" \ neko_server_app:latest; diff --git a/internal/config/session.go b/internal/config/session.go index 990ca129..04d9d8ce 100644 --- a/internal/config/session.go +++ b/internal/config/session.go @@ -8,6 +8,8 @@ import ( ) type Session struct { + File string + ImplicitHosting bool InactiveCursors bool MercifulReconnect bool @@ -20,6 +22,11 @@ type Session struct { } func (Session) Init(cmd *cobra.Command) error { + cmd.PersistentFlags().String("session.file", "", "if sessions should be stored in a file, otherwise they will be stored only in memory") + if err := viper.BindPFlag("session.file", cmd.PersistentFlags().Lookup("session.file")); err != nil { + return err + } + cmd.PersistentFlags().Bool("session.implicit_hosting", true, "allow implicit control switching") if err := viper.BindPFlag("session.implicit_hosting", cmd.PersistentFlags().Lookup("session.implicit_hosting")); err != nil { return err @@ -65,6 +72,8 @@ func (Session) Init(cmd *cobra.Command) error { } func (s *Session) Set() { + s.File = viper.GetString("session.file") + s.ImplicitHosting = viper.GetBool("session.implicit_hosting") s.InactiveCursors = viper.GetBool("session.inactive_cursors") s.MercifulReconnect = viper.GetBool("session.merciful_reconnect") diff --git a/internal/session/manager.go b/internal/session/manager.go index 6498d18d..2d1ff7ba 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -49,6 +49,9 @@ func New(config *config.Session) *SessionManagerCtx { } } + // try to load sessions from file + manager.load() + return manager } @@ -102,6 +105,8 @@ func (manager *SessionManagerCtx) Create(id string, profile types.MemberProfile) manager.sessionsMu.Unlock() manager.emmiter.Emit("created", session) + manager.save() + return session, token, nil } @@ -118,6 +123,8 @@ func (manager *SessionManagerCtx) Update(id string, profile types.MemberProfile) manager.sessionsMu.Unlock() manager.emmiter.Emit("profile_changed", session) + manager.save() + session.profileChanged() return nil } @@ -143,6 +150,8 @@ func (manager *SessionManagerCtx) Delete(id string) error { } manager.emmiter.Emit("deleted", session) + manager.save() + return nil } diff --git a/internal/session/serialize.go b/internal/session/serialize.go new file mode 100644 index 00000000..2d550b5c --- /dev/null +++ b/internal/session/serialize.go @@ -0,0 +1,97 @@ +package session + +import ( + "encoding/json" + "errors" + "os" + + "github.com/demodesk/neko/pkg/types" +) + +func (manager *SessionManagerCtx) save() { + if manager.config.File == "" { + return + } + + // serialize sessions + sessions := make([]types.SessionProfile, 0, len(manager.sessions)) + for _, session := range manager.sessions { + sessions = append(sessions, types.SessionProfile{ + Id: session.id, + Token: session.token, + Profile: session.profile, + }) + } + + // convert to json + data, err := json.Marshal(sessions) + if err != nil { + manager.logger.Error().Err(err).Msg("failed to marshal sessions") + return + } + + // write to file + err = os.WriteFile(manager.config.File, data, 0644) + if err != nil { + manager.logger.Error().Err(err). + Str("file", manager.config.File). + Msg("failed to write sessions to a file") + } +} + +func (manager *SessionManagerCtx) load() { + if manager.config.File == "" { + return + } + + // read file + data, err := os.ReadFile(manager.config.File) + if err != nil { + // if file does not exist + if errors.Is(err, os.ErrNotExist) { + manager.logger.Info(). + Str("file", manager.config.File). + Msg("sessions file does not exist") + return + } + manager.logger.Error().Err(err). + Str("file", manager.config.File). + Msg("failed to read sessions from a file") + return + } + + // if file is empty + if len(data) == 0 { + manager.logger.Info(). + Str("file", manager.config.File). + Msg("sessions file is empty") + return + } + + // deserialize sessions + sessions := make([]types.SessionProfile, 0) + err = json.Unmarshal(data, &sessions) + if err != nil { + manager.logger.Error().Err(err).Msg("failed to unmarshal sessions") + return + } + + // create sessions + manager.sessionsMu.Lock() + for _, session := range sessions { + manager.tokens[session.Token] = session.Id + manager.sessions[session.Id] = &SessionCtx{ + id: session.Id, + token: session.Token, + manager: manager, + logger: manager.logger.With().Str("session_id", session.Id).Logger(), + profile: session.Profile, + } + } + manager.sessionsMu.Unlock() + + manager.logger.Info(). + Int("sessions", len(sessions)). + Str("file", manager.config.File). + Msg("loaded sessions from a file") +} diff --git a/pkg/types/session.go b/pkg/types/session.go index 97b8183c..4527d687 100644 --- a/pkg/types/session.go +++ b/pkg/types/session.go @@ -17,6 +17,12 @@ type Cursor struct { Y int `json:"y"` } +type SessionProfile struct { + Id string + Token string + Profile MemberProfile +} + type SessionState struct { IsConnected bool `json:"is_connected"` IsWatching bool `json:"is_watching"`