Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nicer index #1406

Merged
merged 4 commits into from
Jul 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,54 @@ var initDocPaths = []string{
"init-doc/quick-start",
}

// SeedInitDocs adds the list of embedded init documentation to the passed node, pins it and returns the root key
func SeedInitDocs(nd *core.IpfsNode) (*key.Key, error) {
return addAssetList(nd, initDocPaths)
}

var initGwAssets = []string{
"gw-assets/icons.css",
"gw-assets/bootstrap.min.css",
}

// SeedGatewayAssets adds the list of embedded gateway inidex assets to the passed node, pins it and returns the root key
func SeedGatewayAssets(nd *core.IpfsNode) (*key.Key, error) {
return addAssetList(nd, initGwAssets)
}

func addAssetList(nd *core.IpfsNode, l []string) (*key.Key, error) {
dirb := uio.NewDirectory(nd.DAG)

for _, p := range initDocPaths {
for _, p := range l {
d, err := Asset(p)
if err != nil {
return nil, fmt.Errorf("assets.AddDocuDir: could load Asset '%s': %s", p, err)
return nil, fmt.Errorf("assets: could load Asset '%s': %s", p, err)
}

s, err := coreunix.Add(nd, bytes.NewBuffer(d))
if err != nil {
return nil, fmt.Errorf("assets.AddDocuDir: could not Add '%s': %s", p, err)
return nil, fmt.Errorf("assets: could not Add '%s': %s", p, err)
}

fname := filepath.Base(p)
k := key.B58KeyDecode(s)
if err := dirb.AddChild(fname, k); err != nil {
return nil, fmt.Errorf("assets.AddDocuDir: could not add '%s' as a child: %s", fname, err)
return nil, fmt.Errorf("assets: could not add '%s' as a child: %s", fname, err)
}
}

dir := dirb.GetNode()
dkey, err := nd.DAG.Add(dir)
if err != nil {
return nil, fmt.Errorf("assets.AddDocuDir: DAG.Add(dir) failed: %s", err)
return nil, fmt.Errorf("assets: DAG.Add(dir) failed: %s", err)
}

if err := nd.Pinning.Pin(nd.Context(), dir, true); err != nil {
return nil, fmt.Errorf("assets.AddDocuDir: Pinning on init-docu failed: %s", err)
return nil, fmt.Errorf("assets: Pinning on init-docu failed: %s", err)
}

if err := nd.Pinning.Flush(); err != nil {
return nil, fmt.Errorf("assets.AddDocuDir: Pinnig flush failed: %s", err)
return nil, fmt.Errorf("assets: Pinnig flush failed: %s", err)
}

return &dkey, nil
Expand Down
31 changes: 31 additions & 0 deletions assets/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,34 @@ func TestEmbeddedDocs(t *testing.T) {
}
wg.Wait()
}

func TestGatewayAssets(t *testing.T) {
const wantCnt = 2
if len(initGwAssets) < wantCnt {
t.Fatalf("expected %d assets. got %d", wantCnt, len(initDocPaths))
}

for _, f := range initGwAssets {
// load data from filesystem (git)
vcsData, err := ioutil.ReadFile(f)
if err != nil {
t.Errorf("asset %s: could not read vcs file: %s", f, err)
return
}

// load data from emdedded source
embdData, err := Asset(f)
if err != nil {
t.Errorf("asset %s: could not read vcs file: %s", f, err)
return
}

if !bytes.Equal(vcsData, embdData) {
t.Errorf("asset %s: vcs and embedded data isnt equal", f)
return
}

t.Logf("checked %s", f)
}

}
48 changes: 48 additions & 0 deletions assets/bindata.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions assets/gw-assets/bootstrap.min.css

Large diffs are not rendered by default.

384 changes: 384 additions & 0 deletions assets/gw-assets/icons.css

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions cmd/ipfs/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ func addDefaultAssets(out io.Writer, repoRoot string) error {
}
defer nd.Close()

gwAkey, err := assets.SeedGatewayAssets(nd)
if err != nil {
return fmt.Errorf("init: seeding init docs failed: %s", err)
}
log.Debugf("init: seeded gateway assets %s", gwAkey)

dkey, err := assets.SeedInitDocs(nd)
if err != nil {
return fmt.Errorf("init: seeding init docs failed: %s", err)
Expand Down
86 changes: 31 additions & 55 deletions core/corehttp/gateway_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package corehttp
import (
"errors"
"fmt"
"html/template"
"io"
"net/http"
gopath "path"
Expand All @@ -27,46 +26,21 @@ const (
ipnsPathPrefix = "/ipns/"
)

// shortcut for templating
type webHandler map[string]interface{}

// struct for directory listing
type directoryItem struct {
Size uint64
Name string
Path string
}

// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
type gatewayHandler struct {
node *core.IpfsNode
dirList *template.Template
config GatewayConfig
node *core.IpfsNode
config GatewayConfig
}

func newGatewayHandler(node *core.IpfsNode, conf GatewayConfig) (*gatewayHandler, error) {
i := &gatewayHandler{
node: node,
config: conf,
}
err := i.loadTemplate()
if err != nil {
return nil, err
}
return i, nil
}

// Load the directroy list template
func (i *gatewayHandler) loadTemplate() error {
t, err := template.New("dir").Parse(listingTemplate)
if err != nil {
return err
}
i.dirList = t
return nil
}

// TODO(cryptix): find these helpers somewhere else
func (i *gatewayHandler) newDagFromReader(r io.Reader) (*dag.Node, error) {
// TODO(cryptix): change and remove this helper once PR1136 is merged
Expand Down Expand Up @@ -205,14 +179,36 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
}

if !foundIndex {
// template and return directory listing
hndlr := webHandler{
"listing": dirListing,
"path": urlPath,
}

if r.Method != "HEAD" {
if err := i.dirList.Execute(w, hndlr); err != nil {
// construct the correct back link
// https://github.com/ipfs/go-ipfs/issues/1365
var backLink string = r.URL.Path

// don't go further up than /ipfs/$hash/
pathSplit := strings.Split(backLink, "/")
switch {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the default case here is doing something. It could be turned into a large if !(caseA) && !(caseB) {} but I disliked the readability.

// keep backlink
case len(pathSplit) == 3: // url: /ipfs/$hash

// keep backlink
case len(pathSplit) == 4 && pathSplit[3] == "": // url: /ipfs/$hash/

// add the correct link depending on wether the path ends with a slash
default:
if strings.HasSuffix(backLink, "/") {
backLink += "./.."
} else {
backLink += "/.."
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


tplData := listingTemplateData{
Listing: dirListing,
Path: urlPath,
BackLink: backLink,
}
err := listingTemplate.Execute(w, tplData)
if err != nil {
internalWebError(w, err)
return
}
Expand Down Expand Up @@ -441,23 +437,3 @@ func webErrorWithCode(w http.ResponseWriter, message string, err error, code int
func internalWebError(w http.ResponseWriter, err error) {
webErrorWithCode(w, "internalWebError", err, http.StatusInternalServerError)
}

// Directory listing template
var listingTemplate = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{{ .path }}</title>
</head>
<body>
<h2>Index of {{ .path }}</h2>
<ul>
<li><a href="./..">..</a></li>
{{ range .listing }}
<li><a href="{{ .Path }}">{{ .Name }}</a> - {{ .Size }} bytes</li>
{{ end }}
</ul>
</body>
</html>
`
160 changes: 160 additions & 0 deletions core/corehttp/gateway_indexPage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package corehttp

import (
"html/template"
"path"
)

// structs for directory listing
type listingTemplateData struct {
Listing []directoryItem
Path string
BackLink string
}

type directoryItem struct {
Size uint64
Name string
Path string
}

// Directory listing template
var listingTemplate = template.Must(template.New("dir").Funcs(template.FuncMap{"iconFromExt": iconFromExt}).Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- TODO: seed these - maybe like the starter ex or the webui? -->
<link rel="stylesheet" href="/ipfs/QmXB7PLRWH6bCiwrGh2MrBBjNkLv3mY3JdYXCikYZSwLED/bootstrap.min.css"/>
<!-- helper to construct this is here: https://github.com/cryptix/exp/blob/master/imgesToCSSData/convert.go -->
<link rel="stylesheet" href="/ipfs/QmXB7PLRWH6bCiwrGh2MrBBjNkLv3mY3JdYXCikYZSwLED/icons.css">
<style>
.narrow {width: 0px;}
.padding { margin: 100px;}
#header {
background: #000;
}
#logo {
height: 25px;
margin: 10px;
}
.ipfs-icon {
width:16px;
}
</style>
<title>{{ .Path }}</title>
</head>
<body>
<div id="header" class="row">
<div class="col-xs-2">
<div id="logo" class="ipfs-logo">&nbsp;</div>
</div>
</div>
<br/>
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Index of {{ .Path }}</strong>
</div>
<table class="table table-striped">
<tr>
<td class="narrow">
<div class="ipfs-icon ipfs-_blank">&nbsp;</div>
</td>
<td class="padding">
<a href="{{.BackLink}}">..</a>
</td>
<td></td>
</tr>
{{ range .Listing }}
<tr>
<td>
<div class="ipfs-icon {{iconFromExt .Name}}">&nbsp;</div>
</td>
<td>
<a href="{{ .Path }}">{{ .Name }}</a>
</td>
<td>{{ .Size }} bytes</td>
</tr>
{{ end }}
</table>
</div>
</div>
</body>
</html>
`))

// helper to guess the type/icon for it by the extension name
func iconFromExt(name string) string {
ext := path.Ext(name)
_, ok := knownIcons[ext]
if !ok {
// default blank icon
return "ipfs-_blank"
}
return "ipfs-" + ext[1:] // slice of the first dot
}

var knownIcons = map[string]bool{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure there is a nicer way to do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go generate too maybe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are the icons coming from a font?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should use font-awesome here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh you already put them all in the css 👍 wfm.

".aac": true,
".aiff": true,
".ai": true,
".avi": true,
".bmp": true,
".c": true,
".cpp": true,
".css": true,
".dat": true,
".dmg": true,
".doc": true,
".dotx": true,
".dwg": true,
".dxf": true,
".eps": true,
".exe": true,
".flv": true,
".gif": true,
".h": true,
".hpp": true,
".html": true,
".ics": true,
".iso": true,
".java": true,
".jpg": true,
".js": true,
".key": true,
".less": true,
".mid": true,
".mp3": true,
".mp4": true,
".mpg": true,
".odf": true,
".ods": true,
".odt": true,
".otp": true,
".ots": true,
".ott": true,
".pdf": true,
".php": true,
".png": true,
".ppt": true,
".psd": true,
".py": true,
".qt": true,
".rar": true,
".rb": true,
".rtf": true,
".sass": true,
".scss": true,
".sql": true,
".tga": true,
".tgz": true,
".tiff": true,
".txt": true,
".wav": true,
".xls": true,
".xlsx": true,
".xml": true,
".yml": true,
".zip": true,
}
Loading