usertree now lazy
MODIFY
main.go
main.go
@@ -5,6 +5,9 @@ package main
import (
"flag"
"fmt"
+ "net/http"
+ _ "net/http/pprof"
+
"github.com/Bios-Marcel/cordless/app"
"github.com/Bios-Marcel/cordless/config"
"github.com/Bios-Marcel/cordless/shortcuts"
@@ -12,6 +15,7 @@ import (
)
func main() {
+ go http.ListenAndServe("localhost:8080", nil)
showVersion := flag.Bool("version", false, "Show the version instead of starting cordless")
showShortcutsDialog := flag.Bool("shortcut-dialog", false, "Shows the shortcuts dialog instead of launching cordless")
setConfigDirectory := flag.String("config-dir", "", "Sets the configuration directory")

MODIFY
ui/usertree.go
ui/usertree.go
@@ -2,6 +2,7 @@ package ui
import (
"sort"
+ "sync"
"github.com/Bios-Marcel/cordless/config"
"github.com/Bios-Marcel/cordless/discordutil"
@@ -10,6 +11,7 @@ import (
"github.com/Bios-Marcel/discordgo"
"github.com/Bios-Marcel/tview"
"github.com/gdamore/tcell"
+
)
// UserTree represents the visual list of users in a guild.
@@ -20,20 +22,21 @@ type UserTree struct {
state *discordgo.State
userNodes map[string]*tview.TreeNode
-
roleNodes map[string]*tview.TreeNode
roles []*discordgo.Role
+
+ lock *sync.Mutex
+ loaded bool
}
// NewUserTree creates a new pre-configured UserTree that is empty.
func NewUserTree(state *discordgo.State) *UserTree {
userTree := &UserTree{
state: state,
- userNodes: make(map[string]*tview.TreeNode),
- roleNodes: make(map[string]*tview.TreeNode),
- roles: make([]*discordgo.Role, 0),
rootNode: tview.NewTreeNode(""),
internalTreeView: tview.NewTreeView(),
+ loaded: false,
+ lock: &sync.Mutex{},
}
userTree.internalTreeView.
@@ -48,28 +51,42 @@ func NewUserTree(state *discordgo.State) *UserTree {
// Clear removes all nodes and data out of the view.
func (userTree *UserTree) Clear() {
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ userTree.clear()
+}
+
+func (userTree *UserTree) clear() {
for _, roleNode := range userTree.roleNodes {
roleNode.ClearChildren()
}
-
- userTree.userNodes = make(map[string]*tview.TreeNode)
- userTree.roleNodes = make(map[string]*tview.TreeNode)
- userTree.roles = make([]*discordgo.Role, 0)
-
userTree.rootNode.ClearChildren()
+ userTree.loaded = false
+
+ // After clearing, we don't reallocate anything, since we don't know
+ // whether we actually want to repopulate the tree.
+ userTree.userNodes = nil
+ userTree.roleNodes = nil
+ userTree.roles = nil
}
// LoadGroup loads all users for a group-channel.
func (userTree *UserTree) LoadGroup(channelID string) error {
- userTree.Clear()
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ userTree.clear()
+
+ userTree.userNodes = make(map[string]*tview.TreeNode)
+ userTree.roleNodes = make(map[string]*tview.TreeNode)
channel, stateError := userTree.state.PrivateChannel(channelID)
if stateError != nil {
return stateError
}
- userTree.AddOrUpdateUsers(channel.Recipients)
+ userTree.addOrUpdateUsers(channel.Recipients)
+ userTree.loaded = true
userTree.selectFirstNode()
return nil
@@ -78,7 +95,12 @@ func (userTree *UserTree) LoadGroup(channelID string) error {
// LoadGuild will load all available roles of the guild and then load all
// available members. Afterwards the first available node will be selected.
func (userTree *UserTree) LoadGuild(guildID string) error {
- userTree.Clear()
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ userTree.clear()
+
+ userTree.userNodes = make(map[string]*tview.TreeNode)
+ userTree.roleNodes = make(map[string]*tview.TreeNode)
guildRoles, roleLoadError := userTree.loadGuildRoles(guildID)
if roleLoadError != nil {
@@ -91,6 +113,7 @@ func (userTree *UserTree) LoadGuild(guildID string) error {
return userLoadError
}
+ userTree.loaded = true
userTree.selectFirstNode()
return nil
@@ -111,7 +134,7 @@ func (userTree *UserTree) loadGuildMembers(guildID string) error {
return stateError
}
- userTree.AddOrUpdateMembers(members)
+ userTree.addOrUpdateMembers(members)
return nil
}
@@ -143,6 +166,15 @@ func (userTree *UserTree) loadGuildRoles(guildID string) ([]*discordgo.Role, err
// AddOrUpdateMember adds the passed member to the tree, unless it is
// already part of the tree, in that case the nodes name is updated.
func (userTree *UserTree) AddOrUpdateMember(member *discordgo.Member) {
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ if !userTree.loaded {
+ return
+ }
+ userTree.addOrUpdateMember(member)
+}
+
+func (userTree *UserTree) addOrUpdateMember(member *discordgo.Member) {
nameToUse := discordutil.GetMemberName(member)
if config.Current.UseRandomUserColors {
nameToUse = "[" + discordutil.GetUserColor(member.User) + "]" + nameToUse
@@ -173,6 +205,15 @@ func (userTree *UserTree) AddOrUpdateMember(member *discordgo.Member) {
// AddOrUpdateUser adds a user to the tree, unless the user already exists,
// in that case the users node gets updated.
func (userTree *UserTree) AddOrUpdateUser(user *discordgo.User) {
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ if !userTree.loaded {
+ return
+ }
+ userTree.addOrUpdateUser(user)
+}
+
+func (userTree *UserTree) addOrUpdateUser(user *discordgo.User) {
nameToUse := discordutil.GetUserName(user)
if config.Current.UseRandomUserColors {
nameToUse = "[" + discordutil.GetUserColor(user) + "]" + nameToUse
@@ -192,21 +233,48 @@ func (userTree *UserTree) AddOrUpdateUser(user *discordgo.User) {
// AddOrUpdateUsers adds users to the tree, unless they already exists, in that
// case the users nodes gets updated.
func (userTree *UserTree) AddOrUpdateUsers(users []*discordgo.User) {
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ if !userTree.loaded {
+ return
+ }
+ userTree.addOrUpdateUsers(users)
+}
+
+func (userTree *UserTree) addOrUpdateUsers(users []*discordgo.User) {
for _, user := range users {
- userTree.AddOrUpdateUser(user)
+ userTree.addOrUpdateUser(user)
}
}
// AddOrUpdateMembers adds the all passed members to the tree, unless a node is
// already part of the tree, in that case the nodes name is updated.
func (userTree *UserTree) AddOrUpdateMembers(members []*discordgo.Member) {
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ if !userTree.loaded {
+ return
+ }
+ userTree.addOrUpdateMembers(members)
+}
+
+func (userTree *UserTree) addOrUpdateMembers(members []*discordgo.Member) {
for _, member := range members {
- userTree.AddOrUpdateMember(member)
+ userTree.addOrUpdateMember(member)
}
}
// RemoveMember finds and removes a node from the tree.
func (userTree *UserTree) RemoveMember(member *discordgo.Member) {
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ if !userTree.loaded {
+ return
+ }
+ userTree.removeMember(member)
+}
+
+func (userTree *UserTree) removeMember(member *discordgo.Member) {
userNode, contains := userTree.userNodes[member.User.ID]
if contains {
userTree.rootNode.Walk(func(node, parent *tview.TreeNode) bool {
@@ -242,8 +310,13 @@ func (userTree *UserTree) RemoveMember(member *discordgo.Member) {
// RemoveMembers finds and removes all passed members from the tree.
func (userTree *UserTree) RemoveMembers(members []*discordgo.Member) {
+ userTree.lock.Lock()
+ defer userTree.lock.Unlock()
+ if !userTree.loaded {
+ return
+ }
for _, member := range members {
- userTree.RemoveMember(member)
+ userTree.removeMember(member)
}
}
@@ -251,3 +324,7 @@ func (userTree *UserTree) RemoveMembers(members []*discordgo.Member) {
func (userTree *UserTree) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) {
userTree.internalTreeView.SetInputCapture(capture)
}
+
+func (userTree *UserTree) IsLoaded() bool {
+ return userTree.loaded
+}

MODIFY
ui/window.go
ui/window.go
@@ -203,12 +203,17 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
}
}
+ //Currently has to happen before userlist loading, as it might not load otherwise
+ window.RefreshLayout()
+
+ window.userList.Clear()
+ if window.userList.internalTreeView.IsVisible() {
userLoadError := window.userList.LoadGuild(guildID)
if userLoadError != nil {
window.ShowErrorDialog(userLoadError.Error())
}
+ }
- window.RefreshLayout()
})
window.guildList = guildList
@@ -268,16 +273,22 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
window.chatView.Lock()
defer window.chatView.Unlock()
window.QueueUpdateDrawSynchronized(func() {
- window.LoadChannel(channel)
+ window.userList.Clear()
+ window.RefreshLayout()
if channel.Type == discordgo.ChannelTypeGroupDM {
+
+ if window.userList.internalTreeView.IsVisible() {
loadError := window.userList.LoadGroup(channel.ID)
if loadError != nil {
fmt.Fprintln(window.commandView.commandOutput, "Error loading users for channel.")
}
}
+ }
- window.RefreshLayout()
+ })
+ window.QueueUpdateDrawSynchronized(func() {
+ window.LoadChannel(channel)
})
}()
})
@@ -288,8 +299,7 @@ func NewWindow(doRestart chan bool, app *tview.Application, session *discordgo.S
defer window.chatView.Unlock()
userChannels, _ := window.session.UserChannels()
for _, userChannel := range userChannels {
- if userChannel.Type == discordgo.ChannelTypeDM &&
- (userChannel.Recipients[0].ID == userID) {
+ if userChannel.Type == discordgo.ChannelTypeDM && userChannel.Recipients[0].ID == userID {
window.QueueUpdateDrawSynchronized(func() {
window.loadPrivateChannel(userChannel)
})
@@ -2105,14 +2115,7 @@ func (window *Window) handleGlobalShortcuts(event *tcell.EventKey) *tcell.EventK
window.app.SetFocus(window.commandView.commandInput.internalTextView)
} else if shortcuts.ToggleUserContainer.Equals(event) {
- config.Current.ShowUserContainer = !config.Current.ShowUserContainer
-
- if !config.Current.ShowUserContainer && window.app.GetFocus() == window.userList.internalTreeView {
- window.app.SetFocus(window.messageInput.GetPrimitive())
- }
-
- config.PersistConfig()
- window.RefreshLayout()
+ window.toggleUserContainer()
} else if shortcuts.FocusChannelContainer.Equals(event) {
window.SwitchToGuildsPage()
window.app.SetFocus(window.channelTree)
@@ -2142,6 +2145,29 @@ func (window *Window) handleGlobalShortcuts(event *tcell.EventKey) *tcell.EventK
return nil
}
+func (window *Window) toggleUserContainer() {
+ config.Current.ShowUserContainer = !config.Current.ShowUserContainer
+
+ if !config.Current.ShowUserContainer && window.app.GetFocus() == window.userList.internalTreeView {
+ window.app.SetFocus(window.messageInput.GetPrimitive())
+ }
+
+ if config.Current.ShowUserContainer {
+ if !window.userList.IsLoaded() {
+ if window.selectedChannel != nil && window.selectedChannel.GuildID == "" {
+ window.userList.LoadGroup(window.selectedChannel.ID)
+ } else if window.selectedGuild != nil {
+ window.userList.LoadGuild(window.selectedGuild.ID)
+ }
+ }
+ } else {
+ window.userList.Clear()
+ }
+
+ config.PersistConfig()
+ window.RefreshLayout()
+}
+
// toggleBareChat will display only the chatview as the fullscreen application
// root. Calling this method again will revert the view to it's normal state.
func (window *Window) toggleBareChat() {

usertree now lazy