apps/blobs/handlers_browse.go 4.1 K raw
1
package main
2
3
import (
4
	"errors"
5
	"io"
6
	"net/http"
7
	"strconv"
8
	"strings"
9
)
10
11
func (a *App) listBuckets(w http.ResponseWriter, r *http.Request) {
12
	buckets, err := a.S3.ListBuckets(r.Context())
13
	data := bucketsPageData{Buckets: buckets}
14
	if err != nil {
15
		a.Log.Error("list buckets", "err", err)
16
		data.Error = err.Error()
17
	}
18
	a.renderPage(w, "buckets.html", data)
19
}
20
21
func (a *App) bucketRoot(w http.ResponseWriter, r *http.Request) {
22
	bucket := r.PathValue("bucket")
23
	http.Redirect(w, r, browseHref(bucket, ""), http.StatusSeeOther)
24
}
25
26
func (a *App) browse(w http.ResponseWriter, r *http.Request) {
27
	bucket := r.PathValue("bucket")
28
	prefix := r.PathValue("prefix")
29
	if prefix != "" && !strings.HasSuffix(prefix, "/") {
30
		http.Redirect(w, r, browseHref(bucket, prefix+"/"), http.StatusSeeOther)
31
		return
32
	}
33
34
	folders, files, err := a.S3.List(r.Context(), bucket, prefix)
35
	data := browsePageData{
36
		Bucket:      bucket,
37
		Prefix:      prefix,
38
		Crumbs:      buildCrumbs(bucket, prefix),
39
		Error:       r.URL.Query().Get("error"),
40
		Success:     r.URL.Query().Get("success"),
41
		MaxUploadMB: a.MaxUploadBytes >> 20,
42
	}
43
	if prefix != "" {
44
		data.ParentHref = browseHref(bucket, parentPrefix(prefix))
45
	}
46
	if err != nil {
47
		a.Log.Error("list", "bucket", bucket, "prefix", prefix, "err", err)
48
		if data.Error == "" {
49
			data.Error = err.Error()
50
		}
51
	}
52
	for _, f := range folders {
53
		name := strings.TrimSuffix(strings.TrimPrefix(f, prefix), "/")
54
		if name == "" {
55
			continue
56
		}
57
		data.Folders = append(data.Folders, folderItem{
58
			Name: name,
59
			Href: browseHref(bucket, f),
60
		})
61
	}
62
	for _, f := range files {
63
		// Skip the zero-byte "folder marker" key that matches the prefix exactly.
64
		if f.Name == "" {
65
			continue
66
		}
67
		img := isImageName(f.Name)
68
		item := fileItem{
69
			Name:         f.Name,
70
			Size:         f.Size,
71
			SizeHuman:    humanSize(f.Size),
72
			LastModified: f.LastModified,
73
			DetailHref:   objectHref(bucket, f.Key),
74
			IsImage:      img,
75
		}
76
		if img {
77
			item.PreviewSrc = previewHref(bucket, f.Key)
78
		}
79
		data.Files = append(data.Files, item)
80
	}
81
	a.renderPage(w, "browse.html", data)
82
}
83
84
func (a *App) detail(w http.ResponseWriter, r *http.Request) {
85
	bucket := r.PathValue("bucket")
86
	key := r.PathValue("key")
87
	if key == "" {
88
		http.NotFound(w, r)
89
		return
90
	}
91
	meta, err := a.S3.Head(r.Context(), bucket, key)
92
	if err != nil {
93
		a.Log.Error("head", "bucket", bucket, "key", key, "err", err)
94
		http.Error(w, "object not found", http.StatusNotFound)
95
		return
96
	}
97
	parent := parentOfKey(key)
98
	data := detailPageData{
99
		Bucket:       bucket,
100
		Key:          key,
101
		Name:         nameOfKey(key),
102
		ContentType:  meta.ContentType,
103
		Size:         meta.Size,
104
		SizeHuman:    humanSize(meta.Size),
105
		LastModified: meta.LastModified,
106
		ETag:         meta.ETag,
107
		IsImage:      isImageName(key) || strings.HasPrefix(meta.ContentType, "image/"),
108
		PreviewSrc:   previewHref(bucket, key),
109
		ParentHref:   browseHref(bucket, parent),
110
		ParentPrefix: parent,
111
		Crumbs:       buildCrumbs(bucket, parent),
112
		Error:        r.URL.Query().Get("error"),
113
	}
114
	if u, perr := a.S3.PresignGet(r.Context(), bucket, key); perr == nil {
115
		data.PresignedURL = u
116
	} else {
117
		a.Log.Warn("presign", "err", perr)
118
	}
119
	if u, ok := a.S3.PublicURL(bucket, key); ok {
120
		data.PublicURL = u
121
		data.HasPublic = true
122
	}
123
	a.renderPage(w, "detail.html", data)
124
}
125
126
func (a *App) preview(w http.ResponseWriter, r *http.Request) {
127
	bucket := r.PathValue("bucket")
128
	key := r.PathValue("key")
129
	if key == "" {
130
		http.NotFound(w, r)
131
		return
132
	}
133
	body, meta, err := a.S3.Get(r.Context(), bucket, key)
134
	if err != nil {
135
		a.Log.Error("get", "bucket", bucket, "key", key, "err", err)
136
		http.Error(w, "object not found", http.StatusNotFound)
137
		return
138
	}
139
	defer body.Close()
140
	if meta.ContentType != "" {
141
		w.Header().Set("Content-Type", meta.ContentType)
142
	}
143
	if meta.Size > 0 {
144
		w.Header().Set("Content-Length", strconv.FormatInt(meta.Size, 10))
145
	}
146
	if meta.ETag != "" {
147
		w.Header().Set("ETag", `"`+meta.ETag+`"`)
148
	}
149
	w.Header().Set("Cache-Control", "private, max-age=60")
150
	if _, err := io.Copy(w, body); err != nil && !errors.Is(err, http.ErrBodyReadAfterClose) {
151
		a.Log.Warn("preview copy", "err", err)
152
	}
153
}