From 62ff0f0c01aa2006cc57fc8364418758bdc3a129 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Wed, 21 Oct 2020 23:37:39 +0500 Subject: [PATCH 01/20] Check for name collisions of subhyphae when renaming recursively --- README.md | 8 ++++++-- hypha.go | 19 +++++++++++++------ main.go | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1ff54b3..f564532 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# 🍄 MycorrhizaWiki 0.9 +# 🍄 MycorrhizaWiki 0.10 A wiki engine. +This is the development branch for version 0.10. Features planned for this version: +* [ ] Mycomarkup +* [ ] CLI options +* [ ] CSS improvements + ## Building ```sh git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza @@ -34,4 +39,3 @@ Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where som * Tagging system * Authorization * Better history viewing -* More markups diff --git a/hypha.go b/hypha.go index 74a8685..f184ff3 100644 --- a/hypha.go +++ b/hypha.go @@ -61,10 +61,13 @@ func findHyphaeToRename(hyphaName string, recursive bool) []string { return hyphae } -func renamingPairs(hyphaNames []string, replaceName func(string) string) map[string]string { +func renamingPairs(hyphaNames []string, replaceName func(string) string) (map[string]string, error) { renameMap := make(map[string]string) for _, hn := range hyphaNames { if hd, ok := HyphaStorage[hn]; ok { + if _, nameUsed := HyphaStorage[replaceName(hn)]; nameUsed { + return nil, errors.New("Hypha " + replaceName(hn) + " already exists") + } if hd.textPath != "" { renameMap[hd.textPath] = replaceName(hd.textPath) } @@ -73,7 +76,7 @@ func renamingPairs(hyphaNames []string, replaceName func(string) string) map[str } } } - return renameMap + return renameMap, nil } // word Data is plural here @@ -94,11 +97,15 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his replaceName = func(str string) string { return strings.Replace(str, hyphaName, newName, 1) } - hyphaNames = findHyphaeToRename(hyphaName, recursive) - renameMap = renamingPairs(hyphaNames, replaceName) - renameMsg = "Rename ‘%s’ to ‘%s’" - hop = history.Operation(history.TypeRenameHypha) + hyphaNames = findHyphaeToRename(hyphaName, recursive) + renameMap, err = renamingPairs(hyphaNames, replaceName) + renameMsg = "Rename ‘%s’ to ‘%s’" + hop = history.Operation(history.TypeRenameHypha) ) + if err != nil { + hop.Errs = append(hop.Errs, err) + return hop + } if recursive { renameMsg += " recursively" } diff --git a/main.go b/main.go index 7956270..e8ac7fd 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,7 @@ func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(status) fmt.Fprint(w, base(title, fmt.Sprintf( - `

%s. Go back to the hypha.

`, + `

%s. Go back to the hypha.

`, errMsg, name))) } From 756c4e812500c6fdc4330848b57b85fd57225076 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Thu, 22 Oct 2020 21:42:48 +0500 Subject: [PATCH 02/20] Start implementing new wiki file structure --- history/operations.go | 6 +++ http_mutators.go | 91 ++++++------------------------------------- hypha.go | 78 ++++++++++++++++++++++++++++++------- metarrhiza | 2 +- mime.go | 45 ++++++++++++++------- 5 files changed, 114 insertions(+), 108 deletions(-) diff --git a/history/operations.go b/history/operations.go index 4388916..dca0bd4 100644 --- a/history/operations.go +++ b/history/operations.go @@ -55,6 +55,12 @@ func (hop *HistoryOp) gitop(args ...string) *HistoryOp { return hop } +// WithError appends the `err` to the list of errors. +func (hop *HistoryOp) WithError(err error) *HistoryOp { + hop.Errs = append(hop.Errs, err) + return hop +} + // WithFilesRemoved git-rm-s all passed `paths`. Paths can be rooted or not. Paths that are empty strings are ignored. func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp { args := []string{"rm", "--quiet", "--"} diff --git a/http_mutators.go b/http_mutators.go index aa669c0..4801fe6 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -2,13 +2,9 @@ package main import ( "fmt" - "io/ioutil" "log" "net/http" - "os" - "path/filepath" - "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/util" ) @@ -116,8 +112,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { textAreaFill, err = FetchTextPart(hyphaData) if err != nil { log.Println(err) - HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", - "Could not fetch text data") + HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", "Could not fetch text data") return } } else { @@ -133,42 +128,16 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { hyphaName = HyphaNameFromRq(rq, "upload-text") hyphaData, isOld = HyphaStorage[hyphaName] textData = rq.PostFormValue("text") - textDataBytes = []byte(textData) - fullPath = filepath.Join(WikiDir, hyphaName+"&.gmi") ) if textData == "" { - HttpErr(w, http.StatusBadRequest, hyphaName, "Error", - "No text data passed") + HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed") return } - // For some reason, only 0777 works. Why? - if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { - log.Println(err) - } - if err := ioutil.WriteFile(fullPath, textDataBytes, 0644); err != nil { - log.Println(err) - HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", - fmt.Sprintf("Failed to write %d bytes to %s", - len(textDataBytes), fullPath)) - return - } - if !isOld { - hd := HyphaData{ - textType: TextGemini, - textPath: fullPath, - } - HyphaStorage[hyphaName] = &hd - hyphaData = &hd + if hop := hyphaData.UploadText(hyphaName, textData, isOld); len(hop.Errs) != 0 { + HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) } else { - hyphaData.textType = TextGemini - hyphaData.textPath = fullPath + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) } - history.Operation(history.TypeEditText). - WithFiles(fullPath). - WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)). - WithSignature("anon"). - Apply() - http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) } // handlerUploadBinary uploads a new binary part for the hypha. @@ -176,62 +145,26 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) hyphaName := HyphaNameFromRq(rq, "upload-binary") rq.ParseMultipartForm(10 << 20) - // Read file + file, handler, err := rq.FormFile("binary") if file != nil { defer file.Close() } // If file is not passed: if err != nil { - HttpErr(w, http.StatusBadRequest, hyphaName, "Error", - "No binary data passed") + HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed") return } // If file is passed: var ( hyphaData, isOld = HyphaStorage[hyphaName] - mimeType = MimeToBinaryType(handler.Header.Get("Content-Type")) - ext = mimeType.Extension() - fullPath = filepath.Join(WikiDir, hyphaName+"&"+ext) + mime = handler.Header.Get("Content-Type") + hop = hyphaData.UploadBinary(hyphaName, mime, file, isOld) ) - data, err := ioutil.ReadAll(file) - if err != nil { - HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", - "Could not read passed data") - return - } - if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { - log.Println(err) - } - if !isOld { - hd := HyphaData{ - binaryPath: fullPath, - binaryType: mimeType, - } - HyphaStorage[hyphaName] = &hd - hyphaData = &hd + if len(hop.Errs) != 0 { + HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) } else { - if hyphaData.binaryPath != fullPath { - if err := history.Rename(hyphaData.binaryPath, fullPath); err != nil { - log.Println(err) - } else { - log.Println("Moved", hyphaData.binaryPath, "to", fullPath) - } - } - hyphaData.binaryPath = fullPath - hyphaData.binaryType = mimeType + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) } - if err = ioutil.WriteFile(fullPath, data, 0644); err != nil { - HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", - "Could not save passed data") - return - } - log.Println("Written", len(data), "of binary data for", hyphaName, "to path", fullPath) - history.Operation(history.TypeEditText). - WithFiles(fullPath, hyphaData.binaryPath). - WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mimeType.Mime())). - WithSignature("anon"). - Apply() - http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) } diff --git a/hypha.go b/hypha.go index f184ff3..e630b34 100644 --- a/hypha.go +++ b/hypha.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "log" + "mime/multipart" "os" "path/filepath" "strings" @@ -40,6 +41,52 @@ type HyphaData struct { binaryType BinaryType } +// uploadHelp is a helper function for UploadText and UploadBinary +func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext, originalFullPath string, isOld bool, data []byte) *history.HistoryOp { + var ( + fullPath = filepath.Join(WikiDir, hyphaName+ext) + ) + if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { + return hop.WithError(err) + } + + if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { + return hop.WithError(err) + } + + if isOld && originalFullPath != fullPath { + if err := history.Rename(originalFullPath, fullPath); err != nil { + return hop.WithError(err) + } + log.Println("Move", originalFullPath, "to", fullPath) + } + if !isOld { + HyphaStorage[hyphaName] = hd + } + return hop.WithFiles(fullPath). + WithSignature("anon"). + Apply() +} + +// UploadText loads a new text part from `textData` for hypha `hyphaName` with `hd`. It must be marked if the hypha `isOld`. +func (hd *HyphaData) UploadText(hyphaName, textData string, isOld bool) *history.HistoryOp { + hop := history.Operation(history.TypeEditText).WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)) + return hd.uploadHelp(hop, hyphaName, ".myco", hd.textPath, isOld, []byte(textData)) +} + +// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`. +func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, isOld bool) *history.HistoryOp { + var ( + hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mime)) + data, err = ioutil.ReadAll(file) + ) + if err != nil { + return hop.WithError(err) + } + + return hd.uploadHelp(hop, hyphaName, MimeToExtension(mime), hd.binaryPath, isOld, data) +} + // DeleteHypha deletes hypha and makes a history record about that. func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp { hop := history.Operation(history.TypeDeleteHypha). @@ -160,30 +207,35 @@ func Index(path string) { } for _, node := range nodes { - // If this hypha looks like it can be a hypha path, go deeper - if node.IsDir() && isCanonicalName(node.Name()) { + // 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())) } - hyphaPartFilename := filepath.Join(path, node.Name()) - skip, hyphaName, isText, mimeId := DataFromFilename(hyphaPartFilename) + var ( + hyphaPartPath = filepath.Join(path, node.Name()) + hyphaName, isText, skip = DataFromFilename(hyphaPartPath) + hyphaData *HyphaData + ) if !skip { - var ( - hyphaData *HyphaData - ok bool - ) - if hyphaData, ok = HyphaStorage[hyphaName]; !ok { + // 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 } if isText { - hyphaData.textPath = hyphaPartFilename - hyphaData.textType = TextType(mimeId) + hyphaData.textPath = hyphaPartPath } else { - hyphaData.binaryPath = hyphaPartFilename - hyphaData.binaryType = BinaryType(mimeId) + // 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/metarrhiza b/metarrhiza index 2c0e431..faab70f 160000 --- a/metarrhiza +++ b/metarrhiza @@ -1 +1 @@ -Subproject commit 2c0e43199ed28f7022a38463a0eec3af3ecb03c9 +Subproject commit faab70f77e18197b1c4cecb5ad54e6d26da843f5 diff --git a/mime.go b/mime.go index bdc69c2..7008e24 100644 --- a/mime.go +++ b/mime.go @@ -44,12 +44,7 @@ const ( BinaryMp4 ) -var binaryMimes = [...]string{ - "application/octet-stream", - "image/jpeg", "image/gif", "image/png", "image/webp", - "image/svg+xml", "image/x-icon", - "application/ogg", "video/webm", "audio/mp3", "video/mp4", -} +var binaryMimes = [...]string{} // Mime returns mime type representation of `t`. func (t BinaryType) Mime() string { @@ -66,6 +61,26 @@ func (t BinaryType) Extension() string { return binaryExtensions[t] } +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" +} + // MimeToBinaryType converts mime type to BinaryType. If the mime type is not supported, BinaryOctet is returned as a fallback type. func MimeToBinaryType(mime string) BinaryType { for i, binaryMime := range binaryMimes { @@ -77,17 +92,17 @@ func MimeToBinaryType(mime string) BinaryType { } // 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) (skip bool, name string, isText bool, mimeId int) { +func DataFromFilename(fullPath string) (name string, isText bool, skip bool) { shortPath := strings.TrimPrefix(fullPath, WikiDir)[1:] - // Special files start with & - // &. is used in normal hypha part names - if shortPath[0] == '&' || strings.LastIndex(shortPath, "&.") < 0 { - skip = true - return - } ext := filepath.Ext(shortPath) - name = strings.TrimSuffix(shortPath, "&"+ext) - isText, mimeId = mimeData(ext) + name = CanonicalName(strings.TrimSuffix(shortPath, ext)) + switch ext { + case ".myco": + isText = true + case "", shortPath: + skip = true + } + return } From dbafbb85d7d417b8c1e9669b09a8274b5e0be6aa Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Thu, 22 Oct 2020 22:12:12 +0500 Subject: [PATCH 03/20] Get rid of the older mime type mechanism --- README.md | 1 + http_readers.go | 5 +-- hypha.go | 12 +++---- main.go | 2 +- mime.go | 96 +++++++++---------------------------------------- 5 files changed, 27 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index f564532..2f42622 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ A wiki engine. This is the development branch for version 0.10. Features planned for this version: +* [ ] New file structure * [ ] Mycomarkup * [ ] CLI options * [ ] CSS improvements diff --git a/http_readers.go b/http_readers.go index 5843e4d..8166353 100644 --- a/http_readers.go +++ b/http_readers.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "os" + "path/filepath" "strings" "github.com/bouncepaw/mycorrhiza/gemtext" @@ -75,7 +76,7 @@ func handlerText(w http.ResponseWriter, rq *http.Request) { hyphaName := HyphaNameFromRq(rq, "text") if data, ok := HyphaStorage[hyphaName]; ok { log.Println("Serving", data.textPath) - w.Header().Set("Content-Type", data.textType.Mime()) + w.Header().Set("Content-Type", "text/plain") http.ServeFile(w, rq, data.textPath) } } @@ -86,7 +87,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", data.binaryType.Mime()) + w.Header().Set("Content-Type", ExtensionToMime(filepath.Ext(data.binaryPath))) http.ServeFile(w, rq, data.binaryPath) } } diff --git a/hypha.go b/hypha.go index e630b34..322a3d8 100644 --- a/hypha.go +++ b/hypha.go @@ -36,9 +36,7 @@ func init() { // HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types. type HyphaData struct { textPath string - textType TextType binaryPath string - binaryType BinaryType } // uploadHelp is a helper function for UploadText and UploadBinary @@ -167,14 +165,14 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his } // binaryHtmlBlock creates an html block for binary part of the hypha. -func binaryHtmlBlock(hyphaName string, d *HyphaData) string { - switch d.binaryType { - case BinaryJpeg, BinaryGif, BinaryPng, BinaryWebp, BinarySvg, BinaryIco: +func binaryHtmlBlock(hyphaName string, hd *HyphaData) string { + switch filepath.Ext(hd.binaryPath) { + case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico": return fmt.Sprintf(`
`, hyphaName) - case BinaryOgg, BinaryWebm, BinaryMp4: + case ".ogg", ".webm", ".mp4": return fmt.Sprintf(`

Your browser does not support video. See video's direct url

`, hyphaName) - case BinaryMp3: + case ".mp3": return fmt.Sprintf(`