morpheus

A Matrix client written in Go-QT
git clone git://archive.git.mtrnord.blog/Nordgedanken/morpheus.git
Log | Files | Refs | README | LICENSE

commit e6c580d73a64b13098061b4c7987c263b5b8a77b
parent 528f91784d10cd7116db9199ebb68d679d6ec938
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sun, 26 Nov 2017 16:09:51 +0100

Use new logger, use own storage for sync, use own syncer with custom filter, try QThreads

Signed-off-by: MTRNord <mtrnord1@gmail.com>

Diffstat:
Mmain.go | 72+++++++++++++++++++++++++++++++++++++++---------------------------------
Mmatrix/db/db.go | 6------
Amatrix/db/storer.go | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmatrix/login.go | 20++++++++++----------
Mmatrix/rooms.go | 11++++++-----
Amatrix/syncer/store.go | 39+++++++++++++++++++++++++++++++++++++++
Amatrix/syncer/syncer.go | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmatrix/user.go | 51++++++++++++++++++++-------------------------------
Mui/LoginUI.go | 24++++--------------------
Mui/MainUI.go | 80++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mui/messageList.go | 13+++++++------
Mui/types.go | 44+++++++++++++++++++++++++++-----------------
Dutil/logger.go | 47-----------------------------------------------
13 files changed, 397 insertions(+), 214 deletions(-)

diff --git a/main.go b/main.go @@ -2,9 +2,9 @@ package main import ( "fmt" - "log" "os" "os/signal" + "path/filepath" "runtime" "sync" "syscall" @@ -12,20 +12,44 @@ import ( "github.com/Nordgedanken/Morpheus/matrix" "github.com/Nordgedanken/Morpheus/matrix/db" "github.com/Nordgedanken/Morpheus/ui" - "github.com/Nordgedanken/Morpheus/util" "github.com/dgraph-io/badger" + "github.com/matrix-org/dugong" "github.com/matrix-org/gomatrix" + "github.com/shibukawa/configdir" + log "github.com/sirupsen/logrus" "github.com/therecipe/qt/core" "github.com/therecipe/qt/gui" "github.com/therecipe/qt/widgets" ) var window *widgets.QMainWindow -var localLog *log.Logger func main() { runtime.GOMAXPROCS(128) + // Init Logs + configDirs := configdir.New("Nordgedanken", "Morpheus") + + log.SetFormatter(&log.TextFormatter{ + TimestampFormat: "2006-01-02 15:04:05.000000", + DisableColors: false, + DisableTimestamp: true, + DisableSorting: true, + QuoteEmptyFields: true, + }) + + log.AddHook(dugong.NewFSHook( + filepath.Join(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path)+"/log/", "info.log"), + filepath.Join(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path)+"/log/", "warn.log"), + filepath.Join(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path)+"/log/", "error.log"), + &log.TextFormatter{ + TimestampFormat: "2006-01-02 15:04:05.000000", + DisableColors: true, + DisableTimestamp: false, + DisableSorting: false, + }, &dugong.DailyRotationSchedule{GZip: false}, + )) + c := make(chan os.Signal, 2) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { @@ -34,27 +58,17 @@ func main() { os.Exit(1) }() - var file *os.File - var err error - - localLog = util.Logger() - localLog, file, err = util.StartFileLog(localLog) - if err != nil { - localLog.Fatalln(err) - } - defer file.Close() - UserDB, DBOpenErr := db.OpenUserDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Fatalln(DBOpenErr) } CacheDB, DBOpenErr := db.OpenCacheDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Fatalln(DBOpenErr) } - localLog.Println("Starting Morpheus") + log.Infoln("Starting Morpheus") app := widgets.NewQApplication(len(os.Args), os.Args) @@ -136,16 +150,16 @@ func main() { return nil }) if DBErr != nil { - localLog.Fatalln("Login: ", DBErr) + log.Errorln("Login: ", DBErr) } if accessToken != "" && homeserverURL != "" && userID != "" { var wg sync.WaitGroup - localLog.Println("Starting Auto Login Sequenze in background") + log.Infoln("Starting Auto Login Sequenze in background") results := make(chan *gomatrix.Client) wg.Add(1) - go matrix.DoLogin("", "", homeserverURL, userID, accessToken, localLog, results, &wg) + go matrix.DoLogin("", "", homeserverURL, userID, accessToken, results, &wg) go func() { wg.Wait() // wait for each execTask to return @@ -157,13 +171,9 @@ func main() { //TODO Don't switch screen on wrong login data. MainUIStruct := ui.NewMainUIStruct(windowWidth, windowHeight, window) MainUIStruct.SetCli(result) - MainUILoggerInitErr := MainUIStruct.InitLogger() - if MainUILoggerInitErr != nil { - localLog.Fatalln(MainUILoggerInitErr) - } mainUIErr := MainUIStruct.NewUI() if mainUIErr != nil { - localLog.Fatalln("mainUI: ", mainUIErr) + log.Errorln("mainUI: ", mainUIErr) return } MainUIStruct.GetWidget().Resize2(windowWidth, windowHeight) @@ -172,13 +182,9 @@ func main() { } else { //Show loginUI LoginUIStruct := ui.NewLoginUIStruct(windowWidth, windowHeight, window) - LoginUIStructInitErr := LoginUIStruct.InitLogger() - if LoginUIStructInitErr != nil { - localLog.Fatalln(LoginUIStructInitErr) - } loginUIErr := LoginUIStruct.NewUI() if loginUIErr != nil { - localLog.Fatalln("Login Err: ", loginUIErr) + log.Errorln("Login Err: ", loginUIErr) return } LoginUIStruct.GetWidget().Resize2(windowWidth, windowHeight) @@ -192,19 +198,19 @@ func main() { _ = widgets.QApplication_Exec() defer UserDB.Close() defer CacheDB.Close() - localLog.Println("Stopping Morpheus") + log.Infoln("Stopping Morpheus") } func cleanup() { - fmt.Println("cleanup") + log.Infoln("cleanup") UserDB, DBOpenErr := db.OpenUserDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Errorln(DBOpenErr) } CacheDB, DBOpenErr := db.OpenCacheDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Errorln(DBOpenErr) } UserDB.Close() diff --git a/matrix/db/db.go b/matrix/db/db.go @@ -2,7 +2,6 @@ package db import ( "errors" - "log" "os" "path/filepath" "strconv" @@ -50,7 +49,6 @@ func OpenCacheDB() (db *badger.DB, err error) { }) if CacheDB == nil { - log.Println(CacheDB) err = errors.New("missing CacheDB") return } @@ -93,7 +91,6 @@ func OpenUserDB() (db *badger.DB, err error) { }) if UserDB == nil { - log.Println(UserDB) err = errors.New("missing UserDB") return } @@ -104,7 +101,6 @@ func OpenUserDB() (db *badger.DB, err error) { // CacheMessageEvents writes message infos into the cache into the defined room func CacheMessageEvents(id, sender, roomID, message string, timestamp int64) (err error) { - log.Println("start Caching") db, DBOpenErr := OpenCacheDB() if DBOpenErr != nil { err = DBOpenErr @@ -113,7 +109,6 @@ func CacheMessageEvents(id, sender, roomID, message string, timestamp int64) (er // Update cache DBerr := db.Update(func(txn *badger.Txn) error { - log.Println(id) DBSetIDErr := txn.Set([]byte("room|"+roomID+"|messages|"+id+"|id"), []byte(id)) if DBSetIDErr != nil { return DBSetIDErr @@ -135,7 +130,6 @@ func CacheMessageEvents(id, sender, roomID, message string, timestamp int64) (er }) if DBerr != nil { - log.Println("DBERR: ", DBerr) err = DBerr return } diff --git a/matrix/db/storer.go b/matrix/db/storer.go @@ -0,0 +1,53 @@ +package db + +import ( + "github.com/dgraph-io/badger" +) + +// Storer is the interface which needs to be conformed to in order to persist Go-NEB data +type Storer interface { + UpdateNextBatch(userID, nextBatch string) (err error) + LoadNextBatch(userID string) (nextBatch string, err error) +} + +type MorpheusStorage struct { + Database *badger.DB +} + +// UpdateNextBatch updates the next_batch token for the given user. +func (m *MorpheusStorage) UpdateNextBatch(userID, nextBatch string) (err error) { + DBerr := m.Database.Update(func(txn *badger.Txn) error { + + DBSetNextBatchErr := txn.Set([]byte("matrix|"+userID+"|nextBatch|"), []byte(nextBatch)) + return DBSetNextBatchErr + }) + if DBerr != nil { + err = DBerr + return + } + return +} + +// LoadNextBatch loads the next_batch token for the given user. +func (m *MorpheusStorage) LoadNextBatch(userID string) (nextBatch string, err error) { + DBerr := m.Database.View(func(txn *badger.Txn) error { + + nextBatchItem, NextBatchErr := txn.Get([]byte("matrix|" + userID + "|nextBatch|")) + if NextBatchErr != nil { + return NextBatchErr + } + + nextBatchByte, nextBatchByteErr := nextBatchItem.Value() + if nextBatchByteErr != nil { + return nextBatchByteErr + } + + nextBatch = string(nextBatchByte) + return nil + }) + if DBerr != nil { + err = DBerr + return + } + return +} diff --git a/matrix/login.go b/matrix/login.go @@ -1,19 +1,19 @@ package matrix import ( - "log" "strings" "sync" "github.com/Nordgedanken/Morpheus/matrix/db" "github.com/matrix-org/gomatrix" + log "github.com/sirupsen/logrus" ) //GetClient returns a Client func GetClient(homeserverURL, userID, accessToken string) (client *gomatrix.Client, err error) { - db, DBOpenErr := db.OpenUserDB() + userDB, DBOpenErr := db.OpenUserDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Errorln(DBOpenErr) } client, ClientErr := gomatrix.NewClient(homeserverURL, userID, accessToken) @@ -22,7 +22,7 @@ func GetClient(homeserverURL, userID, accessToken string) (client *gomatrix.Clie return } - txn := db.NewTransaction(true) // Read-write txn + txn := userDB.NewTransaction(true) // Read-write txn DBSetAccessTokenErr := txn.Set([]byte("user|accessToken"), []byte(client.AccessToken)) if DBSetAccessTokenErr != nil { err = DBSetAccessTokenErr @@ -74,13 +74,13 @@ func LoginUser(username, password string) (*gomatrix.Client, error) { return nil, err } - db, DBOpenErr := db.OpenUserDB() + userDB, DBOpenErr := db.OpenUserDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Errorln(DBOpenErr) } cli.SetCredentials(resp.UserID, resp.AccessToken) - txn := db.NewTransaction(true) // Read-write txn + txn := userDB.NewTransaction(true) // Read-write txn DBSetAccessTokenErr := txn.Set([]byte("user|accessToken"), []byte(resp.AccessToken)) if DBSetAccessTokenErr != nil { return nil, DBSetAccessTokenErr @@ -110,7 +110,7 @@ func LoginUser(username, password string) (*gomatrix.Client, error) { } // DoLogin generates the needed Client -func DoLogin(username, password, homeserverURL, userID, accessToken string, localLog *log.Logger, results chan<- *gomatrix.Client, wg *sync.WaitGroup) { +func DoLogin(username, password, homeserverURL, userID, accessToken string, results chan<- *gomatrix.Client, wg *sync.WaitGroup) { defer wg.Done() var cli *gomatrix.Client if accessToken != "" && homeserverURL != "" && userID != "" { @@ -123,14 +123,14 @@ func DoLogin(username, password, homeserverURL, userID, accessToken string, loca cli, cliErr = GetClient("https://"+homeserverURL, userID, accessToken) } if cliErr != nil { - localLog.Println(cliErr) + log.Errorln(cliErr) } cli.SetCredentials(userID, accessToken) } else { var err error cli, err = LoginUser(username, password) if err != nil { - localLog.Println(err) + log.Errorln(err) } } diff --git a/matrix/rooms.go b/matrix/rooms.go @@ -10,6 +10,7 @@ import ( "github.com/Nordgedanken/Morpheus/matrix/db" "github.com/dgraph-io/badger" "github.com/matrix-org/gomatrix" + log "github.com/sirupsen/logrus" "github.com/therecipe/qt/gui" ) @@ -63,7 +64,7 @@ func (r *Room) GetRoomAvatar() (avatarResp *gui.QPixmap, err error) { // Get the image Data cacheDB, DBOpenErr := db.OpenCacheDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Fatalln(DBOpenErr) } // Init local vars @@ -104,14 +105,14 @@ func (r *Room) GetRoomAvatar() (avatarResp *gui.QPixmap, err error) { } IMGdata = data } else { - localLog.Println("Generating Room Avatar") + log.Println("Generating Room Avatar") var GenerateImgErr error var roomName string if r.RoomName == "" { r.crawlRoomName() } roomName = r.RoomName - localLog.Println(roomName) + log.Println(roomName) if roomName == "" { roomName = "#" } @@ -170,12 +171,12 @@ func (r *Room) crawlRoomName() { }{} if roomNameStateEventErr := r.cli.StateEvent(r.RoomID, "m.room.name", "", &roomName); roomNameStateEventErr != nil { - localLog.Println(roomNameStateEventErr) + log.Println(roomNameStateEventErr) // Not returning as a Error NotFound is allowed } if roomName.Name == "" { if roomCanoncialAliasStateEventErr := r.cli.StateEvent(r.RoomID, "m.room.canonical_alias", "", &roomCanoncialAlias); roomCanoncialAliasStateEventErr != nil { - localLog.Println(roomCanoncialAliasStateEventErr) + log.Println(roomCanoncialAliasStateEventErr) // Not returning as a Error NotFound is allowed } if roomCanoncialAlias.Alias == "" { diff --git a/matrix/syncer/store.go b/matrix/syncer/store.go @@ -0,0 +1,39 @@ +package syncer + +import ( + "github.com/Nordgedanken/Morpheus/matrix/db" + "github.com/matrix-org/gomatrix" + log "github.com/sirupsen/logrus" +) + +// MorpheusStore implements the gomatrix.Storer interface. +// +// It persists the next batch token in the database, and includes a ClientConfig for the client. +type MorpheusStore struct { + gomatrix.InMemoryStore + CacheDatabase db.Storer +} + +// SaveNextBatch saves to the database. +func (m *MorpheusStore) SaveNextBatch(userID, nextBatch string) { + if err := m.CacheDatabase.UpdateNextBatch(userID, nextBatch); err != nil { + log.WithFields(log.Fields{ + log.ErrorKey: err, + "user_id": userID, + "next_batch": nextBatch, + }).Error("Failed to persist next_batch token") + } +} + +// LoadNextBatch loads from the database. +func (m *MorpheusStore) LoadNextBatch(userID string) string { + token, err := m.CacheDatabase.LoadNextBatch(userID) + if err != nil { + log.WithFields(log.Fields{ + log.ErrorKey: err, + "user_id": userID, + }).Error("Failed to load next_batch token") + return "" + } + return token +} diff --git a/matrix/syncer/syncer.go b/matrix/syncer/syncer.go @@ -0,0 +1,151 @@ +package syncer + +import ( + "encoding/json" + "fmt" + "runtime/debug" + "time" + + "github.com/matrix-org/gomatrix" +) + +type MorpheusSyncer struct { + UserID string + Store gomatrix.Storer + listeners map[string][]OnEventListener // event type to listeners array +} + +// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events. +type OnEventListener func(*gomatrix.Event) + +// NewMorpheusSyncer returns an instantiated MorpheusSyncer +func NewMorpheusSyncer(userID string, store gomatrix.Storer) *MorpheusSyncer { + return &MorpheusSyncer{ + UserID: userID, + Store: store, + listeners: make(map[string][]OnEventListener), + } +} + +// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of +// unrepeating events. Returns a fatal error if a listener panics. +func (s *MorpheusSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (err error) { + if !s.shouldProcessResponse(res, since) { + return + } + + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack()) + } + }() + + for roomID, roomData := range res.Rooms.Join { + room := s.getOrCreateRoom(roomID) + for _, event := range roomData.State.Events { + event.RoomID = roomID + room.UpdateState(&event) + s.notifyListeners(&event) + } + for _, event := range roomData.Timeline.Events { + event.RoomID = roomID + s.notifyListeners(&event) + } + } + for roomID, roomData := range res.Rooms.Invite { + room := s.getOrCreateRoom(roomID) + for _, event := range roomData.State.Events { + event.RoomID = roomID + room.UpdateState(&event) + s.notifyListeners(&event) + } + } + for roomID, roomData := range res.Rooms.Leave { + room := s.getOrCreateRoom(roomID) + for _, event := range roomData.Timeline.Events { + if event.StateKey != nil { + event.RoomID = roomID + room.UpdateState(&event) + s.notifyListeners(&event) + } + } + } + return +} + +// OnEventType allows callers to be notified when there are new events for the given event type. +// There are no duplicate checks. +func (s *MorpheusSyncer) OnEventType(eventType string, callback OnEventListener) { + _, exists := s.listeners[eventType] + if !exists { + s.listeners[eventType] = []OnEventListener{} + } + s.listeners[eventType] = append(s.listeners[eventType], callback) +} + +// shouldProcessResponse returns true if the response should be processed. May modify the response to remove +// stuff that shouldn't be processed. +func (s *MorpheusSyncer) shouldProcessResponse(resp *gomatrix.RespSync, since string) bool { + if since == "" { + return false + } + // This is a horrible hack because /sync will return the most recent messages for a room + // as soon as you /join it. We do NOT want to process those events in that particular room + // because they may have already been processed (if you toggle the bot in/out of the room). + // + // Work around this by inspecting each room's timeline and seeing if an m.room.member event for us + // exists and is "join" and then discard processing that room entirely if so. + // TODO: We probably want to process messages from after the last join event in the timeline. + for roomID, roomData := range resp.Rooms.Join { + for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- { + e := roomData.Timeline.Events[i] + if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID { + m := e.Content["membership"] + mship, ok := m.(string) + if !ok { + continue + } + if mship == "join" { + _, ok := resp.Rooms.Join[roomID] + if !ok { + continue + } + delete(resp.Rooms.Join, roomID) // don't re-process messages + delete(resp.Rooms.Invite, roomID) // don't re-process invites + break + } + } + } + } + return true +} + +// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse() +func (s *MorpheusSyncer) getOrCreateRoom(roomID string) *gomatrix.Room { + room := s.Store.LoadRoom(roomID) + if room == nil { // create a new Room + room = gomatrix.NewRoom(roomID) + s.Store.SaveRoom(room) + } + return room +} + +func (s *MorpheusSyncer) notifyListeners(event *gomatrix.Event) { + listeners, exists := s.listeners[event.Type] + if !exists { + return + } + for _, fn := range listeners { + fn(event) + } +} + +// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error. +func (s *MorpheusSyncer) OnFailedSync(res *gomatrix.RespSync, err error) (time.Duration, error) { + return 10 * time.Second, nil +} + +// GetFilterJSON returns a filter with a timeline limit of 50. +func (s *MorpheusSyncer) GetFilterJSON(userID string) json.RawMessage { + return json.RawMessage(`{"room":{"state":{"types":["m.room.*"]},"timeline":{"limit":20,"types":["m.room.message"]}}}`) +} diff --git a/matrix/user.go b/matrix/user.go @@ -12,15 +12,14 @@ import ( "image/png" // image/png needed to load png images _ "image/png" - "log" - "os" "strconv" "strings" "unicode" "unicode/utf8" + log "github.com/sirupsen/logrus" + "github.com/Nordgedanken/Morpheus/matrix/db" - "github.com/Nordgedanken/Morpheus/util" "github.com/disintegration/letteravatar" "github.com/matrix-org/gomatrix" "github.com/therecipe/qt/gui" @@ -39,20 +38,6 @@ import ( _ "golang.org/x/image/tiff" ) -var localLog *log.Logger - -func init() { - var file *os.File - var err error - - localLog = util.Logger() - localLog, file, err = util.StartFileLog(localLog) - if err != nil { - localLog.Fatalln(err) - } - defer file.Close() -} - type circle struct { p image.Point r int @@ -108,7 +93,7 @@ func GetOwnUserAvatar(cli *gomatrix.Client) (avatar *gui.QPixmap, err error) { func GetUserAvatar(cli *gomatrix.Client, mxid string, size int) (avatarResp *gui.QPixmap, err error) { cacheDB, DBOpenErr := db.OpenCacheDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Fatalln(DBOpenErr) } // Init local vars @@ -116,18 +101,20 @@ func GetUserAvatar(cli *gomatrix.Client, mxid string, size int) (avatarResp *gui var IMGdata []byte // Get cache - txn := cacheDB.NewTransaction(false) - avatarDataItem, QueryErr := txn.Get([]byte("user|" + mxid + "|avatarData" + strconv.Itoa(size) + "x" + strconv.Itoa(size))) - if QueryErr != nil && QueryErr != badger.ErrKeyNotFound { - err = QueryErr - return - } - if QueryErr != badger.ErrKeyNotFound { - avatarDataByte, avatarDataErr := avatarDataItem.Value() - avatarData = avatarDataByte - if avatarDataErr != nil { - err = avatarDataErr + DBErr := cacheDB.View(func(txn *badger.Txn) error { + roomAvatarDataItem, QueryErr := txn.Get([]byte("user|" + mxid + "|avatarData" + strconv.Itoa(size) + "x" + strconv.Itoa(size))) + if QueryErr != nil && QueryErr != badger.ErrKeyNotFound { + return QueryErr + } + if QueryErr != badger.ErrKeyNotFound { + avatarDataBytes, avatarDataErr := roomAvatarDataItem.Value() + avatarData = avatarDataBytes + return avatarDataErr } + return nil + }) + if DBErr != nil { + err = DBErr return } @@ -171,8 +158,10 @@ func GetUserAvatar(cli *gomatrix.Client, mxid string, size int) (avatarResp *gui } // Update cache - txn := cacheDB.NewTransaction(true) - DBSetErr := txn.Set([]byte("user|"+mxid+"|avatarData"+strconv.Itoa(size)+"x"+strconv.Itoa(size)), IMGdata) + DBSetErr := cacheDB.Update(func(txn *badger.Txn) error { + DBSetErr := txn.Set([]byte("user|"+mxid+"|avatarData"+strconv.Itoa(size)+"x"+strconv.Itoa(size)), IMGdata) + return DBSetErr + }) if DBSetErr != nil { err = DBSetErr return diff --git a/ui/LoginUI.go b/ui/LoginUI.go @@ -4,8 +4,8 @@ import ( "sync" "github.com/Nordgedanken/Morpheus/matrix" - "github.com/Nordgedanken/Morpheus/util" "github.com/matrix-org/gomatrix" + log "github.com/sirupsen/logrus" "github.com/therecipe/qt/core" "github.com/therecipe/qt/widgets" ) @@ -38,17 +38,6 @@ func (l *LoginUI) GetWidget() (widget *widgets.QWidget) { return } -// InitLogger adds a new logger to the LoginUI struct -func (l *LoginUI) InitLogger() error { - localLog := util.Logger() - localLog, _, err := util.StartFileLog(localLog) - if err != nil { - return err - } - l.localLog = localLog - return nil -} - // NewUI initializes a new login Screen func (l *LoginUI) NewUI() (err error) { widget := widgets.NewQWidget(nil, 0) @@ -134,11 +123,11 @@ func (l *LoginUI) login() (err error) { var wg sync.WaitGroup if l.username != "" && l.password != "" { - l.localLog.Println("Starting Login Sequenze in background") + log.Infoln("Starting Login Sequenze in background") results := make(chan *gomatrix.Client) wg.Add(1) - go matrix.DoLogin(l.username, l.password, "", "", "", l.localLog, results, &wg) + go matrix.DoLogin(l.username, l.password, "", "", "", results, &wg) go func() { wg.Wait() // wait for each execTask to return @@ -150,11 +139,6 @@ func (l *LoginUI) login() (err error) { //TODO Don't switch screen on wrong login data. l.cli = result MainUIStruct := NewMainUIStructWithExistingConfig(l.config, l.window) - MainUILoggerInitErr := MainUIStruct.InitLogger() - if MainUILoggerInitErr != nil { - err = MainUILoggerInitErr - return - } mainUIErr := MainUIStruct.NewUI() if mainUIErr != nil { err = mainUIErr @@ -164,7 +148,7 @@ func (l *LoginUI) login() (err error) { l.window.Resize(l.widget.Size()) } } else { - l.localLog.Println("Username and/or password is empty. Do Nothing.") + log.Warningln("Username and/or password is empty. Do Nothing.") } return } diff --git a/ui/MainUI.go b/ui/MainUI.go @@ -2,18 +2,18 @@ package ui import ( "fmt" - "log" "strconv" "strings" "sync" "github.com/Nordgedanken/Morpheus/matrix" "github.com/Nordgedanken/Morpheus/matrix/db" - "github.com/Nordgedanken/Morpheus/util" + "github.com/Nordgedanken/Morpheus/matrix/syncer" "github.com/dgraph-io/badger" "github.com/matrix-org/gomatrix" "github.com/pkg/errors" "github.com/rhinoman/go-commonmark" + log "github.com/sirupsen/logrus" "github.com/therecipe/qt/core" "github.com/therecipe/qt/gui" "github.com/therecipe/qt/uitools" @@ -55,20 +55,12 @@ func (m *MainUI) GetWidget() (widget *widgets.QWidget) { return } -// InitLogger adds a new logger to the MainUI struct -func (m *MainUI) InitLogger() error { - localLog := util.Logger() - localLog, _, err := util.StartFileLog(localLog) - if err != nil { - return err - } - m.localLog = localLog - return nil -} - // NewUI initializes a new Main Screen func (m *MainUI) NewUI() (err error) { m.widget = widgets.NewQWidget(nil, 0) + m.widgetThread = core.NewQThread(nil) + m.widget.MoveToThread(m.widgetThread) + m.widgetThread.Start() var loader = uitools.NewQUiLoader(nil) var file = core.NewQFile2(":/qml/ui/chat.ui") @@ -78,6 +70,9 @@ func (m *MainUI) NewUI() (err error) { file.Close() m.messageScrollArea = widgets.NewQScrollAreaFromPointer(m.widget.FindChild("messageScroll", core.Qt__FindChildrenRecursively).Pointer()) + m.messageScrollAreaThread = core.NewQThread(nil) + m.messageScrollArea.MoveToThread(m.messageScrollAreaThread) + m.messageScrollAreaThread.Start() messagesScrollAreaContent := widgets.NewQWidgetFromPointer(m.widget.FindChild("messagesScrollAreaContent", core.Qt__FindChildrenRecursively).Pointer()) roomScrollArea := widgets.NewQScrollAreaFromPointer(m.widget.FindChild("roomScroll", core.Qt__FindChildrenRecursively).Pointer()) roomScrollAreaContent := widgets.NewQWidgetFromPointer(m.widget.FindChild("roomScrollAreaContent", core.Qt__FindChildrenRecursively).Pointer()) @@ -139,7 +134,7 @@ func (m *MainUI) NewUI() (err error) { } else { own = false } - NewMessageErr := m.MessageListLayout.NewMessage(messageBody, m.cli, sender, timestamp, m.messageScrollArea, own) + NewMessageErr := m.MessageListLayout.NewMessage(messageBody, m.cli, sender, timestamp, m.messageScrollArea, own, m) if NewMessageErr != nil { err = NewMessageErr return @@ -222,47 +217,47 @@ func (m *MainUI) sendMessage(message string) (err error) { func (m *MainUI) logout(widget *widgets.QWidget, messageScrollArea *widgets.QScrollArea) (err error) { //TODO register enter and show loader or so - m.localLog.Println("Starting Logout Sequence in background") + log.Infoln("Starting Logout Sequence in background") var wg sync.WaitGroup results := make(chan bool) wg.Add(1) - go func(cli *gomatrix.Client, localLog *log.Logger, results chan<- bool) { + go func(cli *gomatrix.Client, results chan<- bool) { defer wg.Done() _, LogoutErr := cli.Logout() if LogoutErr != nil { - localLog.Println(LogoutErr) + log.Errorln(LogoutErr) results <- false } cli.ClearCredentials() - db, DBOpenErr := db.OpenUserDB() + userDB, DBOpenErr := db.OpenUserDB() if DBOpenErr != nil { - localLog.Fatalln(DBOpenErr) + log.Errorln(DBOpenErr) } //Flush complete DB - txn := db.NewTransaction(true) // Read-write txn + txn := userDB.NewTransaction(true) // Read-write txn QueryErr := txn.Delete([]byte("")) if QueryErr != nil { - localLog.Println(QueryErr) + log.Errorln(QueryErr) results <- false } CommitErr := txn.Commit(nil) if CommitErr != nil { - localLog.Println(CommitErr) + log.Errorln(CommitErr) results <- false } - DBPurgeErr := db.PurgeOlderVersions() + DBPurgeErr := userDB.PurgeOlderVersions() if DBPurgeErr != nil { - localLog.Println(DBPurgeErr) + log.Errorln(DBPurgeErr) results <- false } else { results <- true } - }(m.cli, m.localLog, results) + }(m.cli, results) go func() { wg.Wait() // wait for each execTask to return @@ -278,11 +273,6 @@ func (m *MainUI) logout(widget *widgets.QWidget, messageScrollArea *widgets.QScr messageScrollArea.DisconnectResizeEvent() LoginUIStruct := NewLoginUIStructWithExistingConfig(m.config, m.window) - LoginUIStructInitErr := LoginUIStruct.InitLogger() - if LoginUIStructInitErr != nil { - err = LoginUIStructInitErr - return - } loginUIErr := LoginUIStruct.NewUI() if loginUIErr != nil { err = loginUIErr @@ -296,9 +286,23 @@ func (m *MainUI) logout(widget *widgets.QWidget, messageScrollArea *widgets.QScr func (m *MainUI) startSync() (err error) { //Start Syncer! - m.syncer = m.cli.Syncer.(*gomatrix.DefaultSyncer) - m.storage = gomatrix.NewInMemoryStore() + CacheDB, DBOpenErr := db.OpenCacheDB() + if DBOpenErr != nil { + log.Errorln(DBOpenErr) + } + + m.SetCacheDB(&db.MorpheusStorage{ + Database: CacheDB, + }) + m.storage = &syncer.MorpheusStore{ + InMemoryStore: *gomatrix.NewInMemoryStore(), + CacheDatabase: m.cacheDB, + } + + m.syncer = syncer.NewMorpheusSyncer(m.cli.UserID, m.storage) + m.cli.Store = m.storage + m.cli.Syncer = m.syncer m.syncer.Store = m.storage m.syncer.OnEventType("m.room.message", func(ev *gomatrix.Event) { @@ -320,7 +324,7 @@ func (m *MainUI) startSync() (err error) { // Start Non-blocking sync go func() { - m.localLog.Println("Start sync") + log.Infoln("Start sync") for { if e := m.cli.Sync(); e != nil { @@ -368,14 +372,12 @@ func (m *MainUI) loadCache() (err error) { barAtBottom = true } - db, DBOpenErr := db.OpenCacheDB() + cacheDB, DBOpenErr := db.OpenCacheDB() if DBOpenErr != nil { err = DBOpenErr } - log.Println("load cache") - - DBerr := db.View(func(txn *badger.Txn) error { + DBerr := cacheDB.View(func(txn *badger.Txn) error { MsgOpts := badger.DefaultIteratorOptions MsgOpts.PrefetchSize = 10 MsgIt := txn.NewIterator(MsgOpts) @@ -590,12 +592,12 @@ func (m *MainUI) loadCache() (err error) { return nil }) if DBerr != nil { - fmt.Println("DBERR: ", DBerr) + log.Errorln("DBERR: ", DBerr) err = DBerr return } - if !barAtBottom { + if barAtBottom { bar.SetValue(bar.Maximum()) } diff --git a/ui/messageList.go b/ui/messageList.go @@ -1,7 +1,6 @@ package ui import ( - "fmt" "time" "github.com/Nordgedanken/Morpheus/matrix" @@ -45,10 +44,9 @@ func NewMessageList(scrollArea *widgets.QScrollArea, messageView *widgets.QWidge } // NewMessage adds a new message object to the view -func (messageViewLayout *QVBoxLayoutWithTriggerSlot) NewMessage(body string, cli *gomatrix.Client, sender string, timestamp int64, scrollArea *widgets.QScrollArea, own bool) (err error) { +func (messageViewLayout *QVBoxLayoutWithTriggerSlot) NewMessage(body string, cli *gomatrix.Client, sender string, timestamp int64, scrollArea *widgets.QScrollArea, own bool, mainUIStruct *MainUI) (err error) { barAtBottom := false bar := scrollArea.VerticalScrollBar() - fmt.Println(bar.Value()) if bar.Value() == bar.Maximum() { barAtBottom = true } @@ -59,6 +57,9 @@ func (messageViewLayout *QVBoxLayoutWithTriggerSlot) NewMessage(body string, cli } var widget = widgets.NewQWidget(nil, 0) + widgetThread := core.NewQThread(nil) + widget.MoveToThread(widgetThread) + widgetThread.Start() var loader = uitools.NewQUiLoader(nil) var file *core.QFile @@ -76,6 +77,9 @@ func (messageViewLayout *QVBoxLayoutWithTriggerSlot) NewMessage(body string, cli timestampString := timestampFormat.Format("15:04:05 - Mon 2.01.2006") messageWidget := widgets.NewQWidgetFromPointer(widget.FindChild("message", core.Qt__FindChildrenRecursively).Pointer()) + messageWidgetThread := core.NewQThread(nil) + messageWidget.MoveToThread(messageWidgetThread) + messageWidgetThread.Start() avatarLogo := widgets.NewQLabelFromPointer(widget.FindChild("avatar", core.Qt__FindChildrenRecursively).Pointer()) messageContent := widgets.NewQLabelFromPointer(widget.FindChild("messageContent", core.Qt__FindChildrenRecursively).Pointer()) timestampContent := widgets.NewQLabelFromPointer(widget.FindChild("timestamp", core.Qt__FindChildrenRecursively).Pointer()) @@ -94,7 +98,6 @@ func (messageViewLayout *QVBoxLayoutWithTriggerSlot) NewMessage(body string, cli } else { senderDisplayName = senderDisplayNameResp.DisplayName } - fmt.Println(senderDisplayName) senderContent.SetText(senderDisplayName) timestampContent.SetText(timestampString) avatarLogo.SetPixmap(avatar) @@ -115,8 +118,6 @@ func (messageViewLayout *QVBoxLayoutWithTriggerSlot) NewMessage(body string, cli messageViewLayout.InsertWidget(messageViewLayout.Count()+1, messageWidget, 0, core.Qt__AlignBottom) - fmt.Println(barAtBottom) - fmt.Println(bar.Maximum()) if barAtBottom { bar.SetValue(bar.Maximum()) } diff --git a/ui/types.go b/ui/types.go @@ -1,10 +1,11 @@ package ui import ( - "log" - "github.com/Nordgedanken/Morpheus/matrix" + "github.com/Nordgedanken/Morpheus/matrix/db" + "github.com/Nordgedanken/Morpheus/matrix/syncer" "github.com/matrix-org/gomatrix" + "github.com/therecipe/qt/core" "github.com/therecipe/qt/widgets" ) @@ -16,30 +17,40 @@ type config struct { windowWidth int windowHeight int - cli *gomatrix.Client + matrixClient +} + +type matrixClient struct { + databases + cli *gomatrix.Client + syncer *syncer.MorpheusSyncer } -// Logger holds the initialised Logger -type logger struct { - localLog *log.Logger +type databases struct { + cacheDB db.Storer +} + +// SetCurrentRoom sets the new room ID of the MainUI +func (d *databases) SetCacheDB(db db.Storer) { + d.cacheDB = db } // MainUI holds information about the MainUI type MainUI struct { - logger config - widget *widgets.QWidget - RoomAvatar *widgets.QLabel - RoomTitle *widgets.QLabel - RoomTopic *widgets.QLabel - MainWidget *widgets.QWidget - MessageListLayout *QVBoxLayoutWithTriggerSlot - messageScrollArea *widgets.QScrollArea + widget *widgets.QWidget + widgetThread *core.QThread + RoomAvatar *widgets.QLabel + RoomTitle *widgets.QLabel + RoomTopic *widgets.QLabel + MainWidget *widgets.QWidget + MessageListLayout *QVBoxLayoutWithTriggerSlot + messageScrollArea *widgets.QScrollArea + messageScrollAreaThread *core.QThread window *widgets.QMainWindow - syncer *gomatrix.DefaultSyncer - storage *gomatrix.InMemoryStore + storage *syncer.MorpheusStore rooms map[string]*matrix.Room currentRoom string } @@ -51,7 +62,6 @@ func (m *MainUI) SetCurrentRoom(RoomID string) { // LoginUI holds information about the LoginUI type LoginUI struct { - logger config widget *widgets.QWidget window *widgets.QMainWindow diff --git a/util/logger.go b/util/logger.go @@ -1,47 +0,0 @@ -package util - -import ( - "io" - "log" - "os" - "path/filepath" - "sync" - "time" - - "github.com/shibukawa/configdir" -) - -var logInstance *log.Logger -var once sync.Once - -//Logger Exports a default Logger -func Logger() *log.Logger { - once.Do(func() { - logInstance = &log.Logger{} - }) - return logInstance -} - -//StartFileLog initialises the logging function -func StartFileLog(localLog *log.Logger) (logger *log.Logger, logFile *os.File, err error) { - configDirs := configdir.New("Nordgedanken", "Morpheus") - if _, StatErr := os.Stat(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path) + "/log/"); os.IsNotExist(StatErr) { - MkdirErr := os.MkdirAll(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path)+"/log/", 0666) - if MkdirErr != nil { - err = MkdirErr - return - } - } - f, err := os.OpenFile(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path)+"/log/Morpheus.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) - if err != nil { - localLog.Printf("error opening file: %v", err) - } - - mw := io.MultiWriter(os.Stdout, f) - - localLog.SetOutput(mw) - localLog.SetPrefix(time.Now().Format("2006-01-02 15:04:05") + " - ") - logger = localLog - logFile = f - return -}