@@ 93,19 93,54 @@ func main() {
}
}()
- el := ui.TUI(client, elections)
- if el == nil || el.LocalVoter.Ballot == nil {
- // user likely hit C-c
+ // select a room
+ roomID, err := ui.RoomListTUI(client, elections)
+ if err != nil {
+ panic(err)
+ }
+ if roomID == "" {
+ // no room selected; user likely hit C-c
+ return
+ }
+
+ // select an election
+ el, err := ui.RoomTUI(client, roomID, elections)
+ if err != nil {
+ panic(err)
+ }
+ if el == nil || el.LocalVoter == nil {
+ // no election selected; user likely hit C-c
return
}
- el.Lock()
+ // wait for election to start
+ err = ui.ElectionWaitTUI(client, el)
+ if err != nil {
+ panic(err)
+ }
+ if el.StartEvt == nil {
+ // election never started; user likely hit C-c
+ return
+ }
+
+ // vote if we need to (user may have voted in previous tallyard
+ // invocation)
+ if el.LocalVoter.Ballot == nil {
+ candidates := el.Candidates
+ ballot := ui.VoteTUI(candidates)
+ el.LocalVoter.Ballot = &ballot
+ el.Save()
+ }
+
+ // set random poly with ballot
el.LocalVoter.Poly = math.NewRandomPoly(uint(len(*el.FinalVoters)-1), 1024, *el.LocalVoter.Ballot)
- el.Unlock()
el.Save()
+ // wait for other voters to finish
el.WaitForVoters(client)
+ // send eval if we need to (if LocalVoter.Eval is set, we've already
+ // sent the event)
if el.LocalVoter.Eval == nil {
err = el.SendEvals(client)
if err != nil {
@@ 113,6 148,8 @@ func main() {
}
}
+ // send sum if we need to (if LocalVoter.Sum is set, we've already sent
+ // the event)
if el.LocalVoter.Sum == nil {
err = el.SendSum(client)
if err != nil {
@@ 18,12 18,11 @@ import (
"tallyard.xyz/election"
)
-func TUI(client *mautrix.Client, elections *election.ElectionsMap) (el *election.Election) {
- alive := true
- app := tview.NewApplication()
+func RoomListTUI(client *mautrix.Client, elections *election.ElectionsMap) (roomID id.RoomID, err error) {
+ app := newTallyardApplication()
resp, err := client.JoinedRooms()
if err != nil {
- panic(err)
+ return
}
// sorts rooms by name (putting rooms without names at the end)
cmpRooms := func(i, j id.RoomID) bool {
@@ 70,25 69,22 @@ func TUI(client *mautrix.Client, elections *election.ElectionsMap) (el *election
}
}
go func() {
- for alive {
+ for app.alive {
app.QueueUpdateDraw(update)
time.Sleep(300 * time.Millisecond)
}
}()
list.SetSelectedFunc(func(i int, _ string, _ string, _ rune) {
- alive = false
app.Stop()
- el = RoomTUI(client, resp.JoinedRooms[i], elections)
+ roomID = resp.JoinedRooms[i]
})
- if err := app.SetRoot(list, true).Run(); err != nil {
- panic(err)
- }
+ app.SetRoot(list, true)
+ err = app.Run()
return
}
-func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.ElectionsMap) (el *election.Election) {
- alive := true
- app := tview.NewApplication()
+func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.ElectionsMap) (el *election.Election, err error) {
+ app := newTallyardApplication()
list := tview.NewList().
AddItem("Create Election", "start a new election in this room", 0, nil)
list.SetTitle("Select election").SetBorder(true)
@@ 113,13 109,12 @@ func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.Elect
}
}
go func() {
- for alive {
+ for app.alive {
app.QueueUpdateDraw(update)
time.Sleep(1 * time.Second)
}
}()
list.SetSelectedFunc(func(i int, _ string, _ string, _ rune) {
- alive = false
app.Stop()
elections.RLock()
// don't do anything if the election under the cursor has
@@ 131,21 126,16 @@ func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.Elect
if i > 0 {
// user wants to join election
-
el = L[i - 1]
// don't need to lock because this goroutine controls LocalVoter
- if el.LocalVoter != nil {
- if el.LocalVoter.Ballot == nil {
- ElectionTUI(client, el)
- }
- } else if joinElectionConfirmation(el) {
- err := el.JoinElection(client)
- if err != nil {
- panic(err)
+ if el.LocalVoter == nil {
+ // ask user if s/he wants to join election
+ if joinElectionConfirmation(el) {
+ err = el.JoinElection(client)
+ } else {
+ // user needs to select a different election
+ el, err = RoomTUI(client, roomID, elections)
}
- ElectionTUI(client, el)
- } else {
- el = RoomTUI(client, roomID, elections)
}
return
}
@@ 155,27 145,24 @@ func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.Elect
if candidates == nil {
return
}
- log.Debugf("title: %s", title)
- log.Debugf("candidates: %s", candidates)
- var err error
+ log.Debugf("created election title: %s", title)
+ log.Debugf("created election candidates: %s", candidates)
el, err = election.CreateElection(client, candidates, title, roomID, elections)
if err != nil {
- panic(err)
+ return
}
err = el.JoinElection(client)
if err != nil {
- panic(err)
+ return
}
- ElectionTUI(client, el)
})
- if err := app.SetRoot(list, true).Run(); err != nil {
- panic(err)
- }
+ app.SetRoot(list, true)
+ err = app.Run()
return
}
func joinElectionConfirmation(el *election.Election) (shouldJoin bool) {
- app := tview.NewApplication()
+ app := newTallyardApplication()
var buttons []string
var text string
@@ 202,7 189,8 @@ func joinElectionConfirmation(el *election.Election) (shouldJoin bool) {
shouldJoin = false
}
})
- if err := app.SetRoot(modal, false).Run(); err != nil {
+ app.SetRoot(modal, false)
+ if err := app.Run(); err != nil {
panic(err)
}
return
@@ 211,7 199,7 @@ func joinElectionConfirmation(el *election.Election) (shouldJoin bool) {
func CreateElectionTUI() (title string, candidates []election.Candidate) {
var form *tview.Form
n := 2
- app := tview.NewApplication()
+ app := newTallyardApplication()
plus := func() {
form.AddInputField(fmt.Sprintf("%d.", n+1), "", 50, nil, nil)
n++
@@ 243,21 231,23 @@ func CreateElectionTUI() (title string, candidates []election.Candidate) {
AddButton("-", minus).
AddButton("Done", done)
form.SetTitle("Create an election").SetBorder(true)
- if err := app.SetRoot(form, true).Run(); err != nil {
+ app.SetRoot(form, true)
+ if err := app.Run(); err != nil {
panic(err)
}
if !hitSubmit {
- // user didn't hit submit button; consider form incomplete
+ // user didn't hit submit button (maybe hit C-c); consider form
+ // incomplete
return "", nil
}
return title, candidates
}
-func ElectionTUI(client *mautrix.Client, el *election.Election) {
+func ElectionWaitTUI(client *mautrix.Client, el *election.Election) error {
votersTextView := tview.NewTextView()
frame := tview.NewFrame(votersTextView)
frame.SetTitle(el.Title).SetBorder(true)
- app := tview.NewApplication()
+ app := newTallyardApplication()
el.RLock()
if el.Creator == el.LocalVoter.UserID {
frame.AddText("Press enter to start the election", false, tview.AlignCenter, tcell.ColorWhite)
@@ 292,7 282,7 @@ func ElectionTUI(client *mautrix.Client, el *election.Election) {
votersTextView.SetText(text)
}
go func() {
- for {
+ for app.alive {
app.QueueUpdateDraw(update)
// has the election started?
@@ 307,27 297,15 @@ func ElectionTUI(client *mautrix.Client, el *election.Election) {
time.Sleep(1 * time.Second)
}
}()
- if err := app.SetRoot(frame, true).Run(); err != nil {
- panic(err)
- }
-
- if el.StartEvt == nil {
- // election was not started; perhaps user hit C-c?
- return
- }
-
- el.RLock()
- candidates := el.Candidates
- el.RUnlock()
-
- ballot := Vote(candidates)
- el.LocalVoter.Ballot = &ballot
+ app.SetRoot(frame, true)
+ err := app.Run()
+ return err
}
// displays a voting UI to the user and returns the encoded ballot
-func Vote(candidates []election.Candidate) []byte {
+func VoteTUI(candidates []election.Candidate) []byte {
ranks := make([]int, len(candidates))
- app := tview.NewApplication()
+ app := newTallyardApplication()
form := tview.NewForm()
form.SetTitle("Vote").SetBorder(true)
@@ 350,14 328,30 @@ func Vote(candidates []election.Candidate) []byte {
}
})
- if err := app.SetRoot(form, true).Run(); err != nil {
+ app.SetRoot(form, true)
+ if err := app.Run(); err != nil {
panic(err)
}
- return GetBallotFromRankings(ranks)
+ return getBallotFromRankings(ranks)
+}
+
+type tallyardApplication struct {
+ *tview.Application
+ alive bool
+}
+
+func newTallyardApplication() *tallyardApplication {
+ return &tallyardApplication{tview.NewApplication(), true}
+}
+
+func (t *tallyardApplication) Run() error {
+ err := t.Application.Run()
+ t.alive = false
+ return err
}
-func GetBallotFromRankings(ranks []int) []byte {
+func getBallotFromRankings(ranks []int) []byte {
n := len(ranks)
candidates := make([]int, n)
@@ 366,7 360,7 @@ func GetBallotFromRankings(ranks []int) []byte {
}
// sort candidates by ranking
- cr := CandidateRanking{candidates, ranks}
+ cr := candidateRanking{candidates, ranks}
sort.Sort(&cr)
// TODO: support more than 255 voters (limit from usage of byte)
@@ 399,20 393,20 @@ func GetBallotFromRankings(ranks []int) []byte {
return barray
}
-type CandidateRanking struct {
+type candidateRanking struct {
candidates []int // becomes list of candidate IDs sorted by rank
ranks []int // maps candidate ID to rank
}
-func (cr *CandidateRanking) Len() int {
+func (cr *candidateRanking) Len() int {
return len(cr.ranks)
}
-func (cr *CandidateRanking) Less(i, j int) bool {
+func (cr *candidateRanking) Less(i, j int) bool {
return cr.ranks[cr.candidates[i]] < cr.ranks[cr.candidates[j]]
}
-func (cr *CandidateRanking) Swap(i, j int) {
+func (cr *candidateRanking) Swap(i, j int) {
tmp := cr.candidates[i]
cr.candidates[i] = cr.candidates[j]
cr.candidates[j] = tmp