aboutsummaryrefslogblamecommitdiff
path: root/appservice/db.go
blob: 34fc0469d4eddce153151dd9fc6d46d4ba5db4f5 (plain) (tree)
1
2
3
4
5
6
7
8
9
10


                  
             
              
 



                                                    
                                        
                                     
 
                                                           
                                                       











                                                         

                                  








                                                                                                                                

                  
 



                          
                                          


                    




                                                                 
                                 





















                                                                     

                          


                                
                               




                                                













                                              

       














                                                                                         


                               







                                                              
                                                                            



                                                                  





                                                 
                                 



                                                    
                                                                                                      




                                                                        
                                                                                







                                                                                         
                                         



                                             
                                                    




                                                                                                                              



                                                                                               


                                                   

                                      
                                       
                                  



                                                              
                                                                                    




                                                                                 




                                                                                   

                                   

                                              
                                               

                                                


                                
                                                       




                                                                            



                                                                  



                                                 
                                 



                                                      
                                                







                                                                                                   
                                                                                     


                                           
                                         






                                       

















                                                                                 
package appservice

import (
	"fmt"
	"sync"

	"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

func InitDb() error {
	var err error

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

	db.AutoMigrate(&DbCache{})

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

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

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

	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
type DbUserMap struct {
	gorm.Model

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

// Room mapping between Matrix rooms and outside rooms
type DbRoomMap struct {
	gorm.Model

	// Network protocol
	Protocol string

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

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

// Room mapping between Matrix rooms and private messages
type DbPmRoomMap struct {
	gorm.Model

	// 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:mxroomoid"`
}

// ---- Simple locking mechanism

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()
}

// ----

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 {
	dbLockSlot(key)
	defer dbUnlockSlot(key)

	// 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) {
	slot_key := fmt.Sprintf("room: %s / %s", protocol, roomId)
	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	var room DbRoomMap

	// Check if room exists in our mapping,
	// If not create it
	must_create := db.First(&room, DbRoomMap{
		Protocol: protocol,
		RoomID:   roomId,
	}).RecordNotFound()
	if must_create {
		alias := roomAlias(protocol, roomId)
		// Lookup alias
		mx_room_id, err := mx.DirectoryRoom(fmt.Sprintf("#%s:%s", alias, config.MatrixDomain))

		// If no alias found, create room
		if err != nil {
			name := fmt.Sprintf("%s (%s)", roomId, protocol)

			mx_room_id, err = mx.CreateRoom(name, alias, []string{})
			if err != nil {
				log.Printf("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.Debugf("Got room id: %s", room.MxRoomID)

	return room.MxRoomID, nil
}

func dbGetMxPmRoom(protocol string, them connector.UserID, themMxId string, usMxId string, usAccount string) (string, error) {
	slot_key := fmt.Sprintf("pmroom: %s / %s / %s / %s", protocol, usMxId, usAccount, them)
	dbLockSlot(slot_key)
	defer dbUnlockSlot(slot_key)

	var room DbPmRoomMap

	must_create := db.First(&room, DbPmRoomMap{
		MxUserID:    usMxId,
		Protocol:    protocol,
		AccountName: usAccount,
		UserID:      them,
	}).RecordNotFound()
	if must_create {
		name := fmt.Sprintf("%s (%s)", them, protocol)

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

		//err = mxRoomJoinAs(mx_room_id, themMxId)
		//if err != nil {
		//	log.Printf("Could not join %s as %s", mx_room_id, themMxId)
		//	return "", err
		//}

		room = DbPmRoomMap{
			MxUserID:    usMxId,
			Protocol:    protocol,
			AccountName: usAccount,
			UserID:      them,
			MxRoomID:    mx_room_id,
		}
		db.Create(&room)
	}
	log.Debugf("Got PM room id: %s", room.MxRoomID)

	return room.MxRoomID, nil
}

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)

	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.Printf("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)
	}

	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
	}
}