~edwargix/tallyard

e641e3b3c490bdea97fa86e3c4594ce82e1b65a1 — David Florness 4 years ago 1755252
Use the elections map as our mautrix store
5 files changed, 121 insertions(+), 77 deletions(-)

M cmd/tallyard/main.go
M election/election.go
M election/map.go
D matrix/rooms_disk_store.go
A matrix/store.go
M cmd/tallyard/main.go => cmd/tallyard/main.go +6 -9
@@ 60,25 60,22 @@ func main() {
	if err != nil {
		panic(err)
	}
	store, err := matrix.NewRoomsDiskStore()

	// setup the elections store
	elections, err := election.GetElectionsMap(client.UserID)
	if err != nil {
		panic(err)
	}
	client.Store = matrix.NewTallyardStore(elections)
	defer func() {
		err := store.Save()
		err := elections.Save()
		if err != nil {
			panic(err)
		}
	}()
	client.Store = store

	elections, err := election.GetElectionsMap()
	if err != nil {
		panic(err)
	}

	syncer := client.Syncer.(*mautrix.DefaultSyncer)
	syncer.OnEvent(client.Store.(*matrix.RoomsDiskStore).UpdateState)
	syncer.OnEvent(client.Store.(*matrix.TallyardStore).UpdateState)
	election.SetupEventHooks(client, syncer, elections)

	go func() {

M election/election.go => election/election.go +1 -1
@@ 19,7 19,7 @@ type Election struct {
	Joins             map[id.EventID]*Voter `json:"joins"`
	LocalVoter        *LocalVoter           `json:"local_voter,omitempty"`
	RoomID            id.RoomID             `json:"room_id"`
	Save              func()                `json:"-"`
	Save              func() error          `json:"-"`
	StartEvt          *event.Event          `json:"start_evt,omitempty"`
	Title             string                `json:"title"`
}

M election/map.go => election/map.go +36 -11
@@ 9,9 9,8 @@ import (
	"sync"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/kyoh86/xdg"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/id"
)



@@ 37,16 36,21 @@ type ElectionsMap struct {
	// maps join event ID to voter. This is a convenience in scenarios when
	// we don't know the exact election
	Joins map[id.EventID]*Voter `json:"-"`

	// needed by the state store
	NextBatch string                      `json:"next_batch"`
	Rooms     map[id.RoomID]*mautrix.Room `json:"rooms"`
	UserID    id.UserID                   `json:"userid"`
}

const electionsMapVersion = 1
const electionsMapVersion = 2

var electionsFname = xdg.DataHome() + "/tallyard/elections.json"

func GetElectionsMap() (em *ElectionsMap, err error) {
func GetElectionsMap(userID id.UserID) (em *ElectionsMap, err error) {
	em = &ElectionsMap{}
	if _, err = os.Stat(electionsFname); os.IsNotExist(err) {
		return newElectionsMap(), nil
		return newElectionsMap(userID), nil
	}
	jsonBytes, err := ioutil.ReadFile(electionsFname)
	if err != nil {


@@ 60,6 64,10 @@ func GetElectionsMap() (em *ElectionsMap, err error) {
		return nil, fmt.Errorf("Your stored elections schema version (%d) is incompitable with the latest schema version (%d).",
			em.Version, electionsMapVersion)
	}
	if userID != em.UserID {
		// TODO support multiple users?
		return nil, fmt.Errorf("user IDs don't match")
	}
	em.L = make(map[id.RoomID][]*Election, 0)
	em.Ltime = time.Now()
	for createEventId, el := range em.M {


@@ 75,17 83,35 @@ func GetElectionsMap() (em *ElectionsMap, err error) {
	return
}

func newElectionsMap() *ElectionsMap {
func (em *ElectionsMap) UnmarshalJSON(b []byte) error {
	type Alias ElectionsMap
	err := json.Unmarshal(b, (*Alias)(em))
	if err != nil {
		return err
	}
	for _, room := range em.Rooms {
		for eventType, events := range room.State {
			for _, evt := range events {
				evt.Content.ParseRaw(eventType)
			}
		}
	}
	return nil
}

func newElectionsMap(userID id.UserID) *ElectionsMap {
	return &ElectionsMap{
		Version: electionsMapVersion,
		M:       make(map[id.EventID]*Election),
		L:       make(map[id.RoomID][]*Election),
		Ltime:   time.Now(),
		Joins:   make(map[id.EventID]*Voter),
		Rooms:   make(map[id.RoomID]*mautrix.Room),
		UserID:  userID,
	}
}

func (em *ElectionsMap) Save() {
func (em *ElectionsMap) Save() error {
	em.RLock()
	defer em.RUnlock()
	for _, el := range em.M {


@@ 94,14 120,13 @@ func (em *ElectionsMap) Save() {
	}
	jsonBytes, err := json.Marshal(em)
	if err != nil {
		log.Errorf("couldn't marshal elections: %s", err)
		return
		return fmt.Errorf("couldn't marshal elections: %s", err)
	}
	err = ioutil.WriteFile(electionsFname, jsonBytes, 0600)
	if err != nil {
		log.Errorf("couldn't save elections: %s", err)
		return
		return fmt.Errorf("couldn't save elections: %s", err)
	}
	return nil
}

func (em *ElectionsMap) Get(createEventID id.EventID) *Election {

D matrix/rooms_disk_store.go => matrix/rooms_disk_store.go +0 -56
@@ 1,56 0,0 @@
package matrix

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"

	"github.com/kyoh86/xdg"
	"maunium.net/go/mautrix"
)

// TODO: this sucks; this is not what JSON is for

type RoomsDiskStore struct {
	*mautrix.InMemoryStore
}

var roomsFname = xdg.DataHome() + "/tallyard/rooms.json"

func NewRoomsDiskStore() (*RoomsDiskStore, error) {
	t := &RoomsDiskStore{
		mautrix.NewInMemoryStore(),
	}
	if _, err := os.Stat(roomsFname); os.IsNotExist(err) {
		return t, nil
	}
	roomBytes, err := ioutil.ReadFile(roomsFname)
	if err != nil {
		return t, fmt.Errorf("couldn't read rooms file: %s", err)
	}
	err = json.Unmarshal(roomBytes, &t.Rooms)
	if err != nil {
		return t, fmt.Errorf("couldn't unmarshal rooms file: %s", err)
	}
	for _, room := range t.Rooms {
		for stateEventType, m := range room.State {
			for _, evt := range m {
				evt.Content.ParseRaw(stateEventType)
			}
		}
	}
	return t, nil
}

func (t *RoomsDiskStore) Save() error {
	jsonBytes, err := json.Marshal(t.Rooms)
	if err != nil {
		return fmt.Errorf("couldn't marshal rooms: %s", err)
	}
	err = ioutil.WriteFile(roomsFname, jsonBytes, 0600)
	if err != nil {
		return fmt.Errorf("couldn't write rooms: %s", err)
	}
	return nil
}

A matrix/store.go => matrix/store.go +78 -0
@@ 0,0 1,78 @@
package matrix

import (
	log "github.com/sirupsen/logrus"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/event"
	"maunium.net/go/mautrix/id"
	"tallyard.xyz/election"
)

type TallyardStore struct {
	electionsMap *election.ElectionsMap
	filters      map[id.UserID]string
}

func NewTallyardStore(electionsMap *election.ElectionsMap) *TallyardStore {
	return &TallyardStore{
		electionsMap: electionsMap,
		filters:      make(map[id.UserID]string),
	}
}

func (s *TallyardStore) SaveFilterID(userID id.UserID, filterID string) {
	s.filters[userID] = filterID
}

func (s *TallyardStore) LoadFilterID(userID id.UserID) string {
	return s.filters[userID]
}

func (s *TallyardStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
	if userID == s.electionsMap.UserID {
		s.electionsMap.Lock()
		defer s.electionsMap.Unlock()
		s.electionsMap.NextBatch = nextBatchToken
	} else {
		// *should* never happen
		log.Warnf("SaveNextBatch: %s is not the local user %s; please report to devs",
			userID, s.electionsMap.UserID)
	}
}

func (s *TallyardStore) LoadNextBatch(userID id.UserID) string {
	if userID == s.electionsMap.UserID {
		s.electionsMap.RLock()
		defer s.electionsMap.RUnlock()
		return s.electionsMap.NextBatch
	} else {
		// *should* never happen
		log.Warnf("LoadNextBatch: %s is not the local user %s; please report to devs",
			userID, s.electionsMap.UserID)
		return ""
	}
}

func (s *TallyardStore) SaveRoom(room *mautrix.Room) {
	s.electionsMap.Lock()
	defer s.electionsMap.Unlock()
	s.electionsMap.Rooms[room.ID] = room
}

func (s *TallyardStore) LoadRoom(roomID id.RoomID) *mautrix.Room {
	s.electionsMap.RLock()
	defer s.electionsMap.RUnlock()
	return s.electionsMap.Rooms[roomID]
}

func (s *TallyardStore) UpdateState(_ mautrix.EventSource, evt *event.Event) {
	if !evt.Type.IsState() {
		return
	}
	room := s.LoadRoom(evt.RoomID)
	if room == nil {
		room = mautrix.NewRoom(evt.RoomID)
		s.SaveRoom(room)
	}
	room.UpdateState(evt)
}