Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

lists.gno

7.13 Kb · 212 lines
  1package bolo
  2
  3import "gno.land/p/nt/avl/v0"
  4
  5// Lists (API §4). A size is clamped to 1–50 (INV-9). The campaign lists page by
  6// cursor: each reaches its place in its index by key, in O(log n), never by counting
  7// entries. The per-campaign lists page by number, by offset in the campaign's own tree.
  8
  9const maxPageSize = 50
 10
 11// idKeyLen is the length of a campaign's key, seqid.ID.Binary().
 12const idKeyLen = 8
 13
 14// pageSize clamps a requested size to 1–50.
 15func pageSize(size int) int {
 16	if size < 1 {
 17		return 1
 18	}
 19	if size > maxPageSize {
 20		return maxPageSize
 21	}
 22	return size
 23}
 24
 25// ListCampaigns returns the open, listed campaigns, newest first, from below after (""
 26// for the newest), and the cursor to pass next, "" at the end; category may be "".
 27// A campaign past a deadline nobody recorded is left out (Q6). One call reads at most
 28// four times size entries, so a page can be short while the cursor isn't "".
 29func ListCampaigns(after string, size int, category string) ([]CampaignView, string) {
 30	size = pageSize(size)
 31	index, prefix := &active, ""
 32	if category != "" {
 33		index, prefix = &activeByCategory, category+"/"
 34	}
 35	return campaignPage(index, prefix, after, size, 4*size, func(c *campaign) bool {
 36		status, _, _ := effective(c)
 37		return status == statusOpen
 38	})
 39}
 40
 41// ListByOrganizer returns the campaigns addr organizes, delisted included, newest
 42// first, from below after, and the cursor to pass next. An address is compared as
 43// given (C12).
 44func ListByOrganizer(addr address, after string, size int) ([]CampaignView, string) {
 45	size = pageSize(size)
 46	return campaignPage(&byOrganizer, addr.String()+"/", after, size, size, everyCampaign)
 47}
 48
 49// ListByBeneficiary returns the campaigns naming addr as beneficiary, delisted included
 50// — the beneficiary must keep seeing them (INV-4) — newest first, from below after,
 51// and the cursor to pass next.
 52func ListByBeneficiary(addr address, after string, size int) ([]CampaignView, string) {
 53	size = pageSize(size)
 54	return campaignPage(&byBeneficiary, addr.String()+"/", after, size, size, everyCampaign)
 55}
 56
 57// ListByDonor returns the campaigns addr gave to at least once without anonymity,
 58// newest first, from below after, and the cursor to pass next: a campaign where all of
 59// addr's gifts were anonymous is in no index under addr.
 60func ListByDonor(addr address, after string, size int) ([]CampaignView, string) {
 61	size = pageSize(size)
 62	return campaignPage(&byDonor, addr.String()+"/", after, size, size, everyCampaign)
 63}
 64
 65func everyCampaign(*campaign) bool { return true }
 66
 67// campaignPage reads index newest first: the keys under prefix — a category's or an
 68// address's range, which ends at the "/" after it, or the whole index for "" — below
 69// after's key. It reads at most budget entries, keeps size views of those keep
 70// accepts, and returns them with the ID of the last entry read when more remain, ""
 71// when the range is done. A non-canonical after is ErrNotFound (C9).
 72func campaignPage(index *avl.Tree, prefix, after string, size, budget int, keep func(*campaign) bool) ([]CampaignView, string) {
 73	start, end := prefix, ""
 74	if prefix != "" {
 75		end = prefix[:len(prefix)-1] + "0" // "0" sorts right after "/"
 76	}
 77	cursor := ""
 78	if after != "" {
 79		id, ok := parseID(after)
 80		if !ok {
 81			panic(ErrNotFound)
 82		}
 83		cursor = prefix + id.Binary()
 84		end = cursor
 85	}
 86	views := []CampaignView{}
 87	last, read, more := "", 0, false
 88	// A reverse traversal includes its end: the cursor's own entry, read last time, is
 89	// skipped. A key whose ID part isn't a campaign key's eight bytes stops the reading:
 90	// only a prefix holding "/" — an address or a category crafted to end in a key's
 91	// first bytes — meets one, and every key in its range is such a key, so walking on
 92	// would read them all, past the budget, to find nothing.
 93	index.ReverseIterate(start, end, func(key string, _ any) bool {
 94		if key == cursor {
 95			return false
 96		}
 97		if len(key) != len(prefix)+idKeyLen {
 98			return true
 99		}
100		if len(views) == size || read == budget {
101			more = true
102			return true
103		}
104		read++
105		c := campaigns.Get(key[len(prefix):]).(*campaign)
106		last = idString(c.id)
107		if keep(c) {
108			views = append(views, campaignView(c))
109		}
110		return false
111	})
112	if !more {
113		return views, ""
114	}
115	return views, last
116}
117
118// pageOffset returns where page starts, newest first, in a tree of n entries paged by
119// size, and false past the end. A page counts from 1; below 1 reads as 1.
120func pageOffset(page, size, n int) (int, bool) {
121	if page < 1 {
122		page = 1
123	}
124	if page-1 > n/size {
125		return 0, false
126	}
127	return (page - 1) * size, true
128}
129
130// ListReceipts returns a page of the campaign's gifts, newest first.
131func ListReceipts(id string, page, size int) []ReceiptView {
132	c := campaignOf(id)
133	size = pageSize(size)
134	views := []ReceiptView{}
135	if offset, ok := pageOffset(page, size, c.receipts.Size()); ok {
136		c.receipts.ReverseIterateByOffset(offset, size, func(_ string, v any) bool {
137			views = append(views, receiptView(c, v.(*receipt)))
138			return false
139		})
140	}
141	return views
142}
143
144// WithdrawalView is one payout to the beneficiary (API §2.4): its tracked amount and
145// the coins sent outside Bolo it carried, apart.
146type WithdrawalView struct {
147	Seq, Amount, Untracked, At int64
148	To                         address
149}
150
151// ListWithdrawals returns a page of the campaign's withdrawals, newest first.
152func ListWithdrawals(id string, page, size int) []WithdrawalView {
153	c := campaignOf(id)
154	size = pageSize(size)
155	views := []WithdrawalView{}
156	if offset, ok := pageOffset(page, size, c.withdrawals.Size()); ok {
157		c.withdrawals.ReverseIterateByOffset(offset, size, func(_ string, v any) bool {
158			w := v.(*withdrawal)
159			views = append(views, WithdrawalView{Seq: int64(w.seq), Amount: w.amount, Untracked: w.untracked, At: w.at, To: w.to})
160			return false
161		})
162	}
163	return views
164}
165
166// RefundView is one payout to a donor (API §2.4). Donor is empty when all of that
167// donor's gifts to the campaign were anonymous; Kind is "open" or "exit".
168type RefundView struct {
169	Seq, Amount, At int64
170	Donor           address
171	Kind            string
172}
173
174// ListRefunds returns a page of the campaign's refunds, newest first.
175func ListRefunds(id string, page, size int) []RefundView {
176	c := campaignOf(id)
177	size = pageSize(size)
178	views := []RefundView{}
179	if offset, ok := pageOffset(page, size, c.refunds.Size()); ok {
180		c.refunds.ReverseIterateByOffset(offset, size, func(_ string, v any) bool {
181			r := v.(*refund)
182			view := RefundView{Seq: int64(r.seq), Amount: r.amount, At: r.at, Kind: r.kind}
183			if k := contributionOf(c, r.donor); k != nil && k.public {
184				view.Donor = r.donor
185			}
186			views = append(views, view)
187			return false
188		})
189	}
190	return views
191}
192
193// UpdateView is one organizer update (API §2.4).
194type UpdateView struct {
195	Seq, At int64
196	Body    string
197}
198
199// ListUpdates returns a page of the campaign's updates, newest first.
200func ListUpdates(id string, page, size int) []UpdateView {
201	c := campaignOf(id)
202	size = pageSize(size)
203	views := []UpdateView{}
204	if offset, ok := pageOffset(page, size, c.updates.Size()); ok {
205		c.updates.ReverseIterateByOffset(offset, size, func(_ string, v any) bool {
206			u := v.(*update)
207			views = append(views, UpdateView{Seq: int64(u.seq), At: u.at, Body: u.body})
208			return false
209		})
210	}
211	return views
212}