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

json.gno

7.40 Kb · 232 lines
  1package catalog
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"gno.land/p/nym-alexiscolin000/gnoradio/store/v0"
  8	"gno.land/p/nym-alexiscolin000/gnoradio/text/v0"
  9)
 10
 11// JSON exports for the GnoRadio app and indexers (vm/qeval). Their shapes
 12// are the app's contract (app/src/lib): change them together.
 13
 14func ints(xs []int) string {
 15	var b strings.Builder
 16	b.WriteString("[")
 17	for i, x := range xs {
 18		if i > 0 {
 19			b.WriteString(",")
 20		}
 21		b.WriteString(text.Itoa(x))
 22	}
 23	b.WriteString("]")
 24	return b.String()
 25}
 26
 27// The JSON is written from the records' fields as stored: numbers are
 28// already decimal strings, and converting them costs gas.
 29
 30// trackJSON writes track id from its record and its artist's head.
 31func trackJSON(id int, rec, arec string) string {
 32	f := store.Fields(rec)
 33	out := `{"id":` + text.Itoa(id) + `,"artist":` + f[fArtist] + `,"artistName":"` + store.Field(arec, aName) +
 34		`","claimed":` + strconv.FormatBool(store.Field(arec, aOwner) != "") + `,"origin":"` + f[fOrigin] + `","title":"` + f[fTitle] +
 35		`","genre":` + f[fGenre] + `,"duration":` + f[fDuration] + `,"license":"` + f[fLicense] +
 36		`","cmo":"` + f[fCMO] + `","credits":"` + f[fCredits] + `","audio":"` + f[fAudio] + `","audioSha256":"` + f[fAudioSHA] +
 37		`","cover":"` + f[fCover] + `","coverSha256":"` + f[fCoverSHA] + `","source":"` + f[fSource] +
 38		`","attribution":"` + f[fAttribution] + `","album":` + f[fAlbum] + `,"created":` + f[fCreated] +
 39		`,"likes":` + f[fLikes] + `,"tips":` + f[fTips] + `,"supporters":` + f[fSupporters] + `,"splits":[`
 40	if f[fSplits] != "" {
 41		for i, p := range strings.Split(f[fSplits], ",") {
 42			to, pct, _ := strings.Cut(p, ":")
 43			if i > 0 {
 44				out += ","
 45			}
 46			out += `{"to":"` + to + `","pct":` + pct + `}`
 47		}
 48	}
 49	return out + `]}`
 50}
 51
 52// playableRec returns a track's record and its artist's head when both are
 53// visible ("" otherwise).
 54func playableRec(t *tx, id int) (rec, arec string) {
 55	rec = t.rec(cTracks, id)
 56	if rec == "" || store.Field(rec, fHidden) != "" {
 57		return "", ""
 58	}
 59	arec = t.rec(cArtists, text.Atoi(store.Field(rec, fArtist)))
 60	if arec == "" || store.Field(arec, aHidden) != "" {
 61		return "", ""
 62	}
 63	return rec, arec
 64}
 65
 66// TrackJSON returns one playable track.
 67func TrackJSON(id int) string {
 68	rec, arec := playableRec(rd(), id)
 69	if rec == "" {
 70		panic("catalog: unknown track")
 71	}
 72	return trackJSON(id, rec, arec)
 73}
 74
 75// TracksJSON pages through playable tracks (newest first), at most 100.
 76// Chunks and artist heads are read once per page.
 77func TracksJSON(offset, limit int) string {
 78	limit = store.Limit(limit, maxBatch)
 79	t := rd()
 80	total := int(t.count(mTracks))
 81	var b strings.Builder
 82	b.WriteString(`{"total":` + text.Itoa(total) + `,"tracks":[`)
 83	n := 0
 84	for _, id := range seqPage(total, offset, limit, true) {
 85		if rec, arec := playableRec(t, id); rec != "" {
 86			if n > 0 {
 87				b.WriteString(",")
 88			}
 89			b.WriteString(trackJSON(id, rec, arec))
 90			n++
 91		}
 92	}
 93	b.WriteString(`]}`)
 94	return b.String()
 95}
 96
 97func ArtistJSON(id int) string {
 98	t := rd()
 99	arec := t.rec(cArtists, id)
100	if arec == "" || store.Field(arec, aHidden) != "" {
101		panic("catalog: unknown artist")
102	}
103	return artistJSON(t, id, arec)
104}
105
106func artistJSON(t *tx, id int, arec string) string {
107	f := store.Fields(arec)
108	trackIDs, albumIDs := lists(t, id)
109	promo, ok := t.get(cPromo, store.Pad(id))
110	if !ok {
111		promo = text.Itoa(defaultPromoPct)
112	}
113	a := &Artist{ID: id, Hidden: f[aHidden] != "", Owner: address(f[aOwner]), Verified: f[aVerified] != ""}
114	return `{"id":` + text.Itoa(id) + `,"name":` + text.Str(f[aName]) + `,"kind":` + text.Str(f[aKind]) + `,"owner":` + text.Str(f[aOwner]) +
115		`,"bio":` + text.Str(f[aBio]) + `,"source":` + text.Str(f[aSource]) + `,"joined":` + f[aJoined] + `,"tips":` + f[aTips] +
116		`,"followers":` + f[aFollowers] + `,"verified":` + strconv.FormatBool(a.Verified) +
117		`,"proofHost":` + text.Str(proofHost(t, id, f[aVerified])) + `,"promo":` + promo +
118		`,"sponsor":` + text.Itoa64(offer(t, a)) + `,"tracks":` + store.JSON(trackIDs) + `,"albums":` + store.JSON(albumIDs) + `}`
119}
120
121// albumJSON writes a visible album ("" when it or its artist is hidden).
122func albumJSON(t *tx, id int) string {
123	rec := t.rec(cAlbums, id)
124	if rec == "" {
125		return ""
126	}
127	f := store.Fields(rec)
128	if f[6] != "" || !artistVisible(t, text.Atoi(f[0])) {
129		return ""
130	}
131	return `{"id":` + text.Itoa(id) + `,"artist":` + f[0] + `,"title":` + text.Str(f[1]) + `,"cover":` + text.Str(f[2]) +
132		`,"coverSha256":` + text.Str(f[3]) + `,"year":` + f[4] + `,"tracks":` + store.JSON(f[7]) + `}`
133}
134
135func AlbumJSON(id int) string {
136	s := albumJSON(rd(), id)
137	if s == "" {
138		panic("catalog: unknown album")
139	}
140	return s
141}
142
143// playlistJSON writes a visible playlist ("" when hidden or unknown).
144func playlistJSON(t *tx, id int) string {
145	rec := ""
146	if id > 0 {
147		rec = t.val(cPlaylists, store.Pad(id))
148	}
149	if rec == "" || store.Field(rec, 3) != "" {
150		return ""
151	}
152	f := store.Fields(rec)
153	return `{"id":` + text.Itoa(id) + `,"owner":` + text.Str(f[0]) + `,"title":` + text.Str(f[1]) +
154		`,"updated":` + f[2] + `,"tracks":` + store.JSON(f[4]) + `}`
155}
156
157func PlaylistJSON(id int) string {
158	s := playlistJSON(rd(), id)
159	if s == "" {
160		panic("catalog: unknown playlist")
161	}
162	return s
163}
164
165// UserJSON returns a listener's public counters (likes are paged with LikesJSON).
166func UserJSON(addr address) string {
167	likes, follows, tipped, lists := UserStats(addr)
168	return `{"address":` + text.Str(addr.String()) + `,"likes":` + text.Itoa(likes) + `,"follows":` + text.Itoa(follows) +
169		`,"tipped":` + text.Itoa64(tipped) + `,"playlists":` + ints(lists) + `,"artist":` + text.Itoa(ArtistOf(addr)) + `}`
170}
171
172func LikesJSON(addr address, offset, limit int) string { return ints(LikedPage(addr, offset, limit)) }
173
174func GenresJSON() string {
175	var b strings.Builder
176	b.WriteString("[")
177	for g := 1; g <= NumGenres; g++ {
178		if g > 1 {
179			b.WriteString(",")
180		}
181		b.WriteString(`{"id":` + text.Itoa(g) + `,"name":` + text.Str(genres[g]) + `}`)
182	}
183	b.WriteString("]")
184	return b.String()
185}
186
187// maxBatch caps the batched readers, so one query stays far below the qeval budget.
188const maxBatch = 100
189
190// batch writes {"total":n,"<key>":[...]} for ids offset+1..offset+limit, skipping
191// hidden items; one query replaces up to 100 single reads.
192func batch(key string, total, offset, limit int, one func(id int) string) string {
193	limit = store.Limit(limit, maxBatch)
194	offset = max0(offset)
195	var b strings.Builder
196	b.WriteString(`{"total":` + text.Itoa(total) + `,"` + key + `":[`)
197	n := 0
198	for id := offset + 1; id <= total && id <= offset+limit; id++ {
199		if s := one(id); s != "" {
200			if n > 0 {
201				b.WriteString(",")
202			}
203			b.WriteString(s)
204			n++
205		}
206	}
207	b.WriteString(`]}`)
208	return b.String()
209}
210
211// ArtistsJSON returns visible artists with ids offset+1..offset+limit (at most 100).
212func ArtistsJSON(offset, limit int) string {
213	t := rd()
214	return batch("artists", int(t.count(mArtists)), offset, limit, func(id int) string {
215		if arec := t.rec(cArtists, id); arec != "" && store.Field(arec, aHidden) == "" {
216			return artistJSON(t, id, arec)
217		}
218		return ""
219	})
220}
221
222// AlbumsJSON returns visible albums with ids offset+1..offset+limit (at most 100).
223func AlbumsJSON(offset, limit int) string {
224	t := rd()
225	return batch("albums", int(t.count(mAlbums)), offset, limit, func(id int) string { return albumJSON(t, id) })
226}
227
228// PlaylistsJSON returns visible playlists with ids offset+1..offset+limit (at most 100).
229func PlaylistsJSON(offset, limit int) string {
230	t := rd()
231	return batch("playlists", int(t.count(mPlaylists)), offset, limit, func(id int) string { return playlistJSON(t, id) })
232}