1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17 package handlers
18
19 import (
20 "context"
21 "fmt"
22 "io"
23 "log"
24 "net/http"
25 "strconv"
26 "time"
27
28 "perkeep.org/pkg/blob"
29 "perkeep.org/pkg/blobserver"
30 )
31
32 const defaultMaxEnumerate = 10000
33 const defaultEnumerateSize = 100
34
35 func CreateEnumerateHandler(storage blobserver.BlobEnumerator) http.Handler {
36 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
37 handleEnumerateBlobs(rw, req, storage)
38 })
39 }
40
41 const errMsgMaxWaitSecWithAfter = "Can't use 'maxwaitsec' with 'after'.\n"
42
43 func handleEnumerateBlobs(rw http.ResponseWriter, req *http.Request, storage blobserver.BlobEnumerator) {
44
45 formValueLimit := req.FormValue("limit")
46 formValueMaxWaitSec := req.FormValue("maxwaitsec")
47 formValueAfter := req.FormValue("after")
48
49 maxEnumerate := defaultMaxEnumerate
50 if config, ok := storage.(blobserver.MaxEnumerateConfig); ok {
51 maxEnumerate = config.MaxEnumerate()
52 }
53
54 limit := defaultEnumerateSize
55 if formValueLimit != "" {
56 n, err := strconv.ParseUint(formValueLimit, 10, 32)
57 if err != nil || n > uint64(maxEnumerate) {
58 limit = maxEnumerate
59 } else {
60 limit = int(n)
61 }
62 }
63
64 waitSeconds := 0
65 if formValueMaxWaitSec != "" {
66 waitSeconds, _ = strconv.Atoi(formValueMaxWaitSec)
67 if waitSeconds != 0 && formValueAfter != "" {
68 rw.WriteHeader(http.StatusBadRequest)
69 fmt.Fprintf(rw, errMsgMaxWaitSecWithAfter)
70 return
71 }
72 switch {
73 case waitSeconds < 0:
74 waitSeconds = 0
75 case waitSeconds > 30:
76
77
78
79 waitSeconds = 30
80 }
81 }
82
83 rw.Header().Set("Content-Type", "text/javascript; charset=utf-8")
84 io.WriteString(rw, "{\n \"blobs\": [\n")
85
86 loop := true
87 needsComma := false
88 deadline := time.Now().Add(time.Duration(waitSeconds) * time.Second)
89 after := ""
90 for loop && (waitSeconds == 0 || time.Now().After(deadline)) {
91 if waitSeconds == 0 {
92 loop = false
93 }
94
95 blobch := make(chan blob.SizedRef, 100)
96 resultch := make(chan error, 1)
97 go func() {
98 resultch <- storage.EnumerateBlobs(context.TODO(), blobch, formValueAfter, limit)
99 }()
100
101 gotBlobs := 0
102 for sb := range blobch {
103 gotBlobs++
104 loop = false
105 blobName := sb.Ref.String()
106 if needsComma {
107 io.WriteString(rw, ",\n")
108 }
109 fmt.Fprintf(rw, " {\"blobRef\": \"%s\", \"size\": %d}",
110 blobName, sb.Size)
111 after = blobName
112 needsComma = true
113 }
114 if gotBlobs < limit {
115 after = ""
116 }
117 if err := <-resultch; err != nil {
118 log.Printf("Error during enumerate: %v", err)
119 fmt.Fprintf(rw, "{{{ SERVER ERROR }}}")
120 return
121 }
122
123 if loop {
124 blobserver.WaitForBlob(storage, deadline, nil)
125 }
126 }
127 io.WriteString(rw, "\n ]")
128 if after != "" {
129 fmt.Fprintf(rw, ",\n \"continueAfter\": \"%s\"", after)
130 }
131 const longPollSupported = true
132 if longPollSupported {
133 io.WriteString(rw, ",\n \"canLongPoll\": true")
134 }
135 io.WriteString(rw, "\n}\n")
136 }