morpheus

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

MainUI.go (19384B)


      1 package ui
      2 
      3 import (
      4 	"fmt"
      5 	"strconv"
      6 	"strings"
      7 	"sync"
      8 
      9 	"github.com/Nordgedanken/Morpheus/matrix"
     10 	"github.com/Nordgedanken/Morpheus/matrix/db"
     11 	"github.com/Nordgedanken/Morpheus/matrix/syncer"
     12 	"github.com/dgraph-io/badger"
     13 	"github.com/matrix-org/gomatrix"
     14 	"github.com/pkg/errors"
     15 	"github.com/rhinoman/go-commonmark"
     16 	log "github.com/sirupsen/logrus"
     17 	"github.com/therecipe/qt/core"
     18 	"github.com/therecipe/qt/gui"
     19 	"github.com/therecipe/qt/uitools"
     20 	"github.com/therecipe/qt/widgets"
     21 	"github.com/opennota/linkify"
     22 )
     23 
     24 // NewMainUIStruct gives you a MainUI struct with prefilled data
     25 func NewMainUIStruct(windowWidth, windowHeight int, window *widgets.QMainWindow) (mainUIStruct MainUI) {
     26 	configStruct := config{
     27 		windowWidth:  windowWidth,
     28 		windowHeight: windowHeight,
     29 	}
     30 	mainUIStruct = MainUI{
     31 		config: configStruct,
     32 		window: window,
     33 		rooms:  make(map[string]*matrix.Room),
     34 	}
     35 	return
     36 }
     37 
     38 // NewMainUIStructWithExistingConfig gives you a MainUI struct with prefilled data and data from a previous Config
     39 func NewMainUIStructWithExistingConfig(configStruct config, window *widgets.QMainWindow) (mainUIStruct MainUI) {
     40 	mainUIStruct = MainUI{
     41 		config: configStruct,
     42 		window: window,
     43 		rooms:  make(map[string]*matrix.Room),
     44 	}
     45 	return
     46 }
     47 
     48 // SetCli allows you to add a gomatrix.Client to your MainUI struct
     49 func (m *MainUI) SetCli(cli *gomatrix.Client) {
     50 	m.cli = cli
     51 }
     52 
     53 // GetWidget gives you the widget of the MainUI struct
     54 func (m *MainUI) GetWidget() (widget *widgets.QWidget) {
     55 	widget = m.widget
     56 	return
     57 }
     58 
     59 // NewUI initializes a new Main Screen
     60 func (m *MainUI) NewUI() (err error) {
     61 	m.widget = widgets.NewQWidget(nil, 0)
     62 	m.widgetThread = core.NewQThread(nil)
     63 	m.widget.MoveToThread(m.widgetThread)
     64 	m.widgetThread.Start()
     65 
     66 	var loader = uitools.NewQUiLoader(nil)
     67 	var file = core.NewQFile2(":/qml/ui/chat.ui")
     68 
     69 	file.Open(core.QIODevice__ReadOnly)
     70 	m.MainWidget = loader.Load(file, m.widget)
     71 	file.Close()
     72 
     73 	m.messageScrollArea = widgets.NewQScrollAreaFromPointer(m.widget.FindChild("messageScroll", core.Qt__FindChildrenRecursively).Pointer())
     74 	m.messageScrollAreaThread = core.NewQThread(nil)
     75 	m.messageScrollArea.MoveToThread(m.messageScrollAreaThread)
     76 	m.messageScrollAreaThread.Start()
     77 	messagesScrollAreaContent := widgets.NewQWidgetFromPointer(m.widget.FindChild("messagesScrollAreaContent", core.Qt__FindChildrenRecursively).Pointer())
     78 	roomScrollArea := widgets.NewQScrollAreaFromPointer(m.widget.FindChild("roomScroll", core.Qt__FindChildrenRecursively).Pointer())
     79 	roomScrollAreaContent := widgets.NewQWidgetFromPointer(m.widget.FindChild("roomScrollAreaContent", core.Qt__FindChildrenRecursively).Pointer())
     80 
     81 	m.RoomAvatar = widgets.NewQLabelFromPointer(m.widget.FindChild("roomAvatar", core.Qt__FindChildrenRecursively).Pointer())
     82 	m.RoomTitle = widgets.NewQLabelFromPointer(m.widget.FindChild("RoomTitle", core.Qt__FindChildrenRecursively).Pointer())
     83 	m.RoomTopic = widgets.NewQLabelFromPointer(m.widget.FindChild("Topic", core.Qt__FindChildrenRecursively).Pointer())
     84 
     85 	var layout = widgets.NewQHBoxLayout()
     86 	m.window.SetLayout(layout)
     87 	layout.InsertWidget(0, m.MainWidget, 0, core.Qt__AlignTop|core.Qt__AlignLeft)
     88 	layout.SetSpacing(0)
     89 	layout.SetContentsMargins(0, 0, 0, 0)
     90 
     91 	m.widget.ConnectResizeEvent(func(event *gui.QResizeEvent) {
     92 		m.MainWidget.Resize(event.Size())
     93 		event.Accept()
     94 	})
     95 
     96 	//Set Avatar
     97 	avatarLogo := widgets.NewQLabelFromPointer(m.widget.FindChild("UserAvatar", core.Qt__FindChildrenRecursively).Pointer())
     98 	avatar, AvatarErr := matrix.GetOwnUserAvatar(m.cli)
     99 	if AvatarErr != nil {
    100 		err = AvatarErr
    101 		return
    102 	}
    103 	avatarLogo.SetPixmap(avatar)
    104 
    105 	//Handle LogoutButton
    106 	logoutButton := widgets.NewQPushButtonFromPointer(m.widget.FindChild("LogoutButton", core.Qt__FindChildrenRecursively).Pointer())
    107 	logoutButton.ConnectClicked(func(_ bool) {
    108 		LogoutErr := m.logout(m.widget, m.messageScrollArea)
    109 		if LogoutErr != nil {
    110 			err = LogoutErr
    111 			return
    112 		}
    113 	})
    114 
    115 	// Init Message View
    116 	m.MessageListLayout = NewMessageList(m.messageScrollArea, messagesScrollAreaContent)
    117 
    118 	// Init Room View
    119 	roomListLayout := NewRoomList(roomScrollArea, roomScrollAreaContent)
    120 
    121 	m.messageScrollArea.SetWidgetResizable(true)
    122 	m.messageScrollArea.SetHorizontalScrollBarPolicy(core.Qt__ScrollBarAlwaysOff)
    123 	m.messageScrollArea.SetContentsMargins(0, 0, 0, 0)
    124 	//messageScrollArea.SetSizeAdjustPolicy(widgets.QAbstractScrollArea__AdjustToContents)
    125 
    126 	roomScrollArea.SetWidgetResizable(true)
    127 	roomScrollArea.SetHorizontalScrollBarPolicy(core.Qt__ScrollBarAlwaysOff)
    128 	roomScrollArea.SetContentsMargins(0, 0, 0, 0)
    129 	roomScrollArea.SetSizeAdjustPolicy(widgets.QAbstractScrollArea__AdjustToContents)
    130 
    131 	m.MessageListLayout.ConnectTriggerMessage(func(messageBody, sender string, timestamp int64) {
    132 		var own bool
    133 		if sender == m.cli.UserID {
    134 			own = true
    135 		} else {
    136 			own = false
    137 		}
    138 		lm := linkify.Links(messageBody)
    139 		for _, l := range lm {
    140 			link := messageBody[l.Start:l.End]
    141 			if l.Start-9 > 0 {
    142 				log.Infoln("if1: ",messageBody[l.Start-9:l.Start])
    143 				if !strings.Contains(messageBody[l.Start-9:l.Start], "<a href='") {
    144 					if l.Start-(1+l.Start+l.End) > 0 {
    145 						log.Infoln("if2: ",messageBody[l.Start-(1+l.Start+l.End):l.Start])
    146 						if !strings.Contains(messageBody[l.Start-(1+l.Start+l.End):l.Start], "<a href='"+link+"'>") {
    147 							messageBody = strings.Replace(messageBody, link, "<a href='"+link+"'>"+link+"</a>", -1)
    148 						}
    149 					}else if l.Start-(1+l.Start+l.End) <= 0 {
    150 						log.Infoln("else2: ",messageBody[0:l.Start])
    151 						if !strings.Contains(messageBody[0:l.Start], "<a href='"+link+"'>") {
    152 							messageBody = strings.Replace(messageBody, link, "<a href='" + link + "'>" + link + "</a>", -1)
    153 						}
    154 					}
    155 				}
    156 			} else if l.Start-9 <= 0 {
    157 				log.Infoln(messageBody[0:l.Start])
    158 				if !strings.Contains(messageBody[0:l.Start], "<a href='") {
    159 					if !strings.Contains(messageBody[0:l.Start], "<a href='"+link+"'>") {
    160 						messageBody = strings.Replace(messageBody, link, "<a href='" + link + "'>" + link + "</a>", -1)
    161 					}
    162 				}
    163 			}
    164 		}
    165 		NewMessageErr := m.MessageListLayout.NewMessage(messageBody, m.cli, sender, timestamp, m.messageScrollArea, own, m)
    166 		if NewMessageErr != nil {
    167 			err = NewMessageErr
    168 			return
    169 		}
    170 	})
    171 
    172 	m.startSync()
    173 	m.widget.SetSizePolicy2(widgets.QSizePolicy__Expanding, widgets.QSizePolicy__Expanding)
    174 	m.MainWidget.SetSizePolicy2(widgets.QSizePolicy__Expanding, widgets.QSizePolicy__Expanding)
    175 
    176 	roomListLayout.ConnectTriggerRoom(func(roomID string) {
    177 		room := m.rooms[roomID]
    178 
    179 		NewRoomErr := roomListLayout.NewRoom(room, roomScrollArea, m)
    180 		if NewRoomErr != nil {
    181 			err = NewRoomErr
    182 			return
    183 		}
    184 	})
    185 
    186 	m.initRoomList(roomListLayout, roomScrollArea)
    187 
    188 	go m.loadCache()
    189 
    190 	m.MainWidget.SetWindowTitle("Morpheus - " + m.rooms[m.currentRoom].GetRoomTopic())
    191 
    192 	avatar, roomAvatarErr := m.rooms[m.currentRoom].GetRoomAvatar()
    193 	if roomAvatarErr != nil {
    194 		err = roomAvatarErr
    195 		return
    196 	}
    197 	m.RoomAvatar.SetPixmap(avatar)
    198 
    199 	m.RoomTitle.SetText(m.rooms[m.currentRoom].GetRoomName())
    200 
    201 	m.RoomTopic.SetText(m.rooms[m.currentRoom].GetRoomTopic())
    202 
    203 	var message string
    204 	messageInput := widgets.NewQLineEditFromPointer(m.widget.FindChild("MessageInput", core.Qt__FindChildrenRecursively).Pointer())
    205 	messageInput.ConnectTextChanged(func(value string) {
    206 		message = value
    207 	})
    208 
    209 	m.window.ConnectKeyPressEvent(func(ev *gui.QKeyEvent) {
    210 		if int(ev.Key()) == int(core.Qt__Key_Enter) || int(ev.Key()) == int(core.Qt__Key_Return) {
    211 			MessageErr := m.sendMessage(message)
    212 			if MessageErr != nil {
    213 				err = MessageErr
    214 				return
    215 			}
    216 
    217 			messageInput.Clear()
    218 			ev.Accept()
    219 		} else {
    220 			messageInput.KeyPressEventDefault(ev)
    221 			ev.Ignore()
    222 		}
    223 	})
    224 
    225 	return
    226 }
    227 
    228 func (m *MainUI) sendMessage(message string) (err error) {
    229 	messageOriginal := message
    230 	lm := linkify.Links(message)
    231 	for _, l := range lm {
    232 		link := message[l.Start:l.End]
    233 		message = strings.Replace(message, link, "<a href='" + link + "'>" + link + "</a>", -1)
    234 	}
    235 
    236 	mardownMessage := commonmark.Md2Html(message, 0)
    237 	if mardownMessage == message {
    238 		_, SendErr := m.cli.SendMessageEvent(m.currentRoom, "m.room.message", matrix.HTMLMessage{MsgType: "m.text", Body: messageOriginal, FormattedBody: message, Format: "org.matrix.custom.html"})
    239 		if SendErr != nil {
    240 			err = SendErr
    241 			return
    242 		}
    243 	} else {
    244 		_, SendErr := m.cli.SendMessageEvent(m.currentRoom, "m.room.message", matrix.HTMLMessage{MsgType: "m.text", Body: message, FormattedBody: mardownMessage, Format: "org.matrix.custom.html"})
    245 		if SendErr != nil {
    246 			err = SendErr
    247 			return
    248 		}
    249 	}
    250 	return
    251 }
    252 
    253 func (m *MainUI) logout(widget *widgets.QWidget, messageScrollArea *widgets.QScrollArea) (err error) {
    254 	//TODO register enter and show loader or so
    255 	log.Infoln("Starting Logout Sequence in background")
    256 	var wg sync.WaitGroup
    257 	results := make(chan bool)
    258 
    259 	wg.Add(1)
    260 	go func(cli *gomatrix.Client, results chan<- bool) {
    261 		defer wg.Done()
    262 		_, LogoutErr := cli.Logout()
    263 		if LogoutErr != nil {
    264 			log.Errorln(LogoutErr)
    265 			results <- false
    266 		}
    267 		cli.ClearCredentials()
    268 
    269 		userDB, DBOpenErr := db.OpenUserDB()
    270 		if DBOpenErr != nil {
    271 			log.Errorln(DBOpenErr)
    272 		}
    273 
    274 		//Flush complete DB
    275 		txn := userDB.NewTransaction(true) // Read-write txn
    276 		QueryErr := txn.Delete([]byte(""))
    277 		if QueryErr != nil {
    278 			log.Errorln(QueryErr)
    279 			results <- false
    280 		}
    281 
    282 		CommitErr := txn.Commit(nil)
    283 		if CommitErr != nil {
    284 			log.Errorln(CommitErr)
    285 			results <- false
    286 		}
    287 
    288 		DBPurgeErr := userDB.PurgeOlderVersions()
    289 		if DBPurgeErr != nil {
    290 			log.Errorln(DBPurgeErr)
    291 			results <- false
    292 		} else {
    293 			results <- true
    294 		}
    295 	}(m.cli, results)
    296 
    297 	go func() {
    298 		wg.Wait()      // wait for each execTask to return
    299 		close(results) // then close the results channel
    300 	}()
    301 
    302 	//Show LoginUI
    303 	for result := range results {
    304 		if result {
    305 			m.window.DisconnectKeyPressEvent()
    306 			m.window.DisconnectResizeEvent()
    307 			widget.DisconnectResizeEvent()
    308 			messageScrollArea.DisconnectResizeEvent()
    309 
    310 			LoginUIStruct := NewLoginUIStructWithExistingConfig(m.config, m.window)
    311 			loginUIErr := LoginUIStruct.NewUI()
    312 			if loginUIErr != nil {
    313 				err = loginUIErr
    314 				return
    315 			}
    316 			m.window.SetCentralWidget(LoginUIStruct.GetWidget())
    317 		}
    318 	}
    319 	return
    320 }
    321 
    322 func (m *MainUI) startSync() (err error) {
    323 	//Start Syncer!
    324 	CacheDB, DBOpenErr := db.OpenCacheDB()
    325 	if DBOpenErr != nil {
    326 		log.Errorln(DBOpenErr)
    327 	}
    328 
    329 	m.SetCacheDB(&db.MorpheusStorage{
    330 		Database: CacheDB,
    331 	})
    332 	m.storage = &syncer.MorpheusStore{
    333 		InMemoryStore: *gomatrix.NewInMemoryStore(),
    334 		CacheDatabase: m.cacheDB,
    335 	}
    336 
    337 	m.syncer = syncer.NewMorpheusSyncer(m.cli.UserID, m.storage)
    338 
    339 	m.cli.Store = m.storage
    340 	m.cli.Syncer = m.syncer
    341 	m.syncer.Store = m.storage
    342 
    343 	m.syncer.OnEventType("m.room.message", func(ev *gomatrix.Event) {
    344 		formattedBody, _ := ev.Content["formatted_body"]
    345 		var msg string
    346 		msg, _ = formattedBody.(string)
    347 		if msg == "" {
    348 			msg, _ = ev.Body()
    349 		}
    350 		room := ev.RoomID
    351 		sender := ev.Sender
    352 		id := ev.ID
    353 		timestamp := ev.Timestamp
    354 		go db.CacheMessageEvents(id, sender, room, msg, timestamp)
    355 		if room == m.currentRoom {
    356 			go m.MessageListLayout.TriggerMessage(msg, sender, timestamp)
    357 		}
    358 	})
    359 
    360 	// Start Non-blocking sync
    361 	go func() {
    362 		log.Infoln("Start sync")
    363 		for {
    364 
    365 			if e := m.cli.Sync(); e != nil {
    366 				err = e
    367 			}
    368 		}
    369 	}()
    370 	return
    371 }
    372 
    373 func (m *MainUI) initRoomList(roomListLayout *QRoomVBoxLayoutWithTriggerSlot, roomScrollArea *widgets.QScrollArea) (err error) {
    374 	rooms, ReqErr := m.cli.JoinedRooms()
    375 	if ReqErr != nil {
    376 		err = ReqErr
    377 		return
    378 	}
    379 
    380 	x := 0
    381 	for _, roomID := range rooms.JoinedRooms {
    382 		if x == 0 {
    383 			m.currentRoom = roomID
    384 		}
    385 		x++
    386 		m.rooms[roomID] = matrix.NewRoom(roomID, m.cli)
    387 		roomListLayout.TriggerRoom(roomID)
    388 	}
    389 
    390 	return
    391 }
    392 
    393 func contains(slice []string, item string) bool {
    394 	set := make(map[string]struct{}, len(slice))
    395 	for _, s := range slice {
    396 		set[s] = struct{}{}
    397 	}
    398 
    399 	_, ok := set[item]
    400 	return ok
    401 }
    402 
    403 func (m *MainUI) loadCache() (err error) {
    404 	barAtBottom := false
    405 	bar := m.messageScrollArea.VerticalScrollBar()
    406 	if bar.Value() == bar.Maximum() {
    407 		barAtBottom = true
    408 	}
    409 
    410 	cacheDB, DBOpenErr := db.OpenCacheDB()
    411 	if DBOpenErr != nil {
    412 		err = DBOpenErr
    413 	}
    414 
    415 	DBerr := cacheDB.View(func(txn *badger.Txn) error {
    416 		MsgOpts := badger.DefaultIteratorOptions
    417 		MsgOpts.PrefetchSize = 10
    418 		MsgIt := txn.NewIterator(MsgOpts)
    419 		MsgPrefix := []byte("room|" + m.currentRoom + "|messages|")
    420 
    421 		var doneMsg []string
    422 
    423 		for MsgIt.Seek(MsgPrefix); MsgIt.ValidForPrefix(MsgPrefix); MsgIt.Next() {
    424 			item := MsgIt.Item()
    425 			key := item.Key()
    426 			stringKey := fmt.Sprintf("%s", key)
    427 			value, ValueErr := item.Value()
    428 			if ValueErr != nil {
    429 				return ValueErr
    430 			}
    431 			stringValue := fmt.Sprintf("%s", value)
    432 
    433 			if strings.HasSuffix(stringKey, "|id") {
    434 				if !contains(doneMsg, stringValue) {
    435 					// Remember we already added this message to the view
    436 					doneMsg = append(doneMsg, stringValue)
    437 
    438 					// Get all Data
    439 					senderItem, senderErr := txn.Get([]byte(strings.Replace(stringKey, "|id", "|sender", -1)))
    440 					if senderErr != nil {
    441 						return errors.WithMessage(senderErr, "Key: "+strings.Replace(stringKey, "|id", "|sender", -1))
    442 					}
    443 
    444 					senderValue, senderValueErr := senderItem.Value()
    445 					if senderValueErr != nil {
    446 						return senderValueErr
    447 					}
    448 					sender := fmt.Sprintf("%s", senderValue)
    449 
    450 					messageItem, messageErr := txn.Get([]byte(strings.Replace(stringKey, "|id", "|messageString", -1)))
    451 					if messageErr != nil {
    452 						return errors.WithMessage(messageErr, "Key: "+strings.Replace(stringKey, "|id", "|messageString", -1))
    453 					}
    454 
    455 					messageValue, messageValueErr := messageItem.Value()
    456 					if messageValueErr != nil {
    457 						return messageValueErr
    458 					}
    459 					msg := fmt.Sprintf("%s", messageValue)
    460 
    461 					timestampItem, timestampErr := txn.Get([]byte(strings.Replace(stringKey, "|id", "|timestamp", -1)))
    462 					if timestampErr != nil {
    463 						return errors.WithMessage(timestampErr, "Key: "+strings.Replace(stringKey, "|id", "|timestamp", -1))
    464 					}
    465 
    466 					timestampValue, timestampValueErr := timestampItem.Value()
    467 					if timestampValueErr != nil {
    468 						return timestampValueErr
    469 					}
    470 					timestamp := fmt.Sprintf("%s", timestampValue)
    471 					timestampInt, ConvErr := strconv.ParseInt(timestamp, 10, 64)
    472 					if ConvErr != nil {
    473 						return ConvErr
    474 					}
    475 
    476 					m.MessageListLayout.TriggerMessage(msg, sender, timestampInt)
    477 				}
    478 			}
    479 
    480 			if strings.HasSuffix(stringKey, "|sender") {
    481 				idItem, idErr := txn.Get([]byte(strings.Replace(stringKey, "|sender", "|id", -1)))
    482 				if idErr != nil {
    483 					return errors.WithMessage(idErr, "Key: "+strings.Replace(stringKey, "|sender", "|id", -1))
    484 				}
    485 				idValue, idValueErr := idItem.Value()
    486 				if idValueErr != nil {
    487 					return idValueErr
    488 				}
    489 				id := fmt.Sprintf("%s", idValue)
    490 
    491 				if !contains(doneMsg, id) {
    492 					// Remember we already added this message to the view
    493 					doneMsg = append(doneMsg, id)
    494 
    495 					// Get all Data
    496 					sender := stringValue
    497 
    498 					messageItem, messageErr := txn.Get([]byte(strings.Replace(stringKey, "|sender", "|messageString", -1)))
    499 					if messageErr != nil {
    500 						return errors.WithMessage(messageErr, "Key: "+strings.Replace(stringKey, "|sender", "|messageString", -1))
    501 					}
    502 
    503 					messageValue, messageValueErr := messageItem.Value()
    504 					if messageValueErr != nil {
    505 						return messageValueErr
    506 					}
    507 					msg := fmt.Sprintf("%s", messageValue)
    508 
    509 					timestampItem, timestampErr := txn.Get([]byte(strings.Replace(stringKey, "|sender", "|timestamp", -1)))
    510 					if timestampErr != nil {
    511 						return errors.WithMessage(timestampErr, "Key: "+strings.Replace(stringKey, "|sender", "|timestamp", -1))
    512 					}
    513 
    514 					timestampValue, timestampValueErr := timestampItem.Value()
    515 					if timestampValueErr != nil {
    516 						return timestampValueErr
    517 					}
    518 					timestamp := fmt.Sprintf("%s", timestampValue)
    519 
    520 					timestampInt, ConvErr := strconv.ParseInt(timestamp, 10, 64)
    521 					if ConvErr != nil {
    522 						return ConvErr
    523 					}
    524 
    525 					m.MessageListLayout.TriggerMessage(msg, sender, timestampInt)
    526 				}
    527 			}
    528 
    529 			if strings.HasSuffix(stringKey, "|messageString") {
    530 				idItem, idErr := txn.Get([]byte(strings.Replace(stringKey, "|messageString", "|id", -1)))
    531 				if idErr != nil {
    532 					return errors.WithMessage(idErr, "Key: "+strings.Replace(stringKey, "|messageString", "|id", -1))
    533 				}
    534 
    535 				idValue, idValueErr := idItem.Value()
    536 				if idValueErr != nil {
    537 					return idValueErr
    538 				}
    539 				id := fmt.Sprintf("%s", idValue)
    540 
    541 				if !contains(doneMsg, id) {
    542 					// Remember we already added this message to the view
    543 					doneMsg = append(doneMsg, id)
    544 
    545 					// Get all Data
    546 					senderItem, senderErr := txn.Get([]byte(strings.Replace(stringKey, "|messageString", "|sender", -1)))
    547 					if senderErr != nil {
    548 						return errors.WithMessage(senderErr, "Key: "+strings.Replace(stringKey, "|messageString", "|sender", -1))
    549 					}
    550 
    551 					senderValue, senderValueErr := senderItem.Value()
    552 					if senderValueErr != nil {
    553 						return senderValueErr
    554 					}
    555 					sender := fmt.Sprintf("%s", senderValue)
    556 
    557 					msg := stringValue
    558 
    559 					timestampItem, timestampErr := txn.Get([]byte(strings.Replace(stringKey, "|messageString", "|timestamp", -1)))
    560 					if timestampErr != nil {
    561 						return errors.WithMessage(timestampErr, "Key: "+strings.Replace(stringKey, "|messageString", "|timestamp", -1))
    562 					}
    563 
    564 					timestampValue, timestampValueErr := timestampItem.Value()
    565 					if timestampValueErr != nil {
    566 						return timestampValueErr
    567 					}
    568 					timestamp := fmt.Sprintf("%s", timestampValue)
    569 
    570 					timestampInt, ConvErr := strconv.ParseInt(timestamp, 10, 64)
    571 					if ConvErr != nil {
    572 						return ConvErr
    573 					}
    574 
    575 					m.MessageListLayout.TriggerMessage(msg, sender, timestampInt)
    576 				}
    577 			}
    578 
    579 			if strings.HasSuffix(stringKey, "|timestamp") {
    580 				idItem, idErr := txn.Get([]byte(strings.Replace(stringKey, "|timestamp", "|id", -1)))
    581 				if idErr != nil {
    582 					return errors.WithMessage(idErr, "Key: "+strings.Replace(stringKey, "|timestamp", "|id", -1))
    583 				}
    584 				idValue, idValueErr := idItem.Value()
    585 				if idValueErr != nil {
    586 					return idValueErr
    587 				}
    588 				id := fmt.Sprintf("%s", idValue)
    589 
    590 				if !contains(doneMsg, id) {
    591 					// Remember we already added this message to the view
    592 					doneMsg = append(doneMsg, id)
    593 
    594 					// Get all Data
    595 					senderItem, senderErr := txn.Get([]byte(strings.Replace(stringKey, "|timestamp", "|sender", -1)))
    596 					if senderErr != nil {
    597 						return errors.WithMessage(senderErr, "Key: "+strings.Replace(stringKey, "|timestamp", "|sender", -1))
    598 					}
    599 					senderValue, senderValueErr := senderItem.Value()
    600 					if senderValueErr != nil {
    601 						return senderValueErr
    602 					}
    603 					sender := fmt.Sprintf("%s", senderValue)
    604 
    605 					messageItem, messageErr := txn.Get([]byte(strings.Replace(stringKey, "|timestamp", "|messageString", -1)))
    606 					if messageErr != nil {
    607 						return errors.WithMessage(messageErr, "Key: "+strings.Replace(stringKey, "|timestamp", "|messageString", -1))
    608 					}
    609 					messageValue, messageValueErr := messageItem.Value()
    610 					if messageValueErr != nil {
    611 						return messageValueErr
    612 					}
    613 					msg := fmt.Sprintf("%s", messageValue)
    614 
    615 					timestamp := stringValue
    616 
    617 					timestampInt, ConvErr := strconv.ParseInt(timestamp, 10, 64)
    618 					if ConvErr != nil {
    619 						return ConvErr
    620 					}
    621 
    622 					m.MessageListLayout.TriggerMessage(msg, sender, timestampInt)
    623 				}
    624 			}
    625 		}
    626 
    627 		return nil
    628 	})
    629 	if DBerr != nil {
    630 		log.Errorln("DBERR: ", DBerr)
    631 		err = DBerr
    632 		return
    633 	}
    634 
    635 	if barAtBottom {
    636 		bar.SetValue(bar.Maximum())
    637 	}
    638 
    639 	return
    640 }