sakuin.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "github.com/dustin/go-humanize"
  6. "html/template"
  7. "io/ioutil"
  8. "log"
  9. "net/http"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. )
  14. type fileItem struct {
  15. Name string
  16. Size string
  17. Date string
  18. IsDir bool
  19. Path string
  20. }
  21. type templateVariables struct {
  22. Path []breadcrumb
  23. Files []fileItem
  24. }
  25. type breadcrumb struct {
  26. Name string
  27. Path string
  28. }
  29. var dataDir string
  30. func main() {
  31. dirArg := flag.String("dir", ".", "Path to data dir exposed")
  32. portArg := flag.Int("port", 3000, "Port bound by Sakuin")
  33. flag.Parse()
  34. port := fmt.Sprintf(":%d", *portArg)
  35. tmpDir, err := filepath.Abs(*dirArg)
  36. if err != nil {
  37. log.Println(err)
  38. return
  39. }
  40. dataDir = tmpDir
  41. log.Println(fmt.Sprintf("Sakuin will now expose %s", dataDir))
  42. fs := http.FileServer(http.Dir("assets/static"))
  43. http.Handle("/static/", http.StripPrefix("/static/", fs))
  44. http.HandleFunc("/", serve)
  45. log.Println(fmt.Sprintf("Listening on port %s...", port))
  46. http.ListenAndServe(port, nil)
  47. }
  48. func serve(w http.ResponseWriter, r *http.Request) {
  49. lp := filepath.Join("assets/templates", "layout.html")
  50. // Filepath, from the root data dir
  51. fp := filepath.Join(dataDir, filepath.Clean(r.URL.Path))
  52. // Cleaned filepath, without the root data dir, used for template rendering purpose
  53. cfp := strings.Replace(fp, dataDir, "", 1)
  54. // Return a 404 if the template doesn't exist
  55. info, err := os.Stat(fp)
  56. if err != nil {
  57. if os.IsNotExist(err) {
  58. http.NotFound(w, r)
  59. log.Println(fmt.Sprintf("404 - %s", cfp))
  60. return
  61. }
  62. }
  63. // Return a 404 if the request is for a directory
  64. if info.IsDir() {
  65. files, err := ioutil.ReadDir(fp)
  66. if err != nil {
  67. log.Fatal(err)
  68. }
  69. // Init template variables
  70. tplVars := templateVariables{}
  71. // Construct the breadcrumb
  72. path := strings.Split(cfp, "/")
  73. for len(path) > 0 {
  74. b := breadcrumb{
  75. Name: path[len(path)-1],
  76. Path: strings.Join(path, "/"),
  77. }
  78. path = path[:len(path)-1]
  79. tplVars.Path = append(tplVars.Path, b)
  80. }
  81. // Since the breadcrumb built is not very ordered...
  82. // REVERSE ALL THE THINGS
  83. for left, right := 0, len(tplVars.Path)-1; left < right; left, right = left+1, right-1 {
  84. tplVars.Path[left], tplVars.Path[right] = tplVars.Path[right], tplVars.Path[left]
  85. }
  86. // Establish list of files in the current directory
  87. for _, f := range files {
  88. tplVars.Files = append(tplVars.Files, fileItem{
  89. Name: f.Name(),
  90. Size: humanize.Bytes(uint64(f.Size())),
  91. Date: humanize.Time(f.ModTime()),
  92. IsDir: f.IsDir(),
  93. Path: filepath.Join(cfp, filepath.Clean(f.Name())),
  94. })
  95. }
  96. // Prepare the template
  97. tmpl, err := template.ParseFiles(lp)
  98. if err != nil {
  99. // Log the detailed error
  100. log.Println(err.Error())
  101. // Return a generic "Internal Server Error" message
  102. http.Error(w, http.StatusText(500), 500)
  103. return
  104. }
  105. // Return file listing in the template
  106. if err := tmpl.ExecuteTemplate(w, "layout", tplVars); err != nil {
  107. log.Println(err.Error())
  108. http.Error(w, http.StatusText(500), 500)
  109. }
  110. log.Println(fmt.Sprintf("200 - DIR %s", cfp))
  111. return
  112. }
  113. if !info.IsDir() {
  114. http.ServeFile(w, r, fp)
  115. log.Println(fmt.Sprintf("200 - FILE %s", cfp))
  116. return
  117. }
  118. }