github.com/coreos/mantle@v0.13.0/storage/index/indexer.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package index 16 17 import ( 18 "bytes" 19 "html/template" 20 "net/url" 21 "path" 22 "strings" 23 24 "golang.org/x/net/context" 25 gs "google.golang.org/api/storage/v1" 26 27 "github.com/coreos/mantle/storage" 28 ) 29 30 var ( 31 indexTemplate *template.Template 32 ) 33 34 const ( 35 indexText = `<html> 36 <head> 37 <title>{{.Title}}</title> 38 <meta http-equiv="X-Clacks-Overhead" content="GNU Terry Pratchett" /> 39 </head> 40 <body> 41 <h1>{{.Title}}</h1> 42 {{range .SubDirs}} 43 [dir] <a href="{{.|base}}/">{{.|base}}</a> <br/> 44 {{end}} 45 {{range .Objects}} 46 [file] <a href="{{.Name|base}}">{{.Name|base}}</a> <br/> 47 {{end}} 48 </body> 49 </html> 50 ` 51 ) 52 53 func init() { 54 indexTemplate = template.New("index") 55 indexTemplate.Funcs(template.FuncMap{"base": path.Base}) 56 template.Must(indexTemplate.Parse(indexText)) 57 } 58 59 type Indexer struct { 60 bucket *storage.Bucket 61 prefix string 62 empty bool 63 Title string 64 SubDirs []string 65 Objects []*gs.Object 66 } 67 68 func (t *IndexTree) Indexer(name, prefix string) *Indexer { 69 return &Indexer{ 70 bucket: t.bucket, 71 prefix: prefix, 72 empty: !t.prefixes[prefix], 73 Title: name + "/" + prefix, 74 SubDirs: t.subdirs[prefix], 75 Objects: t.objects[prefix], 76 } 77 } 78 79 func (i *Indexer) Empty() bool { 80 return i.empty 81 } 82 83 func (i *Indexer) maybeDelete(ctx context.Context, name string) error { 84 if name == "" || i.bucket.Object(name) == nil { 85 return nil 86 } 87 return i.bucket.Delete(ctx, name) 88 } 89 90 func (i *Indexer) DeleteRedirect(ctx context.Context) error { 91 return i.maybeDelete(ctx, strings.TrimSuffix(i.prefix, "/")) 92 } 93 94 func (i *Indexer) DeleteDirectory(ctx context.Context) error { 95 return i.maybeDelete(ctx, i.prefix) 96 } 97 98 func (i *Indexer) DeleteIndexHTML(ctx context.Context) error { 99 return i.maybeDelete(ctx, i.prefix+"index.html") 100 } 101 102 func (i *Indexer) UpdateRedirect(ctx context.Context) error { 103 if i.prefix == "" { 104 return nil 105 } 106 107 name := strings.TrimSuffix(i.prefix, "/") 108 obj := gs.Object{ 109 Name: name, 110 ContentType: "text/html", 111 CacheControl: "public, max-age=60", 112 } 113 114 link := escapePath(path.Base(name)) 115 buf := bytes.NewBuffer(make([]byte, 0, 256)) 116 buf.WriteString("<html><head>\n") 117 // TODO: include <link rel="canonical" href="d.Prefix"/> 118 // I suspect that's only meaningful if we switch to absolute paths 119 buf.WriteString(`<meta http-equiv="refresh" content="0;url=`) 120 buf.WriteString(link) 121 buf.WriteString("/\">\n</head></html>\n") 122 123 return i.bucket.Upload(ctx, &obj, bytes.NewReader(buf.Bytes())) 124 } 125 126 func (i *Indexer) updateHTML(ctx context.Context, suffix string) error { 127 obj := gs.Object{ 128 Name: i.prefix + suffix, 129 ContentType: "text/html", 130 CacheControl: "public, max-age=60", 131 } 132 133 buf := bytes.Buffer{} 134 if err := indexTemplate.Execute(&buf, i); err != nil { 135 return err 136 } 137 138 return i.bucket.Upload(ctx, &obj, bytes.NewReader(buf.Bytes())) 139 } 140 141 func (i *Indexer) UpdateDirectoryHTML(ctx context.Context) error { 142 if i.prefix == "" { 143 return nil 144 } 145 146 return i.updateHTML(ctx, "") 147 } 148 149 func (i *Indexer) UpdateIndexHTML(ctx context.Context) error { 150 return i.updateHTML(ctx, "index.html") 151 } 152 153 func escapePath(path string) string { 154 u := url.URL{Path: path} 155 return u.EscapedPath() 156 }