aboutsummaryrefslogtreecommitdiff
blob: d4f87527655438d7d1ebfdef5dded08ab948a86c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
// miscellaneous utility functions used for packages

package packages

import (
	"crypto/md5"
	"encoding/hex"
	"github.com/go-pg/pg"
	"github.com/go-pg/pg/v9/orm"
	"html/template"
	"net/http"
	"soko/pkg/app/utils"
	"soko/pkg/database"
	"soko/pkg/logger"
	"soko/pkg/models"
	utils2 "soko/pkg/utils"
	"sort"
	"strings"
	textTemplate "text/template"
)

// GetAddedPackages returns a list of recently added
// packages containing a given number of packages
func GetAddedPackages(n int) []*models.Package {
	var addedPackages []*models.Package
	err := database.DBCon.Model(&addedPackages).
		Order("preceding_commits DESC").
		Limit(n).
		Relation("Versions").
		Relation("Versions.Commits").
		Select()
	if err != nil && err != pg.ErrNoRows {
		logger.Error.Println("Error during fetching added packages from database")
		logger.Error.Println(err)
	}
	return addedPackages
}

// GetAddedVersions returns a list of recently added
// versions containing a given number of versions
func GetAddedVersions(n int) []*models.Version {
	addedPackages := GetAddedPackages(n)
	var addedVersions []*models.Version
	for _, addedPackage := range addedPackages {
		addedVersions = append(addedVersions, addedPackage.Versions...)
	}
	return addedVersions
}

// GetUpdatedVersions returns a list of recently updated
// versions containing a given number of versions
func GetUpdatedVersions(n int) []*models.Version {
	var updatedVersions []*models.Version
	var updates []models.Commit
	err := database.DBCon.Model(&updates).
		Order("preceding_commits DESC").
		Limit(n).
		Relation("ChangedVersions", func(q *orm.Query) (*orm.Query, error) {
			return q.Limit(10 * n), nil
		}).
		Relation("ChangedVersions.Commits", func(q *orm.Query) (*orm.Query, error) {
			return q.Order("preceding_commits DESC"), nil
		}).
		Select()
	if err != nil {
		return updatedVersions
	}
	for _, commit := range updates {
		for _, changedVersion := range commit.ChangedVersions {
			changedVersion.Commits = changedVersion.Commits[:1]
		}
		updatedVersions = append(updatedVersions, commit.ChangedVersions...)
	}
	if len(updatedVersions) > n {
		updatedVersions = updatedVersions[:10]
	}
	return updatedVersions
}

// GetStabilizedVersions returns a list of recently stabilized
// versions containing a given number of versions
func GetStabilizedVersions(n int) []*models.Version {
	var stabilizedVersions []*models.Version
	var updates []models.KeywordChange
	err := database.DBCon.Model(&updates).
		Relation("Version").
		Relation("Commit").
		Order("commit.preceding_commits DESC").
		Where("stabilized IS NOT NULL").
		Limit(n).
		Select()
	if err != nil {
		return stabilizedVersions
	}
	for _, update := range updates {
		if update.Version != nil {
			update.Version.Commits = []*models.Commit{update.Commit}
			stabilizedVersions = append(stabilizedVersions, update.Version)
		}
	}
	return stabilizedVersions
}

// GetKeywordedVersions returns a list of recently keyworded
// versions containing a given number of versions
func GetKeywordedVersions(n int) []*models.Version {
	var keywordedVersions []*models.Version
	var updates []models.KeywordChange
	err := database.DBCon.Model(&updates).
		Relation("Version").
		Relation("Commit").
		Order("commit.preceding_commits DESC").
		Where("added IS NOT NULL").
		Limit(n).
		Select()
	if err != nil {
		return keywordedVersions
	}
	for _, update := range updates {
		if update.Version != nil {
			update.Version.Commits = []*models.Commit{update.Commit}
			keywordedVersions = append(keywordedVersions, update.Version)
		}
	}
	return keywordedVersions
}

// RenderPackageTemplates renders the given templates using the given data
// One pattern can be used to specify templates
func renderPackageTemplate(page string, templatepattern string, funcMap template.FuncMap, data interface{}, w http.ResponseWriter) {
	templates := template.Must(
		template.Must(
			template.Must(
				template.New(page).
					Funcs(funcMap).
					ParseGlob("web/templates/layout/*.tmpl")).
				ParseGlob("web/templates/packages/components/*.tmpl")).
			ParseGlob("web/templates/packages/" + templatepattern + ".tmpl"))
	templates.ExecuteTemplate(w, page+".tmpl", data)
}

// RenderPackageTemplates renders the given templates using the given data
// Two patterns can be used to specify templates
func RenderPackageTemplates(page string, templatepattern1 string, templatepattern2 string, funcMap template.FuncMap, data interface{}, w http.ResponseWriter) {
	templates := template.Must(
		template.Must(
			template.Must(
				template.Must(
					template.New(page).
						Funcs(funcMap).
						ParseGlob("web/templates/layout/*.tmpl")).
					ParseGlob("web/templates/packages/browsepackagesheader.tmpl")).
				ParseGlob("web/templates/packages/" + templatepattern1 + ".tmpl")).
			ParseGlob("web/templates/packages/" + templatepattern2 + ".tmpl"))
	templates.ExecuteTemplate(w, page+".tmpl", data)
}

// getAtom returns the atom of the package from the given url
func getAtom(r *http.Request) string {
	atom := r.URL.Path[len("/packages/"):]
	atom = strings.Replace(atom, "/changelog", "", 1)
	atom = strings.Replace(atom, ".html", "", 1)
	atom = strings.Replace(atom, ".json", "", 1)
	return atom
}

// getSearchData returns the data used in search templates
func getSearchData(packages []models.Package, search string) interface{} {
	return struct {
		Header      models.Header
		Search      string
		Packages    []models.Package
		Application models.Application
	}{
		Header:      models.Header{Title: search + " – ", Tab: "packages"},
		Search:      search,
		Packages:    packages,
		Application: utils.GetApplicationData(),
	}
}

// getChangelogData returns the data used in changelog templates
func getChangelogData(commits []*models.Commit, atom string) interface{} {
	return struct {
		Commits []*models.Commit
		Atom    string
	}{
		Commits: commits,
		Atom:    atom,
	}
}

// GetFuncMap returns the FuncMap used in templates
func GetFuncMap() template.FuncMap {
	return template.FuncMap{
		"contains":          strings.Contains,
		"replaceall":        strings.ReplaceAll,
		"gravatar":          gravatar,
		"mkSlice":           mkSlice,
		"getReverse":        getReverse,
		"tolower":           strings.ToLower,
		"formatRestricts":   FormatRestricts,
		"isMasked":          isMasked,
		"getMask":           getMask,
		"showRemovalNotice": showRemovalNotice,
		"add": func(a, b int) int {
			return a + b
		},
	}
}

// GetFuncMap returns the FuncMap used in templates
func GetTextFuncMap() textTemplate.FuncMap {
	return textTemplate.FuncMap{
		"contains":          strings.Contains,
		"replaceall":        strings.ReplaceAll,
		"gravatar":          gravatar,
		"mkSlice":           mkSlice,
		"getReverse":        getReverse,
		"tolower":           strings.ToLower,
		"formatRestricts":   FormatRestricts,
		"isMasked":          isMasked,
		"getMask":           getMask,
		"showRemovalNotice": showRemovalNotice,
	}
}

// gravatar creates a link to the gravatar
// based on the email
func gravatar(email string) string {
	hasher := md5.Sum([]byte(email))
	hash := hex.EncodeToString(hasher[:])
	return "https://www.gravatar.com/avatar/" + hash + "?s=13&d=retro"
}

// mkSlice creates a slice of the given arguments
func mkSlice(args ...interface{}) []interface{} {
	return args
}

// getReverse returns the element of a slice in
// reverse direction based on the index
func getReverse(index int, versions []*models.Version) *models.Version {
	return versions[len(versions)-1-index]
}

// getParameterValue returns the value of a given parameter
func getParameterValue(parameterName string, r *http.Request) string {
	results, ok := r.URL.Query()[parameterName]
	if !ok {
		return ""
	}
	if len(results) == 0 {
		return ""
	}
	return results[0]
}

// getPackageUseflags retrieves all local USE flags, global USE
// flags and use expands for a given package
func getPackageUseflags(gpackage *models.Package) ([]models.Useflag, []models.Useflag, map[string][]models.Useflag) {
	var localUseflags, allGlobalUseflags, filteredGlobalUseflags []models.Useflag
	useExpands := make(map[string][]models.Useflag)
	for _, rawUseflag := range gpackage.Versions[0].Useflags {

		var tmp_useflags []models.Useflag
		err := database.DBCon.Model(&tmp_useflags).
			Where("Name = ?", strings.Replace(rawUseflag, "+", "", 1)).
			Select()

		if err != nil && err != pg.ErrNoRows {
			logger.Error.Println("Error during fetching added packages from database")
			logger.Error.Println(err)
			continue
		}

		for _, useflag := range tmp_useflags {
			if useflag.Scope == "global" {
				allGlobalUseflags = append(allGlobalUseflags, useflag)
			} else if useflag.Scope == "local" {
				if useflag.Package == gpackage.Atom {
					localUseflags = append(localUseflags, useflag)
				}
			} else {
				if _, ok := useExpands[useflag.UseExpand]; !ok {
					useExpands[useflag.UseExpand] = []models.Useflag{useflag}
				} else {
					useExpands[useflag.UseExpand] = append(useExpands[useflag.UseExpand], useflag)
				}
			}
		}
	}

	// Only add global useflags that are not present in the local useflags
	for _, useflag := range allGlobalUseflags {
		if !containsUseflag(useflag, localUseflags) {
			filteredGlobalUseflags = append(filteredGlobalUseflags, useflag)
		}
	}

	return localUseflags, filteredGlobalUseflags, useExpands
}

// createPackageData creates the data used in the show package template
func createPackageData(pageName string, gpackage *models.Package, localUseflags []models.Useflag, globalUseflags []models.Useflag, useExpands map[string][]models.Useflag) interface{} {
	return struct {
		PageName       string
		Header         models.Header
		Package        models.Package
		Versions       []*models.Version
		Masks          []models.Mask
		LocalUseflags  []models.Useflag
		GlobalUseflags []models.Useflag
		UseExpands     map[string][]models.Useflag
		Application    models.Application
	}{
		PageName:       pageName,
		Header:         models.Header{Title: gpackage.Atom + " – ", Tab: "packages"},
		Package:        *gpackage,
		Versions:       gpackage.Versions,
		LocalUseflags:  localUseflags,
		GlobalUseflags: globalUseflags,
		UseExpands:     useExpands,
		Masks:          nil,
		Application:    utils.GetApplicationData(),
	}
}

// CreateFeedData creates the data used in changedVersions template
func CreateFeedData(name string, versions []*models.Version) interface{} {
	return struct {
		Header      models.Header
		Name        string
		Versions    []*models.Version
		Application models.Application
	}{
		Header:      models.Header{Title: "Packages – ", Tab: "packages"},
		Name:        name,
		Versions:    versions,
		Application: utils.GetApplicationData(),
	}
}

// FormatRestricts returns a string containing a comma separated
// list of capitalized first letters of the package restricts
func FormatRestricts(restricts []string) string {
	var result []string
	for _, restrict := range restricts {
		if restrict != "" && restrict != "(" && restrict != ")" && !strings.HasSuffix(restrict, "?") {
			result = append(result, strings.ToUpper(string(restrict[0])))
		}
	}
	result = utils2.Deduplicate(result)
	return strings.Join(result, ", ")
}

// isMasked returns true if any version is masked
func isMasked(versions []*models.Version) bool {
	for _, version := range versions {
		if len(version.Masks) > 0 {
			return true
		}
	}
	return false
}

// getMask returns the mask entry of the first version that is masked
func getMask(versions []*models.Version) *models.Mask {
	for _, version := range versions {
		if len(version.Masks) > 0 {
			return version.Masks[0]
		}
	}
	return nil
}

// showRemovalNotice if all versions of the package are masked
func showRemovalNotice(versions []*models.Version) bool {
	showNotice := false
	for _, version := range versions {
		if len(version.Masks) > 0 && version.Masks[0].Versions == version.Atom {
			showNotice = true
		}
	}
	return showNotice
}

// sort the versions in ascending order
func sortVersionsAsc(versions []*models.Version) {
	sort.Slice(versions, func(i, j int) bool {
		return versions[i].SmallerThan(*versions[j])
	})
}

// sort the versions in descending order
func sortVersionsDesc(versions []*models.Version) {
	sort.Slice(versions, func(i, j int) bool {
		return versions[i].GreaterThan(*versions[j])
	})
}

// containsUseflag returns true if the given list of useflags contains the
// given userflag. Otherwise false will be returned.
func containsUseflag(useflag models.Useflag, useflags []models.Useflag) bool {
	for _, flag := range useflags {
		if useflag.Name == flag.Name {
			return true
		}
	}
	return false
}