Live video on the AT Protocol
1package api
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "regexp"
10 "strings"
11
12 "github.com/julienschmidt/httprouter"
13 apierrors "stream.place/streamplace/pkg/errors"
14 "stream.place/streamplace/pkg/log"
15)
16
17var (
18 re = regexp.MustCompile(`^streamplace(-desktop)?-(v[0-9]+\.[0-9]+\.[0-9]+)(-[0-9a-f]+)?-([0-9a-z]+)-([0-9a-z]+)\.(?:([0-9a-f]+)\.)?(.+)$`)
19 inputRe = regexp.MustCompile(`^streamplace(-desktop)?-([0-9a-z]+)-([0-9a-z]+)\.(.+)$`)
20)
21
22func queryGitlabReal(url string) (io.ReadCloser, error) {
23 req, err := http.Get(url)
24 if err != nil {
25 return nil, err
26 }
27 return req.Body, nil
28}
29
30var queryGitlab = queryGitlabReal
31
32func (a *StreamplaceAPI) HandleAppDownload(ctx context.Context) httprouter.Handle {
33 return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
34 log.Log(ctx, "got here")
35 pathname := r.URL.Path
36 parts := strings.Split(pathname, "/")
37 if len(parts) < 4 {
38 apierrors.WriteHTTPBadRequest(w, "usage: /dl/latest/streamplace-linux-arm64.tar.gz", nil)
39 return
40 }
41
42 _, branch, file := parts[1], parts[2], parts[3]
43 if branch == "" || file == "" {
44 apierrors.WriteHTTPBadRequest(w, "usage: /dl/latest/streamplace-linux-arm64.tar.gz", nil)
45 return
46 }
47
48 inputPieces := inputRe.FindStringSubmatch(file)
49 if inputPieces == nil {
50 apierrors.WriteHTTPBadRequest(w, fmt.Sprintf("could not parse filename %s", file), nil)
51 return
52 }
53
54 inputDesktop, inputPlatform, inputArch, inputExt := inputPieces[1], inputPieces[2], inputPieces[3], inputPieces[4]
55 files, err := a.getGitlabPackage(branch)
56 if err != nil {
57 apierrors.WriteHTTPBadRequest(w, fmt.Sprintf("could not get gitlab package %s", file), err)
58 return
59 }
60
61 var foundFile *GitlabFile
62 for _, f := range files {
63 if f.Desktop == inputDesktop && f.Platform == inputPlatform && f.Architecture == inputArch && f.Extension == inputExt {
64 foundFile = &f
65 break
66 }
67 }
68
69 if foundFile == nil {
70 apierrors.WriteHTTPNotFound(w, fmt.Sprintf("could not find a file for desktop=%s platform=%s arch=%s ext=%s", inputDesktop, inputPlatform, inputArch, inputExt), nil)
71 return
72 }
73
74 http.Redirect(w, r, foundFile.URL(), http.StatusTemporaryRedirect)
75 }
76}
77
78type GitlabFile struct {
79 GitLabURL string
80 Branch string
81 Filename string
82 Desktop string
83 Version string
84 Hash string
85 Platform string
86 Architecture string
87 SHA1 string
88 Extension string
89 Size int
90}
91
92func (f GitlabFile) FullVer() string {
93 return f.Version + f.Hash
94}
95
96func (f GitlabFile) URL() string {
97 return fmt.Sprintf("%s/packages/generic/%s/%s/%s", f.GitLabURL, f.Branch, f.FullVer(), f.Filename)
98}
99
100func (a *StreamplaceAPI) getGitlabPackage(branch string) ([]GitlabFile, error) {
101 packageURL := fmt.Sprintf("%s/packages?order_by=created_at&sort=desc&package_name=%s", a.CLI.GitLabURL, branch)
102
103 packageBody, err := queryGitlab(packageURL)
104 if err != nil {
105 return nil, fmt.Errorf("failed to fetch packages: %w", err)
106 }
107 defer packageBody.Close()
108
109 var packages []map[string]any
110 if err := json.NewDecoder(packageBody).Decode(&packages); err != nil {
111 return nil, fmt.Errorf("failed to decode package response: %w", err)
112 }
113 // bs, _ := json.Marshal(packages)
114 // fmt.Println(string(bs))
115
116 if len(packages) == 0 {
117 return nil, fmt.Errorf("package for branch %s not found", branch)
118 }
119
120 pkg := packages[0]
121 fileURL := fmt.Sprintf("%s/packages/%v/package_files", a.CLI.GitLabURL, pkg["id"])
122
123 fileBody, err := queryGitlab(fileURL)
124 if err != nil {
125 return nil, fmt.Errorf("failed to fetch files: %w", err)
126 }
127 defer fileBody.Close()
128
129 var files []map[string]any
130 if err := json.NewDecoder(fileBody).Decode(&files); err != nil {
131 return nil, fmt.Errorf("failed to decode file response: %w", err)
132 }
133
134 out := []GitlabFile{}
135 for _, f := range files {
136 filename, ok := f["file_name"].(string)
137 if !ok {
138 continue
139 }
140 pieces := re.FindStringSubmatch(filename)
141 if pieces == nil {
142 // log.Log(ctx, "could not parse filename %s", "filename", filename)
143 continue
144 }
145 size, ok := f["size"].(float64)
146 if !ok {
147 continue
148 }
149 desktop, ver, hash, platform, arch, sha1, ext := pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7]
150 out = append(out, GitlabFile{
151 GitLabURL: a.CLI.GitLabURL,
152 Branch: branch,
153 Filename: filename,
154 Desktop: desktop,
155 Version: ver,
156 Hash: hash,
157 Platform: platform,
158 Architecture: arch,
159 SHA1: sha1,
160 Extension: ext,
161 Size: int(size),
162 })
163 }
164 return out, nil
165}