Monorepo for Tangled tangled.org
6

Configure Feed

Select the types of activity you want to include in your feed.

appview: update appview repo_language cache when loading index page

Updating only on `refUpdate` event is unstable.

Signed-off-by: Seongmin Lee <git@boltless.me>

author
Seongmin Lee
committer
Tangled
date (May 29, 2026, 9:00 PM +0300) commit 540acdea parent 12598efb change-id pqkrxrnt
+47 -73
-53
appview/db/language.go
··· 10 10 "tangled.org/core/orm" 11 11 ) 12 12 13 - func GetRepoLanguages(e Execer, filters ...orm.Filter) ([]models.RepoLanguage, error) { 14 - var conditions []string 15 - var args []any 16 - for _, filter := range filters { 17 - conditions = append(conditions, filter.Condition()) 18 - args = append(args, filter.Arg()...) 19 - } 20 - 21 - whereClause := "" 22 - if conditions != nil { 23 - whereClause = " where " + strings.Join(conditions, " and ") 24 - } 25 - 26 - query := fmt.Sprintf( 27 - `select id, repo_did, ref, is_default_ref, language, bytes from repo_languages %s`, 28 - whereClause, 29 - ) 30 - rows, err := e.Query(query, args...) 31 - if err != nil { 32 - return nil, fmt.Errorf("failed to execute query: %w ", err) 33 - } 34 - defer rows.Close() 35 - 36 - var langs []models.RepoLanguage 37 - for rows.Next() { 38 - var rl models.RepoLanguage 39 - var isDefaultRef int 40 - 41 - err := rows.Scan( 42 - &rl.Id, 43 - &rl.RepoDid, 44 - &rl.Ref, 45 - &isDefaultRef, 46 - &rl.Language, 47 - &rl.Bytes, 48 - ) 49 - if err != nil { 50 - return nil, fmt.Errorf("failed to scan: %w ", err) 51 - } 52 - 53 - if isDefaultRef != 0 { 54 - rl.IsDefaultRef = true 55 - } 56 - 57 - langs = append(langs, rl) 58 - } 59 - if err = rows.Err(); err != nil { 60 - return nil, fmt.Errorf("failed to scan rows: %w ", err) 61 - } 62 - 63 - return langs, nil 64 - } 65 - 66 13 func InsertRepoLanguages(e Execer, langs []models.RepoLanguage) error { 67 14 stmt, err := e.Prepare( 68 15 "insert or replace into repo_languages (repo_did, ref, is_default_ref, language, bytes) values (?, ?, ?, ?, ?)",
+45 -19
appview/repo/index.go
··· 114 114 115 115 var languageInfo []types.RepoLanguageDetails 116 116 if !result.IsEmpty { 117 - languageInfo, err = rp.getLanguageInfo(r.Context(), syntax.DID(f.RepoDid), result.Ref) 117 + langs, err := rp.getLanguageInfo(r.Context(), syntax.DID(f.RepoDid), result.Ref) 118 118 if err != nil { 119 119 l.Warn("failed to compute language percentages", "err", err) 120 120 // non-fatal 121 + } else if ref == "" { // when request didn't specified ref, we are fetching default branch. 122 + if err := func(repo syntax.DID, ref string, langs []*tangled.GitTempListLanguages_Language) error { 123 + tx, err := rp.db.Begin() 124 + if err != nil { 125 + return err 126 + } 127 + defer tx.Rollback() 128 + 129 + var mlangs []models.RepoLanguage 130 + for _, lang := range langs { 131 + mlangs = append(mlangs, models.RepoLanguage{ 132 + RepoDid: repo, 133 + Ref: ref, 134 + IsDefaultRef: true, 135 + Language: lang.Name, 136 + Bytes: lang.Size, 137 + }) 138 + } 139 + 140 + if err := db.UpdateRepoLanguages(tx, syntax.DID(f.RepoDid), ref, mlangs); err != nil { 141 + return err 142 + } 143 + 144 + return tx.Commit() 145 + }(syntax.DID(f.RepoDid), result.Ref, langs); err != nil { 146 + l.Error("failed to populate appview repo languages index", "err", err) 147 + // non-fatal 148 + } 149 + languageInfo = makeLanguageStats(langs) 121 150 } 122 151 } 123 152 ··· 151 180 ctx context.Context, 152 181 repoId syntax.DID, 153 182 ref string, 154 - ) ([]types.RepoLanguageDetails, error) { 155 - var langs []models.RepoLanguage 183 + ) ([]*tangled.GitTempListLanguages_Language, error) { 156 184 // non-fatal, fetch langs from knotmirror via XRPC 157 185 xrpcc := &indigoxrpc.Client{ 158 186 Host: rp.config.KnotMirror.Url, 159 187 Client: http.DefaultClient, 160 188 } 161 - ls, err := tangled.GitTempListLanguages(ctx, xrpcc, ref, repoId.String()) 189 + out, err := tangled.GitTempListLanguages(ctx, xrpcc, ref, repoId.String()) 162 190 if err != nil { 163 191 return nil, fmt.Errorf("calling knotmirror git.listLanguages: %w", err) 164 192 } 165 193 166 - if ls == nil || ls.Languages == nil { 194 + if out == nil || out.Languages == nil { 167 195 return nil, nil 168 196 } 169 197 198 + return out.Languages, nil 199 + } 200 + 201 + func makeLanguageStats(langs []*tangled.GitTempListLanguages_Language) []types.RepoLanguageDetails { 202 + if len(langs) == 0 { 203 + return nil 204 + } 170 205 var total int64 171 - for _, lang := range ls.Languages { 206 + for _, lang := range langs { 172 207 total += lang.Size 173 - langs = append(langs, models.RepoLanguage{ 174 - RepoDid: repoId, 175 - Ref: ref, 176 - Language: lang.Name, 177 - Bytes: lang.Size, 178 - }) 179 208 } 180 209 181 210 var languageStats []types.RepoLanguageDetails 182 211 for _, l := range langs { 183 - percentage := float32(l.Bytes) / float32(total) * 100 184 - color := enry.GetColor(l.Language) 185 212 languageStats = append(languageStats, types.RepoLanguageDetails{ 186 - Name: l.Language, 187 - Percentage: percentage, 188 - Color: color, 213 + Name: l.Name, 214 + Color: enry.GetColor(l.Name), 215 + Percentage: float32(l.Size) / float32(total) * 100, 189 216 }) 190 217 } 191 218 ··· 201 228 } 202 229 return languageStats[i].Name < languageStats[j].Name 203 230 }) 204 - 205 - return languageStats, nil 231 + return languageStats 206 232 } 207 233 208 234 // buildIndexResponse creates a RepoIndexResponse by combining multiple xrpc calls in parallel
+2 -1
appview/repo/opengraph.go
··· 22 22 23 23 avatarUrl := rp.pages.AvatarUrl(f.Did, "256") 24 24 25 - languageStats, err := rp.getLanguageInfo(r.Context(), syntax.DID(f.RepoDid), "") 25 + langs, err := rp.getLanguageInfo(r.Context(), syntax.DID(f.RepoDid), "") 26 26 if err != nil { 27 27 log.Printf("failed to get language stats from knotmirror: %v", err) 28 28 } 29 + languageStats := makeLanguageStats(langs) 29 30 30 31 ogLanguages := []ogre.LanguageData{} 31 32 for _, lang := range languageStats {