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:
parent
62ff0f0c01
commit
756c4e8125
@ -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", "--"}
|
||||||
|
@ -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,42 +128,16 @@ 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
|
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerUploadBinary uploads a new binary part for the hypha.
|
// 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)
|
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 {
|
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
78
hypha.go
78
hypha.go
@ -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())
|
var (
|
||||||
skip, hyphaName, isText, mimeId := DataFromFilename(hyphaPartFilename)
|
hyphaPartPath = filepath.Join(path, node.Name())
|
||||||
|
hyphaName, isText, skip = DataFromFilename(hyphaPartPath)
|
||||||
|
hyphaData *HyphaData
|
||||||
|
)
|
||||||
if !skip {
|
if !skip {
|
||||||
var (
|
// Reuse the entry for existing hyphae, create a new one for those that do not exist yet.
|
||||||
hyphaData *HyphaData
|
if hd, ok := HyphaStorage[hyphaName]; ok {
|
||||||
ok bool
|
hyphaData = hd
|
||||||
)
|
} else {
|
||||||
if hyphaData, ok = HyphaStorage[hyphaName]; !ok {
|
|
||||||
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
45
mime.go
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user