~edwargix/tallyard

db0590e727b631fc1a8eb15cf1a93d73afd8061b — David Florness 3 years ago 1485006
Generalize ElectionsMap to work without a file
2 files changed, 63 insertions(+), 47 deletions(-)

M cmd/tallyard/main.go
M election/map.go
M cmd/tallyard/main.go => cmd/tallyard/main.go +45 -7
@@ 1,7 1,9 @@
package main

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



@@ 28,9 30,7 @@ func main() {
		log.Errorf("failed to open logging file; using default stderr: %s", err)
	}
	log.Info("tallyard starting...")
	defer func() {
		log.Info("tallyard exiting...")
	}()
	defer log.Info("tallyard exiting...")

	authInfo, err := GetAuthInfo()
	if err != nil {


@@ 46,14 46,12 @@ func main() {
	setDeviceName(client, authInfo.DeviceID)

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

	syncer := client.Syncer.(*mautrix.DefaultSyncer)
	syncer.OnEvent(debugEventHook)


@@ 207,3 205,43 @@ func setDeviceName(client *mautrix.Client, deviceID id.DeviceID) {
		log.Warnf("couldn't set device display name: %s", err)
	}
}

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

func getElections(userID id.UserID) (*election.ElectionsMap, error) {
	if _, err := os.Stat(electionsFname); os.IsNotExist(err) {
		return election.NewElectionsMap(userID, saveElections), nil
	} else if err != nil {
		return nil, fmt.Errorf("couldn't stat elections file: %s", err)
	}

	jsonBytes, err := ioutil.ReadFile(electionsFname)
	if err != nil {
		return nil, fmt.Errorf("couldn't read elections file: %s", err)
	}

	em, err := election.ReadElectionsMapFrom(jsonBytes, userID, saveElections)
	if err != nil {
		return nil, fmt.Errorf("couldn't read elections map: %s", err)
	}

	return em, nil
}

func saveElections(em *election.ElectionsMap) {
	em.RLock()
	defer em.RUnlock()
	for _, el := range em.Elections {
		el.RLock()
		defer el.RUnlock()
	}
	jsonBytes, err := json.Marshal(em)
	if err != nil {
		log.Errorf("couldn't marshal elections: %s", err)
	}
	err = ioutil.WriteFile(electionsFname, jsonBytes, 0600)
	if err != nil {
		log.Errorf("couldn't save elections: %s", err)
	}
	log.Info("saved elections map")
}

M election/map.go => election/map.go +18 -40
@@ 3,13 3,10 @@ package election
import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"sync"
	"time"

	"github.com/kyoh86/xdg"
	log "github.com/sirupsen/logrus"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/id"


@@ 36,24 33,29 @@ type ElectionsMap struct {
	// GUIs.  Ltime represents the latest time L was updated.
	L          map[id.RoomID][]*Election   `json:"-"`
	Ltime      time.Time                   `json:"-"`
	// hook given by parent code to save elections (e.g. to disk)
	save       func(*ElectionsMap)         `json:"-"`
}

const electionsMapVersion = 5

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

func GetElectionsMap(userID id.UserID) (*ElectionsMap, error) {
	if _, err := os.Stat(electionsFname); os.IsNotExist(err) {
		return newElectionsMap(userID), nil
func NewElectionsMap(userID id.UserID, save func(*ElectionsMap)) *ElectionsMap {
	return &ElectionsMap{
		Version:   electionsMapVersion,
		Elections: make(map[id.EventID]*Election),
		L:         make(map[id.RoomID][]*Election),
		Ltime:     time.Now(),
		Rooms:     make(map[id.RoomID]*mautrix.Room),
		UserID:    userID,
		save:      save,
	}
}

func ReadElectionsMapFrom(jsonBytes []byte, userID id.UserID, save func(*ElectionsMap)) (*ElectionsMap, error) {
	em := &ElectionsMap{}
	jsonBytes, err := ioutil.ReadFile(electionsFname)
	if err != nil {
		return nil, fmt.Errorf("error reading data file %s: %s", electionsFname, err)
	}
	err = json.Unmarshal(jsonBytes, em)
	err := json.Unmarshal(jsonBytes, em)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling data file: %s", err)
		return nil, fmt.Errorf("error unmarshalling elections: %s", err)
	}
	if em.Version != electionsMapVersion {
		return nil, fmt.Errorf("Your stored elections schema version (%d) is incompitable with the latest schema version (%d).",


@@ 66,6 68,7 @@ func GetElectionsMap(userID id.UserID) (*ElectionsMap, error) {
	// set runtime attributes for elections
	em.L = make(map[id.RoomID][]*Election, 0)
	em.Ltime = time.Now()
	em.save = save
	storedElections := em.Elections
	em.Elections = make(map[id.EventID]*Election, len(storedElections))
	for _, el := range storedElections {


@@ 92,33 95,8 @@ func (em *ElectionsMap) UnmarshalJSON(b []byte) error {
	return nil
}

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

func (em *ElectionsMap) Save() {
	em.RLock()
	defer em.RUnlock()
	for _, el := range em.Elections {
		el.RLock()
		defer el.RUnlock()
	}
	jsonBytes, err := json.Marshal(em)
	if err != nil {
		log.Errorf("couldn't marshal elections: %s", err)
	}
	err = ioutil.WriteFile(electionsFname, jsonBytes, 0600)
	if err != nil {
		log.Errorf("couldn't save elections: %s", err)
	}
	log.Info("saved elections map")
	em.save(em)
}

func (em *ElectionsMap) GetElection(createID id.EventID) *Election {