diff --git a/server_apps.go b/server_apps.go index 7f374c9ac6f51341990c67963627f09799351375..81b1325a243f9a7cd5287db4d0f36aaca9da1dd7 100644 --- a/server_apps.go +++ b/server_apps.go @@ -18,6 +18,7 @@ import ( "strings" "strconv" "time" + "math" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" @@ -27,6 +28,18 @@ import ( "github.com/pelletier/go-toml/v2" ) +type appPatch struct{ + repository string + zip []byte + targz []byte +} + +type appStatus struct{ + tested_version int + broken bool + patch *appPatch +} + type appRegistry struct { shame []*appShame apps []*appDescriptor @@ -58,6 +71,8 @@ type appInfo struct { targz []byte firstErr error + tags []string + status *appStatus } type GLProject struct { @@ -95,7 +110,7 @@ func (s *server) getStars(ctx context.Context, repo string) (int, error) { return project.StarCount, nil } -func (s *server) zipApp(ctx context.Context, name, pathInRepo, repo string, obj *object.Commit) ([]byte, error) { +func (s *server) zipApp(ctx context.Context, name, pathInRepo, repo string, patch_source *string, obj *object.Commit) ([]byte, error) { fi, err := obj.Files() if err != nil { return nil, fmt.Errorf("listing files: %w", err) @@ -134,6 +149,9 @@ func (s *server) zipApp(ctx context.Context, name, pathInRepo, repo string, obj if err != nil { return nil, fmt.Errorf("Blob.Reader: %w", err) } + if f.Name == "flow3r.toml" && patch_source != nil{ + fo.Write([]byte(fmt.Sprintf("patch_source = \"%s\"\n\n", *patch_source))) + } _, err = io.Copy(fo, rdr) rdr.Close() if err != nil { @@ -146,7 +164,7 @@ func (s *server) zipApp(ctx context.Context, name, pathInRepo, repo string, obj return buf.Bytes(), nil } -func (s *server) targzApp(ctx context.Context, name, pathInRepo, repo string, obj *object.Commit) ([]byte, error) { +func (s *server) targzApp(ctx context.Context, name, pathInRepo, repo string, patch_source * string, obj *object.Commit) ([]byte, error) { fi, err := obj.Files() if err != nil { return nil, fmt.Errorf("listing files: %w", err) @@ -210,10 +228,14 @@ func (s *server) targzApp(ctx context.Context, name, pathInRepo, repo string, ob } } } + patch_bytes := []byte("") + if f.Name == "flow3r.toml" && patch_source != nil{ + patch_bytes = []byte(fmt.Sprintf("patch_source = \"%s\"\n\n", *patch_source)) + } err = t.WriteHeader(&tar.Header{ Name: outPath, Typeflag: tar.TypeReg, - Size: f.Size, + Size: f.Size + int64(len(patch_bytes)), Mode: 0644, }) if err != nil { @@ -226,6 +248,12 @@ func (s *server) targzApp(ctx context.Context, name, pathInRepo, repo string, ob if err != nil { return nil, fmt.Errorf("Blob.Reader: %w", err) } + if len(patch_bytes) != 0{ + t.Write(patch_bytes) + } + if err != nil { + return nil, fmt.Errorf("patch_source: %w", err) + } _, err = io.Copy(t, rdr) rdr.Close() if err != nil { @@ -286,9 +314,10 @@ func (s *server) parseAppToml(ctx context.Context, pathInRepo string, obj *objec "Music": true, "Media": true, "Games": true, + "Demos": true, } if !sections[data.App.Category] { - return nil, fmt.Errorf("app category invalid (must be one of 'Badge', 'Apps', 'Music', 'Media', 'Games')") + return nil, fmt.Errorf("app category invalid (must be one of 'Badge', 'Apps', 'Music', 'Media', 'Games', 'Demos')") } if data.Entry.Class == "" { return nil, fmt.Errorf("no entry class") @@ -311,7 +340,7 @@ func (s *server) parseAppToml(ctx context.Context, pathInRepo string, obj *objec }, nil } -func (s *server) getAppInfo(ctx context.Context, pathInRepo, repo string) (*appInfo, error) { +func (s *server) getAppInfo(ctx context.Context, pathInRepo, repo string, slug *string) (*appInfo, error) { url := "https://git.flow3r.garden/" + repo g, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ URL: url, @@ -389,14 +418,21 @@ func (s *server) getAppInfo(ctx context.Context, pathInRepo, repo string) (*appI if err != nil { return nil, fmt.Errorf("getting gitlab stars failed: %w", err) } + var patch_source *string = nil + slug_str := repo + if slug != nil{ + slug_str = *slug + psrc := fmt.Sprintf("https://git.flow3r.garden/%s", repo) + patch_source = &psrc + } app := firstTime[highestVer] app.stars = stars - zbytes, err := s.zipApp(ctx, app.name, pathInRepo, repo, app.commitObj) + zbytes, err := s.zipApp(ctx, app.name, pathInRepo, slug_str, patch_source, app.commitObj) if err != nil { return nil, fmt.Errorf("zipping failed: %w", err) } app.zip = zbytes - tbytes, err := s.targzApp(ctx, app.name, pathInRepo, repo, app.commitObj) + tbytes, err := s.targzApp(ctx, app.name, pathInRepo, slug_str, patch_source, app.commitObj) if err != nil { return nil, fmt.Errorf("targzing failed: %w", err) } @@ -413,6 +449,7 @@ func (s *server) getAppInfo(ctx context.Context, pathInRepo, repo string) (*appI app.firstErr = firstErr + app.tags = make([]string, 0) return app, nil } @@ -446,6 +483,26 @@ func (s *server) getAppRegistry(ctx context.Context) (*appRegistry, error) { if err != nil { return nil, err } + flags, err := obj.File("tags.json") + if err != nil { + return nil, err + } + flagreader, err := flags.Reader() + if err != nil { + return nil, err + } + flagbytes, err := io.ReadAll(flagreader) + if err != nil { + return nil, err + } + + var flagmap map[string]interface{} + + err = json.Unmarshal([]byte(flagbytes), &flagmap) + if err != nil { + return nil, err + } + iter, err := obj.Files() if err != nil { return nil, err @@ -508,7 +565,7 @@ func (s *server) getAppRegistry(ctx context.Context) (*appRegistry, error) { } for _, app := range registry.apps { - info, err := s.getAppInfo(ctx, app.pathInRepo, app.repository) + info, err := s.getAppInfo(ctx, app.pathInRepo, app.repository, nil) if err != nil { log.Printf("App %q: %v", app.repository, err) registry.shame = append(registry.shame, &appShame{ @@ -522,9 +579,44 @@ func (s *server) getAppRegistry(ctx context.Context) (*appRegistry, error) { repository: app.repository, errorMsg: fmt.Sprintf("%v", info.firstErr), }) + } else if app_flags, ok := flagmap[app.repository].(map[string]interface{}); ok { + info.status = new(appStatus) + printed := false + if tags, ok := app_flags["tags"].([]interface{}); ok { + for _, tag := range tags{ + if tagtxt, ok := tag.(string); ok { + info.tags = append(info.tags, tagtxt) + } + } + } + if status, ok := app_flags["status"].(map[string]interface{}); ok { + if tested, ok := status["tested_version"].(float64); ok { + info.status.tested_version = int(math.Round(tested)) + } + if broken, ok := status["broken"].(bool); ok { + info.status.broken = broken + } + if patch, ok := status["patch"].(string); ok { + printed = true + patch_info, err := s.getAppInfo(ctx, "", patch, &app.repository) + if err != nil { + log.Printf("App %q: couldn't get patch at %q: %v", app.repository, patch, err) + } else { + log.Printf("App: %q: okay, patch found", app.repository) + info.status.patch = &appPatch{ + repository: patch, + zip: patch_info.zip, + targz: patch_info.targz, + } + } + } + } + if !printed{ + log.Printf("App: %q: okay", app.repository) + } } else { log.Printf("App: %q: okay", app.repository) - } + } app.appInfo = info } @@ -533,6 +625,18 @@ func (s *server) getAppRegistry(ctx context.Context) (*appRegistry, error) { return ®istry, nil } +type jsonAppPatch struct{ + RepoURL string `json:"repoUrl"` + DownloadURL string `json:"downloadUrl"` + TarDownloadURL string `json:"tarDownloadUrl"` +} + +type jsonAppStatus struct{ + TestedVersion int `json:"tested_version"` + Broken bool `json:"broken"` + Patch jsonAppPatch `json:"patch"` +} + type jsonApp struct { RepoURL string `json:"repoUrl"` Commit string `json:"commit"` @@ -543,13 +647,15 @@ type jsonApp struct { Author string `json:"author"` Description string `json:"description"` Version int `json:"version"` - Timestamp time.Time `json:"timestamp"` Stars int `json:"stars"` + Tags []string `json:"tags"` + Timestamp time.Time `json:"timestamp"` Flow3rSeed string `json:"flow3rSeed"` + Status *jsonAppStatus `json:"status"` } func makeJsonApp(a *appDescriptor) jsonApp { - return jsonApp{ + ret := jsonApp{ RepoURL: "https://git.flow3r.garden/" + a.repository, Commit: a.appInfo.commit, DownloadURL: fmt.Sprintf("%sapps/zip/%s.zip", flagBaseURL, a.repository), @@ -562,7 +668,23 @@ func makeJsonApp(a *appDescriptor) jsonApp { Timestamp: a.appInfo.commitObj.Committer.When.UTC(), Stars: a.appInfo.stars, Flow3rSeed: a.appInfo.flow3rSeed, - } + Tags: a.appInfo.tags, + } + if a.appInfo.status != nil{ + ret.Status = &jsonAppStatus{ + Broken: a.appInfo.status.broken, + TestedVersion: a.appInfo.status.tested_version, + } + if a.appInfo.status.patch != nil{ + pa := a.appInfo.status.patch.repository + ret.Status.Patch = jsonAppPatch{ + RepoURL: "https://git.flow3r.garden/" + pa, + DownloadURL: fmt.Sprintf("%sapps/zip/%s.zip", flagBaseURL, pa), + TarDownloadURL: fmt.Sprintf("%sapps/tar/%s.tar.gz", flagBaseURL, pa), + } + } + } + return ret } func (s *server) handleApps(w http.ResponseWriter, r *http.Request) { @@ -682,12 +804,22 @@ func (s *server) handleAppZip(w http.ResponseWriter, r *http.Request) { return } + repository := fmt.Sprintf("%s/%s", orga, repo) + for _, app := range apps { - if app.repository == fmt.Sprintf("%s/%s", orga, repo) { + if app.repository == repository { w.Header().Add("Content-Type", "application/zip") w.Write(app.appInfo.zip) return } + if app.appInfo == nil || app.appInfo.status == nil || app.appInfo.status.patch == nil { + continue + } + if app.appInfo.status.patch.repository == repository { + w.Header().Add("Content-Type", "application/zip") + w.Write(app.appInfo.status.patch.zip) + return + } } http.NotFound(w, r) @@ -703,6 +835,7 @@ func (s *server) handleAppTargz(w http.ResponseWriter, r *http.Request) { } orga := matches[1] repo := matches[2] + repository := fmt.Sprintf("%s/%s", orga, repo) apps, err := s.getApps(ctx) if err != nil { @@ -710,12 +843,21 @@ func (s *server) handleAppTargz(w http.ResponseWriter, r *http.Request) { } for _, app := range apps { - if app.repository == fmt.Sprintf("%s/%s", orga, repo) { + if app.repository == repository { w.Header().Add("Content-Type", "application/x-gzip") w.Header().Add("Content-Length", strconv.Itoa(len(app.appInfo.targz))) w.Write(app.appInfo.targz) return } + if app.appInfo == nil || app.appInfo.status == nil || app.appInfo.status.patch == nil { + continue + } + if app.appInfo.status.patch.repository == repository { + w.Header().Add("Content-Type", "application/x-gzip") + w.Header().Add("Content-Length", strconv.Itoa(len(app.appInfo.status.patch.targz))) + w.Write(app.appInfo.status.patch.targz) + return + } } http.NotFound(w, r)