// Copyright (C) 2022 Marius Schellenberger package srv import ( "bytes" "cmp" "encoding/xml" "html/template" "net/http" "slices" "strings" "cachefs/pkg/fs" ) type statusInterceptor struct { w http.ResponseWriter status int } func (s *statusInterceptor) Header() http.Header { return s.w.Header() } func (s *statusInterceptor) Write(p []byte) (int, error) { return s.w.Write(p) } func (s *statusInterceptor) WriteHeader(statusCode int) { s.w.WriteHeader(statusCode) s.status = statusCode } func (s *statusInterceptor) Status() int { if s.status == 0 { return http.StatusOK } return s.status } type dirContents struct { AllPaths []string `xml:"a"` Base string `xml:"-"` Dirs dirs `xml:"-"` Files files `xml:"-"` } type dir struct { Name template.HTML URI template.HTML Anchor string } type dirs []dir func (dirs) Comp(a, b dir) int { return cmp.Compare(a.Name, b.Name) } type file struct { Name template.HTML URI template.HTML Anchor string Size string Rate string Status int Prio int Errc int Running bool } type files []file func (files) Comp(a, b file) int { return cmp.Compare(a.Name, b.Name) } type responseInterceptor struct { buf bytes.Buffer w http.ResponseWriter status int } func (r *responseInterceptor) Header() http.Header { return r.w.Header() } func (r *responseInterceptor) Write(p []byte) (int, error) { return r.buf.Write(p) } func (r *responseInterceptor) WriteHeader(statusCode int) { r.status = statusCode } func (r *responseInterceptor) Status() int { if r.status == 0 { return http.StatusOK } return r.status } func xmldecode(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "'", "'") return s } func urlencode(s string) string { s = strings.ReplaceAll(s, "[", "%5B") s = strings.ReplaceAll(s, "]", "%5D") return s } func (r *responseInterceptor) GetPaths(path string, filesystem *fs.FS, relative bool) (dc dirContents, pa string, err error) { buf := r.buf.Bytes() buf = bytes.ReplaceAll(buf, []byte{'&'}, []byte("&")) err = xml.Unmarshal(buf, &dc) if err != nil { return } dc.Base = path path = strings.TrimSuffix(path, "/") if path != "/" && path != "" { pa = "#" + anchor(path) } for _, p := range dc.AllPaths { p = xmldecode(p) name := template.HTML(p) full := path + "/" + p uri := template.HTML(urlencode(full)) if relative { uri = template.HTML(urlencode(p)) } if p[len(p)-1] == '/' { dc.Dirs = append(dc.Dirs, dir{ Name: name, URI: uri, Anchor: pathAnchor(p), }) } else { dc.Files = append(dc.Files, file{ Name: name, URI: uri, Anchor: anchor(p), Size: filesystem.FileSize(full), Status: filesystem.CacheStatus(full), }) } } slices.SortFunc(dc.Dirs, dc.Dirs.Comp) slices.SortFunc(dc.Files, dc.Files.Comp) return } func getPreloads(path string, filesystem *fs.FS) (dc dirContents, size int64) { dc.Base = path for _, p := range filesystem.Preloads() { size += p.Size dc.Files = append(dc.Files, file{ Name: template.HTML(p.Name), URI: template.HTML(urlencode(p.Name)), Size: fs.FileSize(p.Size), Rate: p.Rate(), Status: p.Status, Prio: p.Prio, Errc: p.Errc, Running: p.Running, }) } return } func pathAnchor(s string) string { return anchor(strings.TrimSuffix(s, "/")) } func anchor(s string) string { if i := strings.LastIndex(s, "/"); i > 0 && i+1 < len(s) { s = s[i+1:] } s = strings.ReplaceAll(s, " ", "_") return strings.ToLower(s) }