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}}
+
+
+
+
+
+
+`))
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 }}
-
+