diff --git a/config/config.go b/config/config.go index cf080ff..41cff41 100644 --- a/config/config.go +++ b/config/config.go @@ -17,53 +17,88 @@ package config import ( - "fmt" "io/ioutil" "os" "path/filepath" "gopkg.in/yaml.v2" "maunium.net/go/gomuks/debug" + "maunium.net/go/gomuks/matrix/rooms" + "maunium.net/go/gomuks/matrix/pushrules" + "encoding/json" + "strings" + "maunium.net/go/gomatrix" ) // Config contains the main config of gomuks. type Config struct { - UserID string `yaml:"mxid"` - HS string `yaml:"homeserver"` + UserID string `yaml:"mxid"` + AccessToken string `yaml:"access_token"` + HS string `yaml:"homeserver"` - Dir string `yaml:"-"` - HistoryDir string `yaml:"history_dir"` - MediaDir string `yaml:"media_dir"` - Session *Session `yaml:"-"` + Dir string `yaml:"-"` + CacheDir string `yaml:"cache_dir"` + HistoryDir string `yaml:"history_dir"` + MediaDir string `yaml:"media_dir"` + StateDir string `yaml:"state_dir"` + + AuthCache struct { + NextBatch string `yaml:"next_batch"` + FilterID string `yaml:"filter_id"` + InitialSyncDone bool `yaml:"initial_sync_done"` + } `yaml:"-"` + + Rooms map[string]*rooms.Room `yaml:"-"` + PushRules *pushrules.PushRuleset `yaml:"-"` + + nosave bool } // NewConfig creates a config that loads data from the given directory. func NewConfig(configDir, cacheDir string) *Config { return &Config{ Dir: configDir, + CacheDir: cacheDir, HistoryDir: filepath.Join(cacheDir, "history"), + StateDir: filepath.Join(cacheDir, "state"), MediaDir: filepath.Join(cacheDir, "media"), + + Rooms: make(map[string]*rooms.Room), } } // Clear clears the session cache and removes all history. func (config *Config) Clear() { - if config.Session != nil { - config.Session.Clear() - } os.RemoveAll(config.HistoryDir) + os.RemoveAll(config.StateDir) os.RemoveAll(config.MediaDir) + os.RemoveAll(config.CacheDir) + config.nosave = true +} + +func (config *Config) CreateCacheDirs() { + os.MkdirAll(config.CacheDir, 0700) + os.MkdirAll(config.HistoryDir, 0700) + os.MkdirAll(config.StateDir, 0700) + os.MkdirAll(config.MediaDir, 0700) } func (config *Config) DeleteSession() { - if config.Session != nil { - os.Remove(config.Session.path) - config.Session = nil - } - os.RemoveAll(config.HistoryDir) - os.RemoveAll(config.MediaDir) - os.MkdirAll(config.HistoryDir, 0700) - os.MkdirAll(config.MediaDir, 0700) + config.AuthCache.NextBatch = "" + config.AuthCache.InitialSyncDone = false + config.Rooms = make(map[string]*rooms.Room) + config.PushRules = nil + + config.Clear() + config.nosave = false + config.CreateCacheDirs() +} + +func (config *Config) LoadAll() { + config.Load() + config.LoadAuthCache() + config.LoadPushRules() + config.LoadRooms() } // Load loads the config from config.yaml in the directory given to the config struct. @@ -74,25 +109,34 @@ func (config *Config) Load() { data, err := ioutil.ReadFile(configPath) if err != nil { if os.IsNotExist(err) { - os.MkdirAll(config.HistoryDir, 0700) - os.MkdirAll(config.MediaDir, 0700) + config.CreateCacheDirs() return } - fmt.Println("Failed to read config from", configPath) + debug.Print("Failed to read config from", configPath) panic(err) } err = yaml.Unmarshal(data, &config) if err != nil { - fmt.Println("Failed to parse config at", configPath) + debug.Print("Failed to parse config at", configPath) panic(err) } - os.MkdirAll(config.HistoryDir, 0700) - os.MkdirAll(config.MediaDir, 0700) + config.CreateCacheDirs() +} + +func (config *Config) SaveAll() { + config.Save() + config.SaveAuthCache() + config.SavePushRules() + config.SaveRooms() } // Save saves this config to config.yaml in the directory given to the config struct. func (config *Config) Save() { + if config.nosave { + return + } + os.MkdirAll(config.Dir, 0700) data, err := yaml.Marshal(&config) if err != nil { @@ -107,3 +151,173 @@ func (config *Config) Save() { panic(err) } } + +func (config *Config) LoadAuthCache() { + os.MkdirAll(config.Dir, 0700) + + configPath := filepath.Join(config.CacheDir, "auth-cache.yaml") + data, err := ioutil.ReadFile(configPath) + if err != nil { + if os.IsNotExist(err) { + return + } + debug.Print("Failed to read auth cache from", configPath) + panic(err) + } + + err = yaml.Unmarshal(data, &config.AuthCache) + if err != nil { + debug.Print("Failed to parse auth cache at", configPath) + panic(err) + } +} + +func (config *Config) SaveAuthCache() { + if config.nosave { + return + } + + os.MkdirAll(config.CacheDir, 0700) + data, err := yaml.Marshal(&config.AuthCache) + if err != nil { + debug.Print("Failed to marshal auth cache") + panic(err) + } + + path := filepath.Join(config.CacheDir, "auth-cache.yaml") + err = ioutil.WriteFile(path, data, 0600) + if err != nil { + debug.Print("Failed to write auth cache to", path) + panic(err) + } +} + +func (config *Config) LoadPushRules() { + os.MkdirAll(config.CacheDir, 0700) + + pushRulesPath := filepath.Join(config.CacheDir, "pushrules.json") + data, err := ioutil.ReadFile(pushRulesPath) + if err != nil { + if os.IsNotExist(err) { + return + } + debug.Print("Failed to read push rules from", pushRulesPath) + return + } + + config.PushRules = &pushrules.PushRuleset{} + err = json.Unmarshal(data, &config.PushRules) + if err != nil { + debug.Print("Failed to parse push rules at", pushRulesPath) + return + } +} + +func (config *Config) SavePushRules() { + if config.nosave || config.PushRules == nil { + return + } + + os.MkdirAll(config.CacheDir, 0700) + data, err := json.Marshal(&config.PushRules) + if err != nil { + debug.Print("Failed to marshal push rules") + return + } + + path := filepath.Join(config.CacheDir, "pushrules.json") + err = ioutil.WriteFile(path, data, 0600) + if err != nil { + debug.Print("Failed to write config to", path) + return + } +} + +func (config *Config) LoadRooms() { + os.MkdirAll(config.StateDir, 0700) + + roomFiles, err := ioutil.ReadDir(config.StateDir) + if err != nil { + debug.Print("Failed to list rooms state caches in", config.StateDir) + panic(err) + } + + for _, roomFile := range roomFiles { + if roomFile.IsDir() || !strings.HasSuffix(roomFile.Name(), ".gmxstate") { + continue + } + path := filepath.Join(config.StateDir, roomFile.Name()) + room := &rooms.Room{} + err = room.Load(path) + if err != nil { + debug.Printf("Failed to load room state cache from %s: %v", path, err) + continue + } + config.Rooms[room.ID] = room + } +} + +func (config *Config) SaveRooms() { + if config.nosave { + return + } + + os.MkdirAll(config.StateDir, 0700) + for _, room := range config.Rooms { + path := config.getRoomCachePath(room) + err := room.Save(path) + if err != nil { + debug.Printf("Failed to save room state cache to file %s: %v", path, err) + } + } +} + +func (config *Config) GetUserID() string { + return config.UserID +} + +func (config *Config) SaveFilterID(_, filterID string) { + config.AuthCache.FilterID = filterID + config.SaveAuthCache() +} + +func (config *Config) LoadFilterID(_ string) string { + return config.AuthCache.FilterID +} + +func (config *Config) SaveNextBatch(_, nextBatch string) { + config.AuthCache.NextBatch = nextBatch + config.SaveAuthCache() +} + +func (config *Config) LoadNextBatch(_ string) string { + return config.AuthCache.NextBatch +} + +func (config *Config) GetRoom(roomID string) *rooms.Room { + room, _ := config.Rooms[roomID] + if room == nil { + room = rooms.NewRoom(roomID, config.UserID) + config.Rooms[room.ID] = room + } + return room +} + +func (config *Config) getRoomCachePath(room *rooms.Room) string { + return filepath.Join(config.StateDir, room.ID+".gmxstate") +} + +func (config *Config) PutRoom(room *rooms.Room) { + config.Rooms[room.ID] = room + room.Save(config.getRoomCachePath(room)) +} + +func (config *Config) SaveRoom(room *gomatrix.Room) { + gmxRoom := config.GetRoom(room.ID) + gmxRoom.Room = room + gmxRoom.Save(config.getRoomCachePath(gmxRoom)) +} + +func (config *Config) LoadRoom(roomID string) *gomatrix.Room { + return config.GetRoom(roomID).Room +} diff --git a/config/session.go b/config/session.go deleted file mode 100644 index d23c778..0000000 --- a/config/session.go +++ /dev/null @@ -1,137 +0,0 @@ -// gomuks - A terminal Matrix client written in Go. -// Copyright (C) 2018 Tulir Asokan -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package config - -import ( - "encoding/json" - "io/ioutil" - "path/filepath" - - "maunium.net/go/gomatrix" - "maunium.net/go/gomuks/debug" - "maunium.net/go/gomuks/matrix/pushrules" - "maunium.net/go/gomuks/matrix/rooms" -) - -type Session struct { - UserID string `json:"-"` - path string - AccessToken string - NextBatch string - FilterID string - Rooms map[string]*rooms.Room - PushRules *pushrules.PushRuleset - - InitialSyncDone bool -} - -func (config *Config) LoadSession(mxid string) error { - config.Session = config.NewSession(mxid) - return config.Session.Load() -} - -func (config *Config) NewSession(mxid string) *Session { - return &Session{ - UserID: mxid, - path: filepath.Join(config.Dir, mxid+".session"), - Rooms: make(map[string]*rooms.Room), - } -} - -func (s *Session) GetUserID() string { - return s.UserID -} - -func (s *Session) Clear() { - s.Rooms = make(map[string]*rooms.Room) - s.PushRules = nil - s.NextBatch = "" - s.FilterID = "" - s.InitialSyncDone = false - s.Save() -} - -func (s *Session) Load() error { - data, err := ioutil.ReadFile(s.path) - if err != nil { - debug.Printf("Failed to read session from %s: %v", s.path, err) - return err - } - - err = json.Unmarshal(data, s) - if err != nil { - debug.Printf("Failed to parse session at %s: %v", s.path, err) - return err - } - return nil -} - -func (s *Session) Save() error { - data, err := json.Marshal(s) - if err != nil { - debug.Printf("Failed to marshal session of %s: %v", s.UserID, err) - return err - } - - err = ioutil.WriteFile(s.path, data, 0600) - if err != nil { - debug.Printf("Failed to write session of %s to %s: %v", s.UserID, s.path, err) - return err - } - return nil -} - -func (s *Session) LoadFilterID(_ string) string { - return s.FilterID -} - -func (s *Session) LoadNextBatch(_ string) string { - return s.NextBatch -} - -func (s *Session) GetRoom(mxid string) *rooms.Room { - room, _ := s.Rooms[mxid] - if room == nil { - room = rooms.NewRoom(mxid, s.UserID) - s.Rooms[room.ID] = room - } - return room -} - -func (s *Session) PutRoom(room *rooms.Room) { - s.Rooms[room.ID] = room - s.Save() -} - -func (s *Session) SaveFilterID(_, filterID string) { - s.FilterID = filterID - s.Save() -} - -func (s *Session) SaveNextBatch(_, nextBatch string) { - s.NextBatch = nextBatch - s.Save() -} - -func (s *Session) LoadRoom(mxid string) *gomatrix.Room { - return s.GetRoom(mxid).Room -} - -func (s *Session) SaveRoom(room *gomatrix.Room) { - s.GetRoom(room.ID).Room = room - s.Save() -} diff --git a/config/session_test.go b/config/session_test.go deleted file mode 100644 index 5a7558b..0000000 --- a/config/session_test.go +++ /dev/null @@ -1,163 +0,0 @@ -// gomuks - A terminal Matrix client written in Go. -// Copyright (C) 2018 Tulir Asokan -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package config_test - -import ( - "testing" - "maunium.net/go/gomuks/config" - "github.com/stretchr/testify/assert" - "os" -) - -func TestConfig_NewSession(t *testing.T) { - defer os.RemoveAll("/tmp/gomuks-test-7") - - cfg := config.NewConfig("/tmp/gomuks-test-7", "/tmp/gomuks-test-7") - cfg.Load() - session := cfg.NewSession("@tulir:maunium.net") - assert.Equal(t, session.GetUserID(), "@tulir:maunium.net") - assert.Empty(t, session.Rooms) - - _, err1 := os.Stat("/tmp/gomuks-test-7/@tulir:maunium.net.session") - assert.True(t, os.IsNotExist(err1)) - assert.Nil(t, session.Save()) - _, err2 := os.Stat("/tmp/gomuks-test-7/@tulir:maunium.net.session") - assert.Nil(t, err2) -} - -func TestSession_Load(t *testing.T) { - defer os.RemoveAll("/tmp/gomuks-test-8") - - cfg := config.NewConfig("/tmp/gomuks-test-8", "/tmp/gomuks-test-8") - cfg.Load() - session := cfg.NewSession("@tulir:maunium.net") - session.SaveNextBatch("@tulir:maunium.net", "foobar") - session.SaveFilterID("@tulir:maunium.net", "1234") - - cfg = config.NewConfig("/tmp/gomuks-test-8", "/tmp/gomuks-test-8") - cfg.LoadSession("@tulir:maunium.net") - assert.NotNil(t, cfg.Session) - assert.Equal(t, "foobar", cfg.Session.LoadNextBatch("@tulir:maunium.net")) - assert.Equal(t, "1234", cfg.Session.LoadFilterID("@tulir:maunium.net")) -} - -func TestSession_Clear(t *testing.T) { - defer os.RemoveAll("/tmp/gomuks-test-9") - - cfg := config.NewConfig("/tmp/gomuks-test-9", "/tmp/gomuks-test-9") - cfg.Load() - session := cfg.NewSession("@tulir:maunium.net") - session.SaveNextBatch("@tulir:maunium.net", "foobar") - session.SaveFilterID("@tulir:maunium.net", "1234") - - cfg = config.NewConfig("/tmp/gomuks-test-9", "/tmp/gomuks-test-9") - cfg.LoadSession("@tulir:maunium.net") - assert.NotNil(t, cfg.Session) - assert.Equal(t, "foobar", cfg.Session.LoadNextBatch("@tulir:maunium.net")) - assert.Equal(t, "1234", cfg.Session.LoadFilterID("@tulir:maunium.net")) - - cfg.Session.Clear() - assert.Empty(t, cfg.Session.FilterID) - assert.Empty(t, cfg.Session.NextBatch) - assert.Empty(t, cfg.Session.Rooms) - - cfg = config.NewConfig("/tmp/gomuks-test-9", "/tmp/gomuks-test-9") - cfg.LoadSession("@tulir:maunium.net") - assert.Empty(t, cfg.Session.FilterID) - assert.Empty(t, cfg.Session.NextBatch) - assert.Empty(t, cfg.Session.Rooms) -} - -func TestConfig_ClearWithSession(t *testing.T) { - defer os.RemoveAll("/tmp/gomuks-test-9") - - cfg := config.NewConfig("/tmp/gomuks-test-9", "/tmp/gomuks-test-9") - cfg.Load() - session := cfg.NewSession("@tulir:maunium.net") - session.SaveNextBatch("@tulir:maunium.net", "foobar") - session.SaveFilterID("@tulir:maunium.net", "1234") - - cfg = config.NewConfig("/tmp/gomuks-test-9", "/tmp/gomuks-test-9") - cfg.LoadSession("@tulir:maunium.net") - assert.NotNil(t, cfg.Session) - assert.Equal(t, "foobar", cfg.Session.LoadNextBatch("@tulir:maunium.net")) - assert.Equal(t, "1234", cfg.Session.LoadFilterID("@tulir:maunium.net")) - - cfg.Clear() - assert.Empty(t, cfg.Session.FilterID) - assert.Empty(t, cfg.Session.NextBatch) - assert.Empty(t, cfg.Session.Rooms) - - cfg = config.NewConfig("/tmp/gomuks-test-9", "/tmp/gomuks-test-9") - cfg.LoadSession("@tulir:maunium.net") - assert.Empty(t, cfg.Session.FilterID) - assert.Empty(t, cfg.Session.NextBatch) - assert.Empty(t, cfg.Session.Rooms) -} - -func TestSession_GetRoom(t *testing.T) { - defer os.RemoveAll("/tmp/gomuks-test-10") - - cfg := config.NewConfig("/tmp/gomuks-test-10", "/tmp/gomuks-test-10") - cfg.Session = cfg.NewSession("@tulir:maunium.net") - room := cfg.Session.GetRoom("!foo:maunium.net") - assert.NotNil(t, room) - assert.Equal(t, room.Room, cfg.Session.LoadRoom("!foo:maunium.net")) -} - -func TestSession_PutRoom(t *testing.T) { - defer os.RemoveAll("/tmp/gomuks-test-11") - - cfg := config.NewConfig("/tmp/gomuks-test-11", "/tmp/gomuks-test-11") - cfg.Load() - cfg.LoadSession("@tulir:maunium.net") - room := cfg.Session.GetRoom("!foo:maunium.net") - room.PrevBatch = "foobar" - room.HasLeft = true - cfg.Session.PutRoom(room) - - cfg = config.NewConfig("/tmp/gomuks-test-11", "/tmp/gomuks-test-11") - cfg.LoadSession("@tulir:maunium.net") - reloadedRoom := cfg.Session.GetRoom("!foo:maunium.net") - assert.Equal(t, "foobar", reloadedRoom.PrevBatch, "%v %v", room, reloadedRoom) - assert.True(t, reloadedRoom.HasLeft, "%v %v", room, reloadedRoom) -} - -func TestConfig_DeleteSession(t *testing.T) { - defer os.RemoveAll("/tmp/gomuks-test-12") - - cfg := config.NewConfig("/tmp/gomuks-test-12", "/tmp/gomuks-test-12") - cfg.Load() - cfg.LoadSession("@tulir:maunium.net") - cfg.Session.SaveNextBatch("@tulir:maunium.net", "foobar") - cfg.Session.SaveFilterID("@tulir:maunium.net", "1234") - - cfg.DeleteSession() - - assert.Nil(t, cfg.Session) - - sessFile, err := os.Stat("/tmp/gomuks-test-12/@tulir:maunium.net.session") - assert.Nil(t, sessFile) - assert.True(t, os.IsNotExist(err)) - - mediaDir, err := os.Stat("/tmp/gomuks-test-12/media") - assert.True(t, mediaDir.IsDir()) - assert.Nil(t, err) - historyDir, err := os.Stat("/tmp/gomuks-test-12/history") - assert.True(t, historyDir.IsDir()) - assert.Nil(t, err) -} diff --git a/gomuks.go b/gomuks.go index ae007e0..b668324 100644 --- a/gomuks.go +++ b/gomuks.go @@ -50,24 +50,17 @@ func NewGomuks(uiProvider ifc.UIProvider) *Gomuks { gmx.ui = uiProvider(gmx) gmx.matrix = matrix.NewContainer(gmx) - gmx.config.Load() + gmx.config.LoadAll() gmx.ui.Init() debug.OnRecover = gmx.ui.Finish - if len(gmx.config.UserID) > 0 { - _ = gmx.config.LoadSession(gmx.config.UserID) - } - return gmx } // Save saves the active session and message history. func (gmx *Gomuks) Save() { - if gmx.config.Session != nil { - debug.Print("Saving session...") - _ = gmx.config.Session.Save() - } + gmx.config.SaveAll() debug.Print("Saving history...") gmx.ui.MainView().SaveAllHistory() } diff --git a/matrix/matrix.go b/matrix/matrix.go index 92fcabe..c41975e 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -83,8 +83,8 @@ func (c *Container) InitClient() error { } var mxid, accessToken string - if c.config.Session != nil { - accessToken = c.config.Session.AccessToken + if len(c.config.AccessToken) > 0 { + accessToken = c.config.AccessToken mxid = c.config.UserID } @@ -96,7 +96,7 @@ func (c *Container) InitClient() error { c.stop = make(chan bool, 1) - if c.config.Session != nil && len(accessToken) > 0 { + if len(accessToken) > 0 { go c.Start() } return nil @@ -120,12 +120,9 @@ func (c *Container) Login(user, password string) error { } c.client.SetCredentials(resp.UserID, resp.AccessToken) c.config.UserID = resp.UserID + c.config.AccessToken = resp.AccessToken c.config.Save() - c.config.Session = c.config.NewSession(resp.UserID) - c.config.Session.AccessToken = resp.AccessToken - c.config.Session.Save() - go c.Start() return nil @@ -156,25 +153,26 @@ func (c *Container) UpdatePushRules() { if err != nil { debug.Print("Failed to fetch push rules:", err) } - c.config.Session.PushRules = resp + c.config.PushRules = resp + c.config.SavePushRules() } // PushRules returns the push notification rules. If no push rules are cached, UpdatePushRules() will be called first. func (c *Container) PushRules() *pushrules.PushRuleset { - if c.config.Session.PushRules == nil { + if c.config.PushRules == nil { c.UpdatePushRules() } - return c.config.Session.PushRules + return c.config.PushRules } // OnLogin initializes the syncer and updates the room list. func (c *Container) OnLogin() { c.ui.OnLogin() - c.client.Store = c.config.Session + c.client.Store = c.config debug.Print("Initializing syncer") - c.syncer = NewGomuksSyncer(c.config.Session) + c.syncer = NewGomuksSyncer(c.config) c.syncer.OnEventType("m.room.message", c.HandleMessage) c.syncer.OnEventType("m.room.member", c.HandleMembership) c.syncer.OnEventType("m.receipt", c.HandleReadReceipt) @@ -183,14 +181,15 @@ func (c *Container) OnLogin() { c.syncer.OnEventType("m.push_rules", c.HandlePushRules) c.syncer.OnEventType("m.tag", c.HandleTag) c.syncer.InitDoneCallback = func() { - c.config.Session.InitialSyncDone = true + c.config.AuthCache.InitialSyncDone = true + c.config.SaveAuthCache() c.ui.MainView().InitialSyncDone() c.ui.Render() } c.client.Syncer = c.syncer debug.Print("Setting existing rooms") - c.ui.MainView().SetRooms(c.config.Session.Rooms) + c.ui.MainView().SetRooms(c.config.Rooms) debug.Print("OnLogin() done.") } @@ -268,7 +267,7 @@ func (c *Container) parseReadReceipt(evt *gomatrix.Event) (largestTimestampEvent continue } - myInfo, ok := mRead[c.config.Session.UserID].(map[string]interface{}) + myInfo, ok := mRead[c.config.UserID].(map[string]interface{}) if !ok { continue } @@ -322,7 +321,7 @@ func (c *Container) parseDirectChatInfo(evt *gomatrix.Event) (map[*rooms.Room]bo func (c *Container) HandleDirectChatInfo(source EventSource, evt *gomatrix.Event) { directChats := c.parseDirectChatInfo(evt) - for _, room := range c.config.Session.Rooms { + for _, room := range c.config.Rooms { shouldBeDirect := directChats[room] if shouldBeDirect != room.IsDirect { room.IsDirect = shouldBeDirect @@ -335,15 +334,17 @@ func (c *Container) HandleDirectChatInfo(source EventSource, evt *gomatrix.Event func (c *Container) HandlePushRules(source EventSource, evt *gomatrix.Event) { debug.Print("Received updated push rules") var err error - c.config.Session.PushRules, err = pushrules.EventToPushRules(evt) + c.config.PushRules, err = pushrules.EventToPushRules(evt) if err != nil { debug.Print("Failed to convert event to push rules:", err) + return } + c.config.SavePushRules() } // HandleTag is the event handler for the m.tag account data event. func (c *Container) HandleTag(source EventSource, evt *gomatrix.Event) { - room := c.config.Session.GetRoom(evt.RoomID) + room := c.config.GetRoom(evt.RoomID) tags, _ := evt.Content["tags"].(map[string]interface{}) newTags := make([]rooms.RoomTag, len(tags)) @@ -396,11 +397,11 @@ func (c *Container) HandleMembership(source EventSource, evt *gomatrix.Event) { isLeave := source&EventSourceLeave != 0 isTimeline := source&EventSourceTimeline != 0 isNonTimelineLeave := isLeave && !isTimeline - if !c.config.Session.InitialSyncDone && isNonTimelineLeave { + if !c.config.AuthCache.InitialSyncDone && isNonTimelineLeave { return - } else if evt.StateKey != nil && *evt.StateKey == c.config.Session.UserID { + } else if evt.StateKey != nil && *evt.StateKey == c.config.UserID { c.processOwnMembershipChange(evt) - } else if !isTimeline && (!c.config.Session.InitialSyncDone || isLeave) { + } else if !isTimeline && (!c.config.AuthCache.InitialSyncDone || isLeave) { // We don't care about other users' membership events in the initial sync or chats we've left. return } @@ -563,7 +564,7 @@ func (c *Container) GetHistory(roomID, prevBatch string, limit int) ([]gomatrix. // GetRoom gets the room instance stored in the session. func (c *Container) GetRoom(roomID string) *rooms.Room { - return c.config.Session.GetRoom(roomID) + return c.config.GetRoom(roomID) } var mxcRegex = regexp.MustCompile("mxc://(.+)/(.+)") diff --git a/matrix/pushrules/rule.go b/matrix/pushrules/rule.go index ec51d24..ddb264e 100644 --- a/matrix/pushrules/rule.go +++ b/matrix/pushrules/rule.go @@ -19,8 +19,14 @@ package pushrules import ( "maunium.net/go/gomuks/lib/glob" "maunium.net/go/gomatrix" + "encoding/gob" ) +func init() { + gob.Register(PushRuleArray{}) + gob.Register(PushRuleMap{}) +} + type PushRuleCollection interface { GetActions(room Room, event *gomatrix.Event) PushActionArray } diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go index 17bf21b..8a543e8 100644 --- a/matrix/rooms/room.go +++ b/matrix/rooms/room.go @@ -24,12 +24,19 @@ import ( "maunium.net/go/gomatrix" "maunium.net/go/gomuks/debug" + "os" + "encoding/gob" ) +func init() { + gob.Register([]interface{}{}) + gob.Register(map[string]interface{}{}) +} + type RoomNameSource int const ( - ExplicitRoomName RoomNameSource = iota + ExplicitRoomName RoomNameSource = iota CanonicalAliasRoomName AliasRoomName MemberRoomName @@ -44,8 +51,8 @@ type RoomTag struct { } type UnreadMessage struct { - EventID string - Counted bool + EventID string + Counted bool Highlight bool } @@ -63,9 +70,9 @@ type Room struct { SessionUserID string // The number of unread messages that were notified about. - UnreadMessages []UnreadMessage + UnreadMessages []UnreadMessage unreadCountCache *int - highlightCache *bool + highlightCache *bool // Whether or not this room is marked as a direct chat. IsDirect bool @@ -113,6 +120,26 @@ func (room *Room) UnlockHistory() { } } +func (room *Room) Load(path string) error { + file, err := os.OpenFile(path, os.O_RDONLY, 0600) + if err != nil { + return err + } + defer file.Close() + dec := gob.NewDecoder(file) + return dec.Decode(room) +} + +func (room *Room) Save(path string) error { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + defer file.Close() + enc := gob.NewEncoder(file) + return enc.Encode(room) +} + // MarkRead clears the new message statuses on this room. func (room *Room) MarkRead(eventID string) { readToIndex := -1 @@ -159,8 +186,8 @@ func (room *Room) HasNewMessages() bool { func (room *Room) AddUnread(eventID string, counted, highlight bool) { room.UnreadMessages = append(room.UnreadMessages, UnreadMessage{ - EventID: eventID, - Counted: counted, + EventID: eventID, + Counted: counted, Highlight: highlight, }) if counted { diff --git a/ui/view-main.go b/ui/view-main.go index c8977d3..314221a 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -430,7 +430,7 @@ func sendNotification(room *rooms.Room, sender, text string, critical, sound boo func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, should pushrules.PushActionArrayShould) { view.roomList.Bump(room) - if message.SenderID() == view.config.Session.UserID { + if message.SenderID() == view.config.UserID { return } // Whether or not the room where the message came is the currently shown room. @@ -505,7 +505,7 @@ func (view *MainView) LoadHistory(room string) { if err != nil { debug.Printf("Failed to save history of %s: %v", roomView.Room.GetTitle(), err) } - view.config.Session.Save() + view.config.PutRoom(roomView.Room) view.parent.Render() }