cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / room / registry.go
registry.go 3.1K
1package room23import (4 "sync"5 "time"67 "src.rest/cat/012/internal/sheet"8)910// Registry is the rooms a server holds, by key: a file's path, or @name11// for a named room.12type Registry struct {13 mode Mode14 mu sync.Mutex15 rooms map[string]*Room16}1718// NewRegistry holds rooms whose participants edit as mode says.19func NewRegistry(mode Mode) *Registry {20 return &Registry{mode: mode, rooms: map[string]*Room{}}21}2223// Has reports whether a room with key is open.24func (g *Registry) Has(key string) bool {25 g.mu.Lock()26 defer g.mu.Unlock()27 return g.rooms[key] != nil28}2930// Rooms is how many rooms are open.31func (g *Registry) Rooms() int {32 g.mu.Lock()33 defer g.mu.Unlock()34 seen := map[*Room]bool{}35 for _, r := range g.rooms {36 seen[r] = true37 }38 return len(seen)39}4041// Join seats p in the room with key, opening it on the workbook book42// makes when there's none (book is called with no lock held but the43// registry's, and may be nil when the room is known to be open). It44// reports whether it opened the room. A room's history is shared: undo45// takes back each participant's own steps.46func (g *Registry) Join(key string, p Participant, book func() *sheet.Workbook) (*Seat, bool) {47 return g.join(key, p, book)48}4950// join is Join; with book nil, it seats p only in a room that's open.51func (g *Registry) join(key string, p Participant, book func() *sheet.Workbook) (*Seat, bool) {52 g.mu.Lock()53 defer g.mu.Unlock()54 r := g.rooms[key]55 created := false56 if r == nil && book == nil {57 return nil, false58 }59 if r == nil {60 w := book()61 w.ShareHistory()62 r = &Room{key: key, reg: g, book: w, mode: g.mode}63 r.seen, r.version = w.LastOp(), w.Version()64 g.rooms[key] = r65 created = true66 }67 r.mu.Lock()68 defer r.mu.Unlock()69 r.nextID++70 s := &Seat{room: r, id: r.nextID, name: p.Name(), color: r.freeColor(), p: p, joined: time.Now()}71 if _, ok := p.(Agent); ok {72 s.agent = true73 }74 if len(r.seats) == 0 {75 r.writer = s.id76 }77 r.seats = append(r.seats, s)78 r.notify(s)79 return s, created80}8182// JoinOpen seats p in the room with key if it's open, reporting false83// when it isn't: the agent joins only a workbook someone has open.84func (g *Registry) JoinOpen(key string, p Participant) (*Seat, bool) {85 s, _ := g.join(key, p, nil)86 return s, s != nil87}8889// freeColor is the first color no one in the room has.90func (r *Room) freeColor() int {91 used := map[int]bool{}92 for _, s := range r.seats {93 used[s.color] = true94 }95 for c := range Colors {96 if !used[c] {97 return c98 }99 }100 return r.nextID % Colors101}102103// alias makes the room also the one of key, a name it was saved under,104// unless another room has it.105func (g *Registry) alias(r *Room, key string) {106 g.mu.Lock()107 defer g.mu.Unlock()108 if g.rooms[key] == nil {109 g.rooms[key] = r110 }111}112113// close forgets the room once its last person has left, reporting whether114// it did: someone may have joined since.115func (g *Registry) close(r *Room) bool {116 g.mu.Lock()117 defer g.mu.Unlock()118 r.mu.Lock()119 defer r.mu.Unlock()120 if r.keeper() != nil || r.closed {121 return false122 }123 r.closed = true124 for k, x := range g.rooms {125 if x == r {126 delete(g.rooms, k)127 }128 }129 return true130}