diff --git a/.gitignore b/.gitignore index c450f48..9632b28 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ mycorrhiza +hyphae/*.gog diff --git a/http_readers.go b/http_readers.go index 7978e69..9cd4c69 100644 --- a/http_readers.go +++ b/http_readers.go @@ -11,6 +11,7 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/tree" "github.com/bouncepaw/mycorrhiza/user" @@ -71,7 +72,7 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) { hyphaName := HyphaNameFromRq(rq, "binary") if data, ok := HyphaStorage[hyphaName]; ok { log.Println("Serving", data.binaryPath) - w.Header().Set("Content-Type", ExtensionToMime(filepath.Ext(data.binaryPath))) + w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(data.binaryPath))) http.ServeFile(w, rq, data.binaryPath) } } diff --git a/hypha.go b/hypha.go index be7fef4..82fe103 100644 --- a/hypha.go +++ b/hypha.go @@ -13,6 +13,7 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) @@ -114,7 +115,7 @@ func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *hi if err != nil { return hop.WithError(err).Apply() } - return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u) + return uploadHelp(hop, hyphaName, mimetype.ToExtension(mime), data, u) } // DeleteHypha deletes hypha and makes a history record about that. @@ -254,48 +255,6 @@ func binaryHtmlBlock(hyphaName string, hd *HyphaData) string { } } -// Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive. -func Index(path string) { - nodes, err := ioutil.ReadDir(path) - if err != nil { - log.Fatal(err) - } - - for _, node := range nodes { - // If this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an admnistrative importance! - if node.IsDir() && isCanonicalName(node.Name()) && node.Name() != ".git" && node.Name() != "static" { - Index(filepath.Join(path, node.Name())) - continue - } - - var ( - hyphaPartPath = filepath.Join(path, node.Name()) - hyphaName, isText, skip = DataFromFilename(hyphaPartPath) - hyphaData *HyphaData - ) - if !skip { - // Reuse the entry for existing hyphae, create a new one for those that do not exist yet. - if hd, ok := HyphaStorage[hyphaName]; ok { - hyphaData = hd - } else { - hyphaData = &HyphaData{} - HyphaStorage[hyphaName] = hyphaData - hyphae.IncrementCount() - } - if isText { - hyphaData.textPath = hyphaPartPath - } else { - // Notify the user about binary part collisions. It's a design decision to just use any of them, it's the user's fault that they have screwed up the folder structure, but the engine should at least let them know, right? - if hyphaData.binaryPath != "" { - log.Println("There is a file collision for binary part of a hypha:", hyphaData.binaryPath, "and", hyphaPartPath, "-- going on with the latter") - } - hyphaData.binaryPath = hyphaPartPath - } - } - - } -} - // FetchTextPart tries to read text file in the `d`. If there is no file, empty string is returned. func FetchTextPart(d *HyphaData) (string, error) { if d.textPath == "" { @@ -327,3 +286,45 @@ func setHeaderLinks() { } } } + +// Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive. +func Index(path string) { + nodes, err := ioutil.ReadDir(path) + if err != nil { + log.Fatal(err) + } + + for _, node := range nodes { + // If this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an admnistrative importance! + if node.IsDir() && isCanonicalName(node.Name()) && node.Name() != ".git" && node.Name() != "static" { + Index(filepath.Join(path, node.Name())) + continue + } + + var ( + hyphaPartPath = filepath.Join(path, node.Name()) + hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath) + hyphaData *HyphaData + ) + if !skip { + // Reuse the entry for existing hyphae, create a new one for those that do not exist yet. + if hd, ok := HyphaStorage[hyphaName]; ok { + hyphaData = hd + } else { + hyphaData = &HyphaData{} + HyphaStorage[hyphaName] = hyphaData + hyphae.IncrementCount() + } + if isText { + hyphaData.textPath = hyphaPartPath + } else { + // Notify the user about binary part collisions. It's a design decision to just use any of them, it's the user's fault that they have screwed up the folder structure, but the engine should at least let them know, right? + if hyphaData.binaryPath != "" { + log.Println("There is a file collision for binary part of a hypha:", hyphaData.binaryPath, "and", hyphaPartPath, "-- going on with the latter") + } + hyphaData.binaryPath = hyphaPartPath + } + } + + } +} diff --git a/hyphae/hypha.go b/hyphae/hypha.go deleted file mode 100644 index d70cd1e..0000000 --- a/hyphae/hypha.go +++ /dev/null @@ -1,21 +0,0 @@ -package hyphae - -// TODO: do -import () - -type Hypha struct { - Name string - Exists bool - TextPath string - BinaryPath string - OutLinks []string - BackLinks []string -} - -// AddHypha adds a hypha named `name` with such `textPath` and `binaryPath`. Both paths can be empty. Does //not// check for hypha's existence beforehand. Count is handled. -func AddHypha(name, textPath, binaryPath string) { -} - -// DeleteHypha clears both paths and all out-links from the named hypha and marks it as non-existent. It does not actually delete it from the memdb. Count is handled. -func DeleteHypha(name string) { -} diff --git a/main.go b/main.go index c87c96e..f16eaa3 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" @@ -64,7 +65,7 @@ func handlerList(w http.ResponseWriter, rq *http.Request) { u = user.FromRequest(rq) ) for hyphaName, data := range HyphaStorage { - tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "") + tbody += templates.HyphaListRowHTML(hyphaName, mimetype.FromExtension(filepath.Ext(data.binaryPath)), data.binaryPath != "") } util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u)) } diff --git a/mime.go b/mime.go deleted file mode 100644 index c16eebe..0000000 --- a/mime.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "path/filepath" - "strings" -) - -func MimeToExtension(mime string) string { - mm := map[string]string{ - "application/octet-stream": "bin", - "image/jpeg": "jpg", - "image/gif": "gif", - "image/png": "png", - "image/webp": "webp", - "image/svg+xml": "svg", - "image/x-icon": "ico", - "application/ogg": "ogg", - "video/webm": "webm", - "audio/mp3": "mp3", - "video/mp4": "mp4", - } - if ext, ok := mm[mime]; ok { - return "." + ext - } - return ".bin" -} - -func ExtensionToMime(ext string) string { - mm := map[string]string{ - ".bin": "application/octet-stream", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".gif": "image/gif", - ".png": "image/png", - ".webp": "image/webp", - ".svg": "image/svg+xml", - ".ico": "image/x-icon", - ".ogg": "application/ogg", - ".webm": "video/webm", - ".mp3": "audio/mp3", - ".mp4": "video/mp4", - } - if mime, ok := mm[ext]; ok { - return mime - } - return "application/octet-stream" -} - -// DataFromFilename fetches all meta information from hypha content file with path `fullPath`. If it is not a content file, `skip` is true, and you are expected to ignore this file when indexing hyphae. `name` is name of the hypha to which this file relates. `isText` is true when the content file is text, false when is binary. `mimeId` is an integer representation of content type. Cast it to TextType if `isText == true`, cast it to BinaryType if `isText == false`. -func DataFromFilename(fullPath string) (name string, isText bool, skip bool) { - shortPath := strings.TrimPrefix(fullPath, WikiDir)[1:] - ext := filepath.Ext(shortPath) - name = CanonicalName(strings.TrimSuffix(shortPath, ext)) - switch ext { - case ".myco": - isText = true - case "", shortPath: - skip = true - } - - return -} diff --git a/mimetype/mime.go b/mimetype/mime.go new file mode 100644 index 0000000..7e18ca7 --- /dev/null +++ b/mimetype/mime.go @@ -0,0 +1,68 @@ +package mimetype + +import ( + "path/filepath" + "strings" + + "github.com/bouncepaw/mycorrhiza/util" +) + +// ToExtension returns dotted extension for given mime-type. +func ToExtension(mime string) string { + if ext, ok := mapMime2Ext[mime]; ok { + return "." + ext + } + return ".bin" +} + +// FromExtension returns mime-type for given extension. The extension must start with a dot. +func FromExtension(ext string) string { + if mime, ok := mapExt2Mime[ext]; ok { + return mime + } + return "application/octet-stream" +} + +// DataFromFilename fetches all meta information from hypha content file with path `fullPath`. If it is not a content file, `skip` is true, and you are expected to ignore this file when indexing hyphae. `name` is name of the hypha to which this file relates. `isText` is true when the content file is text, false when is binary. +func DataFromFilename(fullPath string) (name string, isText bool, skip bool) { + shortPath := util.ShorterPath(fullPath) + ext := filepath.Ext(shortPath) + name = util.CanonicalName(strings.TrimSuffix(shortPath, ext)) + switch ext { + case ".myco": + isText = true + case "", shortPath: + skip = true + } + + return +} + +var mapMime2Ext = map[string]string{ + "application/octet-stream": "bin", + "image/jpeg": "jpg", + "image/gif": "gif", + "image/png": "png", + "image/webp": "webp", + "image/svg+xml": "svg", + "image/x-icon": "ico", + "application/ogg": "ogg", + "video/webm": "webm", + "audio/mp3": "mp3", + "video/mp4": "mp4", +} + +var mapExt2Mime = map[string]string{ + ".bin": "application/octet-stream", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".png": "image/png", + ".webp": "image/webp", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".ogg": "application/ogg", + ".webm": "video/webm", + ".mp3": "audio/mp3", + ".mp4": "video/mp4", +} diff --git a/util/util.go b/util/util.go index c977992..acf0eec 100644 --- a/util/util.go +++ b/util/util.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/hex" "net/http" + "regexp" "strings" ) @@ -65,3 +66,16 @@ func BeautifulName(uglyName string) string { } return strings.Title(strings.ReplaceAll(uglyName, "_", " ")) } + +// CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores. +func CanonicalName(name string) string { + return strings.ToLower(strings.ReplaceAll(name, " ", "_")) +} + +// HyphaPattern is a pattern which all hyphae must match. +var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) + +// IsCanonicalName checks if the `name` is canonical. +func IsCanonicalName(name string) bool { + return HyphaPattern.MatchString(name) +}