From dd72a1e398d51c10042a5f5b63ccf34f51f8076c Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 6 Jul 2020 16:06:48 +0300 Subject: [PATCH] file-server example: use a custom template for listing dirs/files Former-commit-id: 5b9bb0be4ac3f5d463f0957a3074aa6e7b1a71f7 --- _examples/file-server/file-server/main.go | 185 +++++++++++++++++++++- _examples/view/template_jet_0/README.md | 5 +- aliases.go | 5 + core/router/api_builder.go | 5 +- core/router/fs.go | 104 +++++++----- 5 files changed, 249 insertions(+), 55 deletions(-) diff --git a/_examples/file-server/file-server/main.go b/_examples/file-server/file-server/main.go index c67881f2..e3501248 100644 --- a/_examples/file-server/file-server/main.go +++ b/_examples/file-server/file-server/main.go @@ -3,22 +3,31 @@ package main import ( "crypto/md5" "fmt" + "html/template" "io" "mime/multipart" "os" + "path" "strconv" "strings" "time" "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/middleware/basicauth" ) func init() { os.Mkdir("./uploads", 0700) } +const ( + maxSize = 1 * iris.GB + uploadDir = "./uploads" +) + func main() { app := iris.New() + app.RegisterView(iris.HTML("./views", ".html")) // Serve assets (e.g. javascript, css). // app.HandleDir("/public", "./public") @@ -28,11 +37,25 @@ func main() { app.Get("/upload", uploadView) app.Post("/upload", upload) - app.HandleDir("/files", "./uploads", iris.DirOptions{ - Gzip: true, - ShowList: true, - DirList: iris.DirListRich, - }) + filesRouter := app.Party("/files") + { + filesRouter.HandleDir("/", uploadDir, iris.DirOptions{ + Gzip: true, + ShowList: true, + DirList: iris.DirListRich(iris.DirListRichOptions{ + // Optionally, use a custom template for listing: + Tmpl: dirListRichTemplate, + }), + }) + + auth := basicauth.New(basicauth.Config{ + Users: map[string]string{ + "myusername": "mypassword", + }, + }) + + filesRouter.Delete("/{file:path}", auth, deleteFile) + } app.Listen(":8080") } @@ -50,12 +73,10 @@ func uploadView(ctx iris.Context) { ctx.View("upload.html", token) } -const maxSize = 1 * iris.GB - func upload(ctx iris.Context) { ctx.SetMaxRequestBodySize(maxSize) - _, err := ctx.UploadFormFiles("./uploads", beforeSave) + _, err := ctx.UploadFormFiles(uploadDir, beforeSave) if err != nil { ctx.StopWithError(iris.StatusPayloadTooRage, err) return @@ -71,3 +92,151 @@ func beforeSave(ctx iris.Context, file *multipart.FileHeader) { file.Filename = ip + "-" + file.Filename } + +func deleteFile(ctx iris.Context) { + // It does not contain the system path, + // as we are not exposing it to the user. + fileName := ctx.Params().Get("file") + + filePath := path.Join(uploadDir, fileName) + + if err := os.RemoveAll(filePath); err != nil { + ctx.StopWithError(iris.StatusInternalServerError, err) + return + } + + ctx.Redirect("/files") +} + +var dirListRichTemplate = template.Must(template.New("dirlist"). + 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(` + + + + + + + {{.Title}} + + + + + + + + + + + + + + {{ range $idx, $file := .Files }} + + + + {{ if $file.Info.IsDir }} + + {{ else }} + + {{ end }} + + + + {{ end }} + +
#NameSizeActions
{{ $idx }}{{ $file.Name }}Dir{{ formatBytes $file.Info.Size }}
+ + +`)) diff --git a/_examples/view/template_jet_0/README.md b/_examples/view/template_jet_0/README.md index 3957c5ce..627e5b3e 100644 --- a/_examples/view/template_jet_0/README.md +++ b/_examples/view/template_jet_0/README.md @@ -1,9 +1,8 @@ # Jet Engine Example -This example is a fork of to work with Iris, so you can -notice the differences side by side. +This example is a fork of to work with Iris, so you can notice the differences side by side. -Read more at: https://github.com/kataras/iris/issues/1281 +Read more at: https://github.com/CloudyKit/jet/blob/master/docs/syntax.md > The Iris Jet View Engine fixes some bugs that the underline [jet template parser](https://github.com/CloudyKit/jet) currently has. diff --git a/aliases.go b/aliases.go index 22a1098e..001053a7 100644 --- a/aliases.go +++ b/aliases.go @@ -98,6 +98,11 @@ type ( // `FileServer` and `Party#HandleDir` can use to serve files and assets. // A shortcut for the `router.DirOptions`, useful when `FileServer` or `HandleDir` is being used. DirOptions = router.DirOptions + // DirListRichOptions the options for the `DirListRich` helper function. + // The Tmpl's "dirlist" template will be executed. + // A shortcut for the `router.DirListRichOptions`. + // Useful when `DirListRich` function is passed to `DirOptions.DirList` field. + DirListRichOptions = router.DirListRichOptions // ExecutionRules gives control to the execution of the route handlers outside of the handlers themselves. // Usage: // Party#SetExecutionRules(ExecutionRules { diff --git a/core/router/api_builder.go b/core/router/api_builder.go index f50b7ca2..54a9532f 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -395,10 +395,7 @@ func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti stri // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptions) (routes []*Route) { options := getDirOptions(opts...) - // TODO(@kataras): Add option(s) to enable file & directory deletion (DELETE wildcard route) - // and integrade it with the new `DirListRich` helper - // (either context menu override on right-click of the file name or to a new table column) - // Note that, an auth middleware can be already registered, so no need to add options for that here. + h := FileServer(directory, options) description := directory fileName, lineNumber := context.HandlerFileLine(h) // take those before StripPrefix. diff --git a/core/router/fs.go b/core/router/fs.go index 8f89fa68..1fafb7d7 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -395,6 +395,7 @@ func FileServer(directory string, opts ...DirOptions) context.Handler { ctx.SetLastModified(info.ModTime()) err = dirList(ctx, info.Name(), f) if err != nil { + ctx.Application().Logger().Errorf("FileServer: dirList: %v", err) plainStatusCode(ctx, http.StatusInternalServerError) return } @@ -565,54 +566,72 @@ func DirectoryExists(dir string) bool { return true } +// DirListRichOptions the options for the `DirListRich` helper function. +// The Tmpl's "dirlist" template will be executed. +type DirListRichOptions struct { + Tmpl *template.Template +} + // 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 +func DirListRich(opts ...DirListRichOptions) DirListFunc { + var options DirListRichOptions + if len(opts) > 0 { + options = opts[0] } - 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()) }) + if options.Tmpl == nil { + options.Tmpl = DirListRichTemplate } - 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 += "/" + return func(ctx context.Context, dirName string, dir http.File) error { + dirs, err := dir.Readdir(-1) + if err != nil { + return err } - upath := "" - if ctx.Path() == "/" { - upath = ctx.GetCurrentRoute().StaticPath() + "/" + name - } else { - upath = "./" + dirName + "/" + name + 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()) }) } - url := url.URL{Path: upath} + pageData := listPageData{ + Title: fmt.Sprintf("List of %d files", len(dirs)), + Files: make([]fileInfoData, 0, len(dirs)), + } - pageData.Files = append(pageData.Files, fileInfoData{ - Info: d, - ModTime: d.ModTime().UTC().Format(http.TimeFormat), - Path: url.String(), - Name: html.EscapeString(name), - }) + 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(), + RelPath: path.Join(ctx.Path(), name), + Name: html.EscapeString(name), + }) + } + + return options.Tmpl.ExecuteTemplate(ctx, "dirlist", pageData) } - - return DirListRichTemplate.Execute(ctx, pageData) } type ( @@ -625,13 +644,14 @@ type ( Info os.FileInfo ModTime string // format-ed time. Path string // the request path. + RelPath string // file path without the system directory itself (we are not exposing it to the user). 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(""). +var DirListRichTemplate = template.Must(template.New("dirlist"). Funcs(template.FuncMap{ "formatBytes": func(b int64) string { const unit = 1000 @@ -721,18 +741,22 @@ var DirListRichTemplate = template.Must(template.New(""). # Name - Size + Size {{ range $idx, $file := .Files }} {{ $idx }} - {{ $file.Name }} - {{ formatBytes $file.Info.Size }} + {{ $file.Name }} + {{ if $file.Info.IsDir }} + Dir + {{ else }} + {{ formatBytes $file.Info.Size }} + {{ end }} {{ end }} - + `))