1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
package main
import (
"bufio"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
)
func generateDirectoryListing(URL *url.URL, path string, config UserConfig) (string, error) {
var listing string
files, err := ioutil.ReadDir(path)
if err != nil {
return listing, err
}
listing = "# Directory listing\n\n"
// Override with .mollyhead file
header_path := filepath.Join(path, ".mollyhead")
_, err = os.Stat(header_path)
if err == nil {
header, err := ioutil.ReadFile(header_path)
if err != nil {
return listing, err
}
listing = string(header)
}
// Do "up" link first
if URL.Path != "/" {
if strings.HasSuffix(URL.Path, "/") {
URL.Path = URL.Path[:len(URL.Path)-1]
}
up := filepath.Dir(URL.Path)
listing += fmt.Sprintf("=> %s %s\n", up, "..")
}
// Sort files by criteria first
sort.SliceStable(files, func(i, j int) bool {
if config.DirectoryReverse {
i, j = j, i
}
if config.DirectorySort == "Name" {
return files[i].Name() < files[j].Name()
} else if config.DirectorySort == "Size" {
return files[i].Size() < files[j].Size()
} else if config.DirectorySort == "Time" {
return files[i].ModTime().Before(files[j].ModTime())
}
return false // Should not happen
})
// Sort directories before file
if config.DirectorySubdirsFirst {
sort.SliceStable(files, func(i, j int) bool {
// If i is a dir and j is a file, i < j
if files[i].IsDir() && !files[j].IsDir() {
return true
} else {
return false
}
})
}
// Format lines
for _, file := range files {
// Skip dotfiles
if strings.HasPrefix(file.Name(), ".") {
continue
}
// Only list world readable files
if uint64(file.Mode().Perm())&0444 != 0444 {
continue
}
// Make sure links to directories have a trailing slash,
// to avoid needless redirects
relativeUrl := url.PathEscape(file.Name())
if file.IsDir() {
relativeUrl += "/"
}
listing += fmt.Sprintf("=> %s %s\n", relativeUrl, generatePrettyFileLabel(file, path, config))
}
return listing, nil
}
func generatePrettyFileLabel(info os.FileInfo, path string, config UserConfig) string {
var size string
if info.IsDir() {
size = " "
} else if info.Size() < 1024 {
size = fmt.Sprintf("%4d B", info.Size())
} else if info.Size() < (1024 << 10) {
size = fmt.Sprintf("%4d KiB", info.Size()>>10)
} else if info.Size() < 1024<<20 {
size = fmt.Sprintf("%4d MiB", info.Size()>>20)
} else if info.Size() < 1024<<30 {
size = fmt.Sprintf("%4d GiB", info.Size()>>30)
} else if info.Size() < 1024<<40 {
size = fmt.Sprintf("%4d TiB", info.Size()>>40)
} else {
size = "GIGANTIC"
}
name := info.Name()
if config.DirectoryTitles && filepath.Ext(name) == "."+config.GeminiExt {
name = readHeading(path, info)
}
if len(name) > 40 {
name = name[:36] + "..."
}
if info.IsDir() {
name += "/"
}
return fmt.Sprintf("%-40s %s %v", name, size, info.ModTime().Format("Jan _2 2006"))
}
func readHeading(path string, info os.FileInfo) string {
filePath := filepath.Join(path, info.Name())
file, err := os.Open(filePath)
if err != nil {
return info.Name()
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "# ") {
return strings.TrimSpace(line[1:])
}
}
return info.Name()
}
|