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 }