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  }