all repos — flounder @ 98c0e38c17e0935914797a5e6763829d3f48ad1f

A small site builder for the Gemini protocol

Messy commit, some refactoring
alex wennerberg alex@alexwennerberg.com
Sun, 27 Dec 2020 18:40:04 -0800
commit

98c0e38c17e0935914797a5e6763829d3f48ad1f

parent

18a042de0538b53ca8cf949b0b575d1d597bf92a

6 files changed, 142 insertions(+), 86 deletions(-)

jump to
M gemfeed.gogemfeed.go

@@ -30,7 +30,57 @@ DateString string

Feed *Gemfeed } -// TODO definitely cache this function -- it reads EVERY gemini file on flounder. +// Non-standard extension +// Requires yyyy-mm-dd formatted files +func generateFeedFromFolder(folder string) []*FeedEntry { + user := getCreator(folder) + feed := Gemfeed{ + Title: user + "'s Gemfeed", + Creator: user, + // URL? + } + var feedEntries []*FeedEntry + err := filepath.Walk(folder, func(thepath string, info os.FileInfo, err error) error { + base := path.Base(thepath) + if len(base) >= 10 { + entry := FeedEntry{} + date, err := time.Parse("2006-01-02", base[:10]) + if err != nil { + return nil + } + entry.Date = date + entry.DateString = base[:10] + entry.Feed = &feed + f, err := os.Open(thepath) + if err != nil { + return nil + } + defer f.Close() + scanner := bufio.NewScanner(f) + i := 0 + for scanner.Scan() { + if i > 5 { // To be more efficient, only scan the top 5 lines + break + } + line := scanner.Text() + if strings.HasPrefix(line, "#") { + entry.Title = strings.Trim(line, "# \t") + break + } + i += 1 + } + // get title from first header + } + return nil + }) + if err != nil { + return nil + } + return feedEntries +} + +// TODO definitely cache this function +// TODO include generateFeedFromFolder for "gemfeed" folders func getAllGemfeedEntries() ([]*FeedEntry, []*Gemfeed, error) { maxUserItems := 25 maxItems := 50
M gemini.gogemini.go

@@ -1,9 +1,11 @@

package main import ( + "bytes" "crypto/tls" "crypto/x509/pkix" gmi "git.sr.ht/~adnano/go-gemini" + "io/ioutil" "log" "path" "path/filepath"

@@ -12,6 +14,34 @@ "text/template"

"time" ) +var gt *template.Template + +func generateGemfeedPage(user string) string { + return "" +} + +func generateFolderPage(fullpath string) string { + files, _ := ioutil.ReadDir(fullpath) + var renderedFiles = []File{} + for _, file := range files { + // Very awkward + res := fileFromPath(path.Join(fullpath, file.Name())) + renderedFiles = append(renderedFiles, res) + } + var buff bytes.Buffer + data := struct { + Host string + Folder string + Files []File + }{c.Host, getLocalPath(fullpath), renderedFiles} + err := gt.ExecuteTemplate(&buff, "folder.gmi", data) + if err != nil { + log.Println(err) + return "" + } + return buff.String() +} + func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) { log.Println("Index request") t, err := template.ParseFiles("templates/index.gmi")

@@ -54,13 +84,18 @@ }

func runGeminiServer() { log.Println("Starting gemini server") + var err error + gt, err = template.ParseGlob(path.Join(c.TemplatesDirectory, "*.gmi")) + if err != nil { + log.Fatal(err) + } var server gmi.Server server.ReadTimeout = 1 * time.Minute server.WriteTimeout = 2 * time.Minute hostname := strings.SplitN(c.Host, ":", 2)[0] // is this necc? - err := server.Certificates.Load(c.GeminiCertStore) + err = server.Certificates.Load(c.GeminiCertStore) if err != nil { } server.CreateCertificate = func(h string) (tls.Certificate, error) {
M http.gohttp.go

@@ -256,7 +256,7 @@ currentDate := time.Now().Format("2006-01-02")

data := struct { Host string PageTitle string - Files []*File + Files []File AuthUser AuthUser CurrentDate string }{c.Host, c.SiteTitle, files, user, currentDate}

@@ -530,13 +530,13 @@ func userFile(w http.ResponseWriter, r *http.Request) {

userName := filepath.Clean(strings.Split(r.Host, ".")[0]) // Clean probably unnecessary p := filepath.Clean(r.URL.Path) var isDir bool - fileName := path.Join(c.FilesDirectory, userName, p) // TODO rename filepath - stat, err := os.Stat(fileName) + fullPath := path.Join(c.FilesDirectory, userName, p) // TODO rename filepath + stat, _ := os.Stat(fullPath) if stat != nil { isDir = stat.IsDir() } - if p == "/" || isDir { - fileName = path.Join(fileName, "index.gmi") + if strings.HasSuffix(p, "index.gmi") { + http.Redirect(w, r, path.Dir(p), http.StatusMovedPermanently) } if strings.HasPrefix(p, "/"+HIDDEN_FOLDER) {

@@ -548,47 +548,32 @@ http.ServeFile(w, r, path.Join(c.TemplatesDirectory, "static/style.css"))

return } - _, err = os.Stat(fileName) - if os.IsNotExist(err) { - if p == "/" || isDir { - fileName := path.Join(c.FilesDirectory, userName, p) - favicon := getFavicon(userName) - files, _ := ioutil.ReadDir(fileName) - renderedFiles := []File{} - for _, file := range files { - n := file.Name() - newFile := File{ - Name: path.Join(p, n), // SHOULD be safe - UpdatedTime: file.ModTime(), - Host: c.Host, - Creator: getCreator(fileName), - } - renderedFiles = append(renderedFiles, newFile) + var geminiContent string + if p == "/" || isDir { + _, err := os.Stat(path.Join(fullPath, "index.gmi")) + if os.IsNotExist(err) { + if p == "/gemlog" { + // geminiContent = generateGemfeedPage(fullPath) + geminiContent = generateFolderPage(fullPath) + } else { + geminiContent = generateFolderPage(fullPath) } - hostname := strings.Split(r.Host, ":")[0] - URI := hostname + r.URL.String() - data := struct { - Folder string - Files []File - Favicon string - PageTitle string - URI string - }{p, renderedFiles, favicon, userName + p, URI} - // TODO check if gemlog - t.ExecuteTemplate(w, "folder.html", data) - return } else { - renderDefaultError(w, http.StatusNotFound) - return + fullPath = path.Join(fullPath, "index.gmi") } } - // Dumb content negotiation _, raw := r.URL.Query()["raw"] acceptsGemini := strings.Contains(r.Header.Get("Accept"), "text/gemini") - if !raw && !acceptsGemini && isGemini(fileName) { - file, _ := os.Open(fileName) - htmlString := textToHTML(gmi.ParseText(file)) + if !raw && !acceptsGemini && (isGemini(fullPath) || geminiContent != "") { + var htmlString string + if geminiContent == "" { + file, _ := os.Open(fullPath) + htmlString = textToHTML(gmi.ParseText(file)) + defer file.Close() + } else { + htmlString = textToHTML(gmi.ParseText(strings.NewReader(geminiContent))) + } favicon := getFavicon(userName) hostname := strings.Split(r.Host, ":")[0] URI := hostname + r.URL.String()

@@ -599,9 +584,8 @@ PageTitle string

URI string }{template.HTML(htmlString), favicon, userName + p, URI} t.ExecuteTemplate(w, "user_page.html", data) - file.Close() } else { - http.ServeFile(w, r, fileName) + http.ServeFile(w, r, fullPath) } }
M main.gomain.go

@@ -30,8 +30,24 @@ Name string // includes folder

UpdatedTime time.Time TimeAgo string IsText bool - Children []*File + Children []File Host string +} + +func fileFromPath(fullPath string) File { + info, _ := os.Stat(fullPath) + creatorFolder := getCreator(fullPath) + isText := strings.HasPrefix(mime.TypeByExtension(path.Ext(fullPath)), "text") // Not perfect + updatedTime := info.ModTime() + return File{ + Name: getLocalPath(fullPath), + Creator: path.Base(creatorFolder), + UpdatedTime: updatedTime, + IsText: isText, + TimeAgo: timeago(&updatedTime), + Host: c.Host, + } + } type User struct {

@@ -119,14 +135,8 @@ return filepath.SkipDir

} // make this do what it should if !info.IsDir() { - creatorFolder := getCreator(thepath) - updatedTime := info.ModTime() - result = append(result, &File{ - Name: getLocalPath(thepath), - Creator: path.Base(creatorFolder), - UpdatedTime: updatedTime, - TimeAgo: timeago(&updatedTime), - }) + res := fileFromPath(thepath) + result = append(result, &res) } return nil })

@@ -142,23 +152,15 @@ }

return result, nil } // todo clean up paths -func getMyFilesRecursive(p string, creator string) ([]*File, error) { - result := []*File{} +func getMyFilesRecursive(p string, creator string) ([]File, error) { + result := []File{} files, err := ioutil.ReadDir(p) if err != nil { return nil, err } for _, file := range files { - isText := strings.HasPrefix(mime.TypeByExtension(path.Ext(file.Name())), "text") fullPath := path.Join(p, file.Name()) - localPath := getLocalPath(fullPath) - f := &File{ - Name: localPath, - Creator: creator, - UpdatedTime: file.ModTime(), - IsText: isText, - Host: c.Host, - } + f := fileFromPath(fullPath) if file.IsDir() { f.Children, err = getMyFilesRecursive(path.Join(p, file.Name()), creator) }
A templates/folder.gmi

@@ -0,0 +1,8 @@

+{{$host := .Host }} +# {{ .Folder }} + +{{ range .Files }} +=> //{{.Creator}}.{{$host}}/{{.Name}} {{.Name}} +{{ end }} + +=> //{{$host}} Home
D templates/folder.html

@@ -1,23 +0,0 @@

-<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <title>{{.PageTitle }}</title> - <meta name="viewport" content="width=device-width" /> - <link rel="stylesheet" type="text/css" href="/style.css" /> - <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>{{.Favicon}}</text></svg>"> - </head> - <body> -<main> -<h1>{{.PageTitle}}</h1> -{{range .Files}} -<p> -<a href="//{{.Creator}}.{{.Host}}/{{.Name}}">{{.Name}}</a> -</p> -{{end}} -<br> -<a href="/">home</a> -<br> -</main> -</body> -</html>