From 756c4e812500c6fdc4330848b57b85fd57225076 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Thu, 22 Oct 2020 21:42:48 +0500 Subject: [PATCH] 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 }