mirror of
				https://github.com/osmarks/mycorrhiza.git
				synced 2025-10-30 23:23:04 +00:00 
			
		
		
		
	Start implementing new wiki file structure
This commit is contained in:
		| @@ -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 | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
 Submodule metarrhiza updated: 2c0e43199e...faab70f77e
									
								
							
							
								
								
									
										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 | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 bouncepaw
					bouncepaw