add a DirListRich helper to make the file listing a bit more appealing than its default

Former-commit-id: 1d8338cddac0856be1c9f1e7b6d8d400bee71bef
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-07-05 23:27:32 +03:00
parent bdfe8de66d
commit 16a6372cc9
3 changed files with 186 additions and 16 deletions

View File

@ -31,6 +31,7 @@ func main() {
app.HandleDir("/files", "./uploads", iris.DirOptions{ app.HandleDir("/files", "./uploads", iris.DirOptions{
Gzip: true, Gzip: true,
ShowList: true, ShowList: true,
DirList: iris.DirListRich,
}) })
app.Listen(":8080") app.Listen(":8080")
@ -49,7 +50,7 @@ func uploadView(ctx iris.Context) {
ctx.View("upload.html", token) ctx.View("upload.html", token)
} }
const maxSize = 10 * iris.MB const maxSize = 1 * iris.GB
func upload(ctx iris.Context) { func upload(ctx iris.Context) {
ctx.SetMaxRequestBodySize(maxSize) ctx.SetMaxRequestBodySize(maxSize)

View File

@ -3,6 +3,8 @@ package router
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"html"
"html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -19,6 +21,9 @@ import (
const indexName = "/index.html" const indexName = "/index.html"
// DirListFunc is the function signature for customizing directory and file listing.
type DirListFunc func(ctx context.Context, dirName string, dir http.File) error
// DirOptions contains the optional settings that // DirOptions contains the optional settings that
// `FileServer` and `Party#HandleDir` can use to serve files and assets. // `FileServer` and `Party#HandleDir` can use to serve files and assets.
type DirOptions struct { type DirOptions struct {
@ -33,7 +38,7 @@ type DirOptions struct {
// List the files inside the current requested directory if `IndexName` not found. // List the files inside the current requested directory if `IndexName` not found.
ShowList bool ShowList bool
// If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir). // If `ShowList` is true then this function will be used instead of the default one to show the list of files of a current requested directory(dir).
DirList func(ctx context.Context, dirName string, dir http.File) error DirList DirListFunc
// When embedded. // When embedded.
Asset func(name string) ([]byte, error) // we need this to make it compatible os.File. Asset func(name string) ([]byte, error) // we need this to make it compatible os.File.
@ -283,16 +288,6 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
ctx.StatusCode(statusCode) ctx.StatusCode(statusCode)
} }
htmlReplacer := strings.NewReplacer(
"&", "&",
"<", "&lt;",
">", "&gt;",
// "&#34;" is shorter than "&quot;".
`"`, "&#34;",
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
"'", "&#39;",
)
dirList := options.DirList dirList := options.DirList
if dirList == nil { if dirList == nil {
dirList = func(ctx context.Context, dirName string, dir http.File) error { dirList = func(ctx context.Context, dirName string, dir http.File) error {
@ -301,9 +296,6 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
return err return err
} }
// dst, _ := dir.Stat()
// dirName := dst.Name()
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
ctx.ContentType(context.ContentHTMLHeaderValue) ctx.ContentType(context.ContentHTMLHeaderValue)
@ -332,7 +324,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
// name may contain '?' or '#', which must be escaped to remain // name may contain '?' or '#', which must be escaped to remain
// part of the URL path, and not indicate the start of a query // part of the URL path, and not indicate the start of a query
// string or fragment. // string or fragment.
_, err = ctx.Writef("<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name)) _, err = ctx.Writef("<a href=\"%s\">%s</a>\n", url.String(), html.EscapeString(name))
if err != nil { if err != nil {
return err return err
} }
@ -403,6 +395,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler {
ctx.SetLastModified(info.ModTime()) ctx.SetLastModified(info.ModTime())
err = dirList(ctx, info.Name(), f) err = dirList(ctx, info.Name(), f)
if err != nil { if err != nil {
println(err.Error())
plainStatusCode(ctx, http.StatusInternalServerError) plainStatusCode(ctx, http.StatusInternalServerError)
return return
} }
@ -572,3 +565,175 @@ func DirectoryExists(dir string) bool {
} }
return true return true
} }
// DirListRich is a `DirListFunc` which can be passed to `DirOptions.DirList` field
// to override the default file listing appearance.
// See `DirListRichTemplate` to modify the template, if necessary.
func DirListRich(ctx context.Context, dirName string, dir http.File) error {
dirs, err := dir.Readdir(-1)
if err != nil {
return err
}
sortBy := ctx.URLParam("sort")
switch sortBy {
case "name":
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
case "size":
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Size() < dirs[j].Size() })
default:
sort.Slice(dirs, func(i, j int) bool { return dirs[i].ModTime().After(dirs[j].ModTime()) })
}
pageData := listPageData{
Title: fmt.Sprintf("List of %d files", len(dirs)),
Files: make([]fileInfoData, 0, len(dirs)),
}
for _, d := range dirs {
name := d.Name()
if d.IsDir() {
name += "/"
}
upath := ""
if ctx.Path() == "/" {
upath = ctx.GetCurrentRoute().StaticPath() + "/" + name
} else {
upath = "./" + dirName + "/" + name
}
url := url.URL{Path: upath}
pageData.Files = append(pageData.Files, fileInfoData{
Info: d,
ModTime: d.ModTime().UTC().Format(http.TimeFormat),
Path: url.String(),
Name: html.EscapeString(name),
})
}
return DirListRichTemplate.Execute(ctx, pageData)
}
type (
listPageData struct {
Title string // the document's title.
Files []fileInfoData
}
fileInfoData struct {
Info os.FileInfo
ModTime string // format-ed time.
Path string // the request path.
Name string // the html-escaped name.
}
)
// DirListRichTemplate is the html template the `DirListRich` function is using to render
// the directories and files.
var DirListRichTemplate = template.Must(template.New("").
Funcs(template.FuncMap{
"formatBytes": func(b int64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB",
float64(b)/float64(div), "kMGTPE"[exp])
},
}).Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
<style>
a {
padding: 8px 8px;
text-decoration:none;
cursor:pointer;
color: #10a2ff;
}
table {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
border: 1px solid #cbcbcb;
}
table caption {
color: #000;
font: italic 85%/1 arial, sans-serif;
padding: 1em 0;
text-align: center;
}
table td,
table th {
border-left: 1px solid #cbcbcb;
border-width: 0 0 0 1px;
font-size: inherit;
margin: 0;
overflow: visible;
padding: 0.5em 1em;
}
table thead {
background-color: #10a2ff;
color: #fff;
text-align: left;
vertical-align: bottom;
}
table td {
background-color: transparent;
}
.table-odd td {
background-color: #f2f2f2;
}
.table-bordered td {
border-bottom: 1px solid #cbcbcb;
}
.table-bordered tbody > tr:last-child > td {
border-bottom-width: 0;
}
</style>
</head>
<body>
<table class="table-bordered table-odd">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Size</th>
</tr>
</thead>
<tbody>
{{ range $idx, $file := .Files }}
<tr>
<td>{{ $idx }}</td>
<td><a href="{{ $file.Path }}" title="{{ $file.ModTime }}">{{ $file.Name }}</a></td>
<td>{{ formatBytes $file.Info.Size }}</td>
</tr>
{{ end }}
</tbody>
</table>
</body></html>
`))

View File

@ -439,6 +439,10 @@ var (
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server
// A shortcut for the `router.FileServer`. // A shortcut for the `router.FileServer`.
FileServer = router.FileServer FileServer = router.FileServer
// DirListRich can be passed to `DirOptions.DirList` field
// to override the default file listing appearance.
// Read more at: `core/router.DirListRich`.
DirListRich = router.DirListRich
// StripPrefix returns a handler that serves HTTP requests // StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path // by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a // and invoking the handler h. StripPrefix handles a