Implement on-demand updating of room & user pictures

This commit is contained in:
Alex 2020-02-21 18:43:47 +01:00
parent fd768a10be
commit e1b838d304
4 changed files with 131 additions and 28 deletions

View file

@ -137,11 +137,15 @@ func (a *Account) userInfoUpdatedInternal(user UserID, info *UserInfo) error {
} }
if info.Avatar != nil { if info.Avatar != nil {
cache_key := fmt.Sprintf("%s/user_avatar/%s", a.Protocol, user)
cache_val := info.Avatar.Filename()
if cache_val == "" || dbCacheTestAndSet(cache_key, cache_val) {
err2 := mx.ProfileAvatar(mx_user_id, info.Avatar) err2 := mx.ProfileAvatar(mx_user_id, info.Avatar)
if err2 != nil { if err2 != nil {
err = err2 err = err2
} }
} }
}
return err return err
} }
@ -185,11 +189,15 @@ func (a *Account) roomInfoUpdatedInternal(roomId RoomID, author UserID, info *Ro
} }
if info.Picture != nil { if info.Picture != nil {
cache_key := fmt.Sprintf("%s/room_picture/%s", a.Protocol, roomId)
cache_val := info.Picture.Filename()
if cache_val == "" || dbCacheTestAndSet(cache_key, cache_val) {
err2 := mx.RoomAvatarAs(mx_room_id, info.Picture, as_mxid) err2 := mx.RoomAvatarAs(mx_room_id, info.Picture, as_mxid)
if err2 != nil { if err2 != nil {
err = err2 err = err2
} }
} }
}
return err return err
} }
@ -204,6 +212,9 @@ func (a *Account) Event(event *Event) {
} }
func (a *Account) eventInternal(event *Event) error { func (a *Account) eventInternal(event *Event) error {
// TODO: automatically ignore events that come from one of our bridged matrix users
// TODO: deduplicate events if we have several matrix users joined the same room (hard problem)
mx_user_id, err := dbGetMxUser(a.Protocol, event.Author) mx_user_id, err := dbGetMxUser(a.Protocol, event.Author)
if err != nil { if err != nil {
return err return err

View file

@ -23,6 +23,8 @@ func InitDb() error {
return err return err
} }
db.AutoMigrate(&DbCache{})
db.AutoMigrate(&DbUserMap{}) db.AutoMigrate(&DbUserMap{})
db.Model(&DbUserMap{}).AddIndex("idx_protocol_user", "protocol", "user_id") db.Model(&DbUserMap{}).AddIndex("idx_protocol_user", "protocol", "user_id")
@ -35,6 +37,14 @@ func InitDb() error {
return nil return nil
} }
// Long-term cache entries
type DbCache struct {
gorm.Model
Key string `gorm:"unique_index"`
Value string
}
// User mapping between protocol user IDs and puppeted matrix ids // User mapping between protocol user IDs and puppeted matrix ids
type DbUserMap struct { type DbUserMap struct {
gorm.Model gorm.Model
@ -76,6 +86,30 @@ type DbPmRoomMap struct {
// ---- // ----
func dbCacheGet(key string) string {
var entry DbCache
if db.Where(&DbCache{Key: key}).First(&entry).RecordNotFound() {
return ""
} else {
return entry.Value
}
}
func dbCachePut(key string, value string) {
var entry DbCache
db.Where(&DbCache{Key: key}).Assign(&DbCache{Value: value}).FirstOrCreate(&entry)
}
func dbCacheTestAndSet(key string, value string) bool {
// TODO make this really an atomic operation
// True if value was changed, false if was already set
if dbCacheGet(key) != value {
dbCachePut(key, value)
return true
}
return false
}
func dbGetMxRoom(protocol string, roomId connector.RoomID) (string, error) { func dbGetMxRoom(protocol string, roomId connector.RoomID) (string, error) {
var room DbRoomMap var room DbRoomMap

View file

@ -314,17 +314,21 @@ func (mm *Mattermost) handleConnected() {
} else { } else {
room_info.Name = t.Team.Name + " / " + room_info.Name room_info.Name = t.Team.Name + " / " + room_info.Name
} }
// TODO: cache last update time so we don't do this needlessly
if t.Team.LastTeamIconUpdate > 0 { if t.Team.LastTeamIconUpdate > 0 {
room_info.Picture = &LazyBlobMediaObject{
ObjectFilename: fmt.Sprintf("%s-%d",
t.Team.Name,
t.Team.LastTeamIconUpdate),
GetFn: func(o *LazyBlobMediaObject) error {
team_img, resp := mm.conn.Client.GetTeamIcon(t.Id, "") team_img, resp := mm.conn.Client.GetTeamIcon(t.Id, "")
if resp.Error == nil { if resp.Error == nil {
room_info.Picture = &BlobMediaObject{
ObjectFilename: t.Team.Name,
ObjectMimetype: http.DetectContentType(team_img),
ObjectData: team_img,
}
} else {
log.Warnf("Could not get team image: %s", resp.Error) log.Warnf("Could not get team image: %s", resp.Error)
return resp.Error
}
o.ObjectData = team_img
o.ObjectMimetype = http.DetectContentType(team_img)
return nil
},
} }
} }
break break
@ -378,22 +382,26 @@ func (mm *Mattermost) updateUserInfo(user *model.User) {
DisplayName: userDisp, DisplayName: userDisp,
} }
if user.LastPictureUpdate > 0 { if user.LastPictureUpdate > 0 {
// TODO: cache last update time so we don't do this needlessly ui.Avatar = &LazyBlobMediaObject{
ObjectFilename: fmt.Sprintf("%s-%d",
user.Username,
user.LastPictureUpdate),
GetFn: func(o *LazyBlobMediaObject) error {
img, resp := mm.conn.Client.GetProfileImage(user.Id, "") img, resp := mm.conn.Client.GetProfileImage(user.Id, "")
if resp.Error == nil { if resp.Error == nil {
ui.Avatar = &BlobMediaObject{
ObjectFilename: user.Username,
ObjectMimetype: http.DetectContentType(img),
ObjectData: img,
}
} else {
log.Warnf("Could not get profile picture: %s", resp.Error) log.Warnf("Could not get profile picture: %s", resp.Error)
return resp.Error
}
o.ObjectData = img
o.ObjectMimetype = http.DetectContentType(img)
return nil
},
}
} }
mm.handler.UserInfoUpdated(userId, ui) mm.handler.UserInfoUpdated(userId, ui)
mm.userdisplaynamemap[userId] = userDisp mm.userdisplaynamemap[userId] = userDisp
} }
} }
}
func (mm *Mattermost) ensureJoined(user *model.User, roomId RoomID) { func (mm *Mattermost) ensureJoined(user *model.User, roomId RoomID) {
userId := UserID(fmt.Sprintf("%s@%s", user.Username, mm.server)) userId := UserID(fmt.Sprintf("%s@%s", user.Username, mm.server))

View file

@ -132,3 +132,53 @@ type nullCloseReader struct {
func (ncr nullCloseReader) Close() error { func (ncr nullCloseReader) Close() error {
return nil return nil
} }
// ----
type LazyBlobMediaObject struct {
ObjectFilename string
ObjectMimetype string
ObjectImageSize *ImageSize
ObjectData []byte
GetFn func(o *LazyBlobMediaObject) error
}
func (m *LazyBlobMediaObject) Filename() string {
return m.ObjectFilename
}
func (m *LazyBlobMediaObject) Size() int64 {
if m.ObjectData == nil {
m.GetFn(m)
}
return int64(len(m.ObjectData))
}
func (m *LazyBlobMediaObject) Mimetype() string {
if m.ObjectData == nil {
m.GetFn(m)
}
return m.ObjectMimetype
}
func (m *LazyBlobMediaObject) ImageSize() *ImageSize {
if m.ObjectData == nil {
m.GetFn(m)
}
return m.ObjectImageSize
}
func (m *LazyBlobMediaObject) Read() (io.ReadCloser, error) {
if m.ObjectData == nil {
err := m.GetFn(m)
if err != nil {
return nil, err
}
}
return nullCloseReader{bytes.NewBuffer(m.ObjectData)}, nil
}
func (m *LazyBlobMediaObject) URL() string {
return ""
}