ServeHTTP streams a zip archive of all the files "under" zh.root. That is, all the files pointed by file permanodes, which are directly members of zh.root or recursively down directory permanodes and permanodes members. To build the fullpath of a file in a collection, it uses the collection title if
(rw http.ResponseWriter, req *http.Request)
| 225 | // the collection title if present, its blobRef otherwise, as |
| 226 | // a directory name. |
| 227 | func (zh *zipHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { |
| 228 | // TODO: use http.ServeContent, so Range requests work and downloads can be resumed. |
| 229 | // Will require calculating the zip length once first (ideally as cheaply as possible, |
| 230 | // with dummy counting writer and dummy all-zero-byte-files of a fixed size), |
| 231 | // and then making a dummy ReadSeeker for ServeContent that can seek to the end, |
| 232 | // and then seek back to the beginning, but then seeks forward make it remember |
| 233 | // to skip that many bytes from the archive/zip writer when answering Reads. |
| 234 | if !httputil.IsGet(req) { |
| 235 | http.Error(rw, "Invalid method", http.StatusMethodNotAllowed) |
| 236 | return |
| 237 | } |
| 238 | bf, err := zh.blobList("", zh.root) |
| 239 | if err != nil { |
| 240 | log.Printf("Could not serve zip for %v: %v", zh.root, err) |
| 241 | http.Error(rw, "Server error", http.StatusInternalServerError) |
| 242 | return |
| 243 | } |
| 244 | blobFiles := renameDuplicates(bf) |
| 245 | |
| 246 | h := rw.Header() |
| 247 | h.Set("Content-Type", "application/zip") |
| 248 | filename := zh.filename |
| 249 | if filename == "" { |
| 250 | filename = "download.zip" |
| 251 | } |
| 252 | h.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename})) |
| 253 | zw := zip.NewWriter(rw) |
| 254 | etag := sha1.New() |
| 255 | for _, file := range blobFiles { |
| 256 | etag.Write([]byte(file.blobRef.String())) |
| 257 | } |
| 258 | h.Set("Etag", fmt.Sprintf(`"%x"`, etag.Sum(nil))) |
| 259 | |
| 260 | for _, file := range blobFiles { |
| 261 | fr, err := schema.NewFileReader(context.TODO(), zh.fetcher, file.blobRef) |
| 262 | if err != nil { |
| 263 | log.Printf("Can not add %v in zip, not a file: %v", file.blobRef, err) |
| 264 | http.Error(rw, "Server error", http.StatusInternalServerError) |
| 265 | return |
| 266 | } |
| 267 | zh := zip.FileHeader{ |
| 268 | Name: file.path, |
| 269 | Method: zip.Store, |
| 270 | Modified: fr.ModTime().UTC(), |
| 271 | } |
| 272 | f, err := zw.CreateHeader(&zh) |
| 273 | if err != nil { |
| 274 | log.Printf("Could not create %q in zip: %v", file.path, err) |
| 275 | http.Error(rw, "Server error", http.StatusInternalServerError) |
| 276 | return |
| 277 | } |
| 278 | _, err = io.Copy(f, fr) |
| 279 | fr.Close() |
| 280 | if err != nil { |
| 281 | log.Printf("Could not zip %q: %v", file.path, err) |
| 282 | return |
| 283 | } |
| 284 | } |
no test coverage detected