package main
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(&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", "mx_user_id", "account_name", "user_id")
db.AutoMigrate(&DbKv{})
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:"primaryKey;autoIncrement:true"`
// 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:"primaryKey;autoIncrement:true"`
// Protocol and user ID on the bridged network
Protocol string
UserID connector.UserID
// Puppetted Matrix ID
MxUserID string `gorm:"index"`
}
// Room mapping between Matrix rooms and outside rooms
type DbRoomMap struct {
ID uint `gorm:"primaryKey;autoIncrement:true"`
// Network protocol and room ID on the bridged network
Protocol string
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:"primaryKey;autoIncrement:true"`
// User id and account name of the local end viewed on Matrix
MxUserID string
Protocol string
AccountName string
// User id to reach them on the bridged network
UserID connector.UserID
// Bridged room for PMs
MxRoomID string `gorm:"index"`
}
// Key-value store for various things
type DbKv struct {
Key string `gorm:"primaryKey"`
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
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 dbKvGet(key string) string {
var entry DbKv
if db.Where(&DbKv{Key: key}).First(&entry).RecordNotFound() {
return ""
} else {
return entry.Value
}
}
func dbKvPut(key string, value string) {
dbLockSlot(key)
defer dbUnlockSlot(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) {
var entry DbKv
db.Where(&DbKv{Key: key}).Assign(&DbKv{Value: value}).FirstOrCreate(&entry)
}
func dbKvTestAndSet(key string, value string) bool {
dbLockSlot(key)
defer dbUnlockSlot(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)
// 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)
return room.MxRoomID, nil
}
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 := 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, 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)
return room.MxRoomID, nil
}
func dbDeletePmRoom(room *DbPmRoomMap) {
if room.ID != 0 {
db.Delete(room)
} else {
log.Warnf("In dbDeletePmRoom: %#v (not deleting since primary key is zero)", room)
}
}
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.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)
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
}
}