aboutsummaryrefslogblamecommitdiff
path: root/db.go
blob: 42c9aeef0798f3dc4dc735403da8dc4b5a306aed (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
            

        
             
              
 
                                         



                                                    
                                        
                                     
 
                                                           
                                                       


               
                              








                                                         

                                          

                                                                                                                             
 
                                    
                                                                                            

                                    
                                                                                            

                                      
                                                                                                                                            
 
                               
 




                                       

                  
 



                             



                                      

 

                                                       
                                    
 
                                   


                          

                  
                               

 

                                                                 
                                    
 


                                      



                                                      
                                    

                           
                       

                                         
                               

                                 
                                      



                                                         
                                    

                                                                     


                          

                                
                               

                               
                                      

 

                                     

                                         

 
                                


                                                                    












                                              




                                                      
 
                                 





                                                 

                                                                     





                                  
                                        




                                    







                                                

                                                                                   
                                    

 
                                                    



                                    
 
                                                              

                                  
         
 
                                 
                   

 

       
                                                                            

                                                               


                                    


                                                    


                                               
                          

                                                 
                                 
                           
 

                                                    
 





                                                                                    
 






                                                                                



                                           
                                         



                                             


                                                       



                                 




                                                                            
                                                                                                                              






                                            
 


                                    


                                                    
 
                            
                                                                
 


                                                              
                                                                                    
                               
                                                                                


                                      
                                   

                                              
                                               

                                                


                                


                                                       



                                 









                                         
                                                                            

                                                               


                                    



                                                    



                                                 
                                 



                                                      
                                                

                                                                                                   
                                                                                     




                                                                            
                                                                                     


                                           
                                         




                                       


                                                       

                                 

















                                                                                 
package main

import (
	"fmt"
	"sync"

	"github.com/hashicorp/golang-lru"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	log "github.com/sirupsen/logrus"
	"golang.org/x/crypto/blake2b"

	"git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
	"git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib"
)

var db *gorm.DB
var dbCache *lru.TwoQueueCache

func InitDb() error {
	var err error

	db, err = gorm.Open(config.DbType, config.DbPath)
	if err != nil {
		return err
	}

	db.AutoMigrate(&DbAccountConfig{})

	db.AutoMigrate(&DbJoinedRoom{})
	db.Model(&DbJoinedRoom{}).AddIndex("idx_joined_room_user_protocol_account", "mx_user_id", "protocol", "account_name")

	db.AutoMigrate(&DbUserMap{})
	db.Model(&DbUserMap{}).AddIndex("idx_user_map_protocol_user", "protocol", "user_id")

	db.AutoMigrate(&DbRoomMap{})
	db.Model(&DbRoomMap{}).AddIndex("idx_room_map_protocol_room", "protocol", "room_id")

	db.AutoMigrate(&DbPmRoomMap{})
	db.Model(&DbPmRoomMap{}).AddIndex("idx_pm_room_map_protocol_user_account_user", "protocol", "user_id", "mx_user_id", "account_name")

	db.AutoMigrate(&DbKv{})

	dbCache, err = lru.New2Q(10000)
	if err != nil {
		return err
	}

	return nil
}

// Account configuration
type DbAccountConfig struct {
	gorm.Model

	MxUserID string `gorm:"index"`
	Name     string
	Protocol string
	Config   string
}

// List of joined channels to be re-joined on reconnect
type DbJoinedRoom struct {
	ID uint `gorm:"primary_key"`

	// User id and account name
	MxUserID    string
	Protocol    string
	AccountName string

	// Room ID
	RoomID connector.RoomID
}

// User mapping between protocol user IDs and puppeted matrix ids
type DbUserMap struct {
	ID uint `gorm:"primary_key"`

	Protocol string
	UserID   connector.UserID
	MxUserID string `gorm:"index"`
}

// Room mapping between Matrix rooms and outside rooms
type DbRoomMap struct {
	ID uint `gorm:"primary_key"`

	// Network protocol
	Protocol string

	// Room id on the bridged network
	RoomID connector.RoomID

	// Bridged room matrix id
	MxRoomID string `gorm:"index"`
}

// Room mapping between Matrix rooms and private messages
type DbPmRoomMap struct {
	ID uint `gorm:"primary_key"`

	// User id and account name of the local end viewed on Matrix
	MxUserID    string
	Protocol    string
	AccountName string

	// User id to reach them
	UserID connector.UserID

	// Bridged room for PMs
	MxRoomID string `gorm:"index"`
}

// Key-value store for various things
type DbKv struct {
	Key   string `gorm:"primary_key"`
	Value string
}

// ---- Simple locking mechanism
// Slot keys are strings that identify the object we are acting upon
// They define which lock to lock for a certain operation
// They are also used as keys in the LRU cache

var dbLocks [256]sync.Mutex

func dbLockSlot(key string) {
	slot := blake2b.Sum512([]byte(key))[0]
	dbLocks[slot].Lock()
}

func dbUnlockSlot(key string) {
	slot := blake2b.Sum512([]byte(key))[0]
	dbLocks[slot].Unlock()
}

// ---- Key-value store supporting atomic test-and-set

func dbKvSlotKey(key string) string {
	return "kv:" + key
}

func dbKvGet(key string) string {
	slot_key := dbKvSlotKey(key)

	if ent, ok := dbCache.Get(slot_key); ok {
		return ent.(string)
	}

	var entry DbKv
	if db.Where(&DbKv{Key: key}).First(&entry).RecordNotFound() {
		return ""
	} else {
		return entry.Value
	}
}

func dbKvPut(key string, value string) {
	slot_key := dbKvSlotKey(key)

	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	dbKvPutLocked(key, value)
}

// Variant of dbKvPut that does not take a lock,
// use this if the slot is already locked
func dbKvPutLocked(key string, value string) {
	slot_key := dbKvSlotKey(key)

	var entry DbKv
	db.Where(&DbKv{Key: key}).Assign(&DbKv{Value: value}).FirstOrCreate(&entry)
	dbCache.Add(slot_key, value)
}

func dbKvTestAndSet(key string, value string) bool {
	slot_key := dbKvSlotKey(key)

	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	// True if value was changed, false if was already set
	if dbKvGet(key) == value {
		return false
	}

	dbKvPutLocked(key, value)
	return true
}

// ----

func dbGetMxRoom(protocol string, roomId connector.RoomID) (string, error) {
	slot_key := fmt.Sprintf("room:%s/%s", protocol, roomId)

	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	if cached, ok := dbCache.Get(slot_key); ok {
		return cached.(string), nil
	}

	// Check if room exists in our mapping,
	// If not create it
	var room DbRoomMap
	must_create := db.First(&room, DbRoomMap{
		Protocol: protocol,
		RoomID:   roomId,
	}).RecordNotFound()

	if must_create {
		alias := roomAlias(protocol, roomId)

		// Delete previous alias if it existed
		prev_full_alias := fmt.Sprintf("#%s:%s", alias, config.MatrixDomain)
		mx_room_id, err := mx.DirectoryRoom(prev_full_alias)
		if err == nil {
			mx.DirectoryDeleteRoom(prev_full_alias)
		}

		// Create room
		name := fmt.Sprintf("%s (%s)", roomId, protocol)

		mx_room_id, err = mx.CreateRoom(name, alias, []string{})
		if err != nil {
			log.Warnf("Could not create room for %s: %s", name, err)
			return "", err
		}

		room = DbRoomMap{
			Protocol: protocol,
			RoomID:   roomId,
			MxRoomID: mx_room_id,
		}
		db.Create(&room)
	}

	log.Tracef("%s -> %s", slot_key, room.MxRoomID)
	dbCache.Add(slot_key, room.MxRoomID)

	return room.MxRoomID, nil
}

func dbPmRoomSlotKey(room *DbPmRoomMap) string {
	return fmt.Sprintf("pmroom:%s/%s/%s/%s",
		room.Protocol, room.MxUserID, room.AccountName, room.UserID)
}

func dbGetMxPmRoom(protocol string, them connector.UserID, themMxId string, usMxId string, usAccount string) (string, error) {
	map_key := &DbPmRoomMap{
		MxUserID:    usMxId,
		Protocol:    protocol,
		AccountName: usAccount,
		UserID:      them,
	}
	slot_key := dbPmRoomSlotKey(map_key)

	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	if cached, ok := dbCache.Get(slot_key); ok {
		return cached.(string), nil
	}

	var room DbPmRoomMap
	must_create := db.First(&room, map_key).RecordNotFound()

	if must_create {
		name := fmt.Sprintf("%s (%s)", them, protocol)

		mx_room_id, err := mx.CreateDirectRoomAs([]string{usMxId}, themMxId)
		if err != nil {
			log.Warnf("Could not create room for %s: %s", name, err)
			return "", err
		}

		room = DbPmRoomMap{
			MxUserID:    usMxId,
			Protocol:    protocol,
			AccountName: usAccount,
			UserID:      them,
			MxRoomID:    mx_room_id,
		}
		db.Create(&room)
	}

	log.Tracef("%s -> %s", slot_key, room.MxRoomID)
	dbCache.Add(slot_key, room.MxRoomID)

	return room.MxRoomID, nil
}

func dbDeletePmRoom(room *DbPmRoomMap) {
	slot_key := dbPmRoomSlotKey(room)

	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	db.Delete(room)
	dbCache.Remove(slot_key)
}

func dbGetMxUser(protocol string, userId connector.UserID) (string, error) {
	slot_key := fmt.Sprintf("user:%s/%s", protocol, userId)

	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	if cached, ok := dbCache.Get(slot_key); ok {
		return cached.(string), nil
	}

	var user DbUserMap

	must_create := db.First(&user, DbUserMap{
		Protocol: protocol,
		UserID:   userId,
	}).RecordNotFound()
	if must_create {
		username := userMxId(protocol, userId)

		err := mx.RegisterUser(username)
		if err != nil {
			if mxE, ok := err.(*mxlib.MxError); !ok || mxE.ErrCode != "M_USER_IN_USE" {
				log.Warnf("Could not register %s: %s", username, err)
				return "", err
			}
		}

		mxid := fmt.Sprintf("@%s:%s", username, config.MatrixDomain)
		mx.ProfileDisplayname(mxid, fmt.Sprintf("%s (%s)", userId, protocol))

		user = DbUserMap{
			Protocol: protocol,
			UserID:   userId,
			MxUserID: mxid,
		}
		db.Create(&user)
	}

	log.Tracef("%s -> %s", slot_key, user.MxUserID)
	dbCache.Add(slot_key, user.MxUserID)

	return user.MxUserID, nil
}

func dbIsPmRoom(mxRoomId string) *DbPmRoomMap {
	var pm_room DbPmRoomMap
	if db.First(&pm_room, DbPmRoomMap{MxRoomID: mxRoomId}).RecordNotFound() {
		return nil
	} else {
		return &pm_room
	}
}

func dbIsPublicRoom(mxRoomId string) *DbRoomMap {
	var room DbRoomMap
	if db.First(&room, DbRoomMap{MxRoomID: mxRoomId}).RecordNotFound() {
		return nil
	} else {
		return &room
	}
}