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