1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-01-18 22:52:50 +00:00

Start implementing new wiki file structure

This commit is contained in:
bouncepaw 2020-10-22 21:42:48 +05:00
parent 62ff0f0c01
commit 756c4e8125
5 changed files with 114 additions and 108 deletions

View File

@ -55,6 +55,12 @@ func (hop *HistoryOp) gitop(args ...string) *HistoryOp {
return hop 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. // 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 { func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp {
args := []string{"rm", "--quiet", "--"} args := []string{"rm", "--quiet", "--"}

View File

@ -2,13 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os"
"path/filepath"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -116,8 +112,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
textAreaFill, err = FetchTextPart(hyphaData) textAreaFill, err = FetchTextPart(hyphaData)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", "Could not fetch text data")
"Could not fetch text data")
return return
} }
} else { } else {
@ -133,105 +128,43 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
hyphaName = HyphaNameFromRq(rq, "upload-text") hyphaName = HyphaNameFromRq(rq, "upload-text")
hyphaData, isOld = HyphaStorage[hyphaName] hyphaData, isOld = HyphaStorage[hyphaName]
textData = rq.PostFormValue("text") textData = rq.PostFormValue("text")
textDataBytes = []byte(textData)
fullPath = filepath.Join(WikiDir, hyphaName+"&.gmi")
) )
if textData == "" { if textData == "" {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
"No text data passed")
return return
} }
// For some reason, only 0777 works. Why? if hop := hyphaData.UploadText(hyphaName, textData, isOld); len(hop.Errs) != 0 {
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
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
} else { } else {
hyphaData.textType = TextGemini
hyphaData.textPath = fullPath
}
history.Operation(history.TypeEditText).
WithFiles(fullPath).
WithMsg(fmt.Sprintf("Edit %s", hyphaName)).
WithSignature("anon").
Apply()
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }
}
// handlerUploadBinary uploads a new binary part for the hypha. // handlerUploadBinary uploads a new binary part for the hypha.
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "upload-binary") hyphaName := HyphaNameFromRq(rq, "upload-binary")
rq.ParseMultipartForm(10 << 20) rq.ParseMultipartForm(10 << 20)
// Read file
file, handler, err := rq.FormFile("binary") file, handler, err := rq.FormFile("binary")
if file != nil { if file != nil {
defer file.Close() defer file.Close()
} }
// If file is not passed: // If file is not passed:
if err != nil { if err != nil {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
"No binary data passed")
return return
} }
// If file is passed: // If file is passed:
var ( var (
hyphaData, isOld = HyphaStorage[hyphaName] hyphaData, isOld = HyphaStorage[hyphaName]
mimeType = MimeToBinaryType(handler.Header.Get("Content-Type")) mime = handler.Header.Get("Content-Type")
ext = mimeType.Extension() hop = hyphaData.UploadBinary(hyphaName, mime, file, isOld)
fullPath = filepath.Join(WikiDir, hyphaName+"&"+ext)
) )
data, err := ioutil.ReadAll(file) if len(hop.Errs) != 0 {
if err != nil { HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
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
} else { } 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
}
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) http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }
}

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"mime/multipart"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -40,6 +41,52 @@ type HyphaData struct {
binaryType BinaryType 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. // DeleteHypha deletes hypha and makes a history record about that.
func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp { func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp {
hop := history.Operation(history.TypeDeleteHypha). hop := history.Operation(history.TypeDeleteHypha).
@ -160,30 +207,35 @@ func Index(path string) {
} }
for _, node := range nodes { for _, node := range nodes {
// If this hypha looks like it can be a hypha path, go deeper // 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()) { if node.IsDir() && isCanonicalName(node.Name()) && node.Name() != ".git" && node.Name() != "static" {
Index(filepath.Join(path, node.Name())) Index(filepath.Join(path, node.Name()))
} }
hyphaPartFilename := filepath.Join(path, node.Name())
skip, hyphaName, isText, mimeId := DataFromFilename(hyphaPartFilename)
if !skip {
var ( var (
hyphaPartPath = filepath.Join(path, node.Name())
hyphaName, isText, skip = DataFromFilename(hyphaPartPath)
hyphaData *HyphaData hyphaData *HyphaData
ok bool
) )
if hyphaData, ok = HyphaStorage[hyphaName]; !ok { 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{} hyphaData = &HyphaData{}
HyphaStorage[hyphaName] = hyphaData HyphaStorage[hyphaName] = hyphaData
} }
if isText { if isText {
hyphaData.textPath = hyphaPartFilename hyphaData.textPath = hyphaPartPath
hyphaData.textType = TextType(mimeId)
} else { } else {
hyphaData.binaryPath = hyphaPartFilename // 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?
hyphaData.binaryType = BinaryType(mimeId) 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
} }
} }
} }
} }

@ -1 +1 @@
Subproject commit 2c0e43199ed28f7022a38463a0eec3af3ecb03c9 Subproject commit faab70f77e18197b1c4cecb5ad54e6d26da843f5

45
mime.go
View File

@ -44,12 +44,7 @@ const (
BinaryMp4 BinaryMp4
) )
var binaryMimes = [...]string{ 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",
}
// Mime returns mime type representation of `t`. // Mime returns mime type representation of `t`.
func (t BinaryType) Mime() string { func (t BinaryType) Mime() string {
@ -66,6 +61,26 @@ func (t BinaryType) Extension() string {
return binaryExtensions[t] 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. // 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 { func MimeToBinaryType(mime string) BinaryType {
for i, binaryMime := range binaryMimes { 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`. // 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:] 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) ext := filepath.Ext(shortPath)
name = strings.TrimSuffix(shortPath, "&"+ext) name = CanonicalName(strings.TrimSuffix(shortPath, ext))
isText, mimeId = mimeData(ext) switch ext {
case ".myco":
isText = true
case "", shortPath:
skip = true
}
return return
} }