mirror of
				https://github.com/osmarks/mycorrhiza.git
				synced 2025-10-31 15:43:00 +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 | ||||
| } | ||||
|  | ||||
| // 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", "--"} | ||||
|   | ||||
| @@ -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,105 +128,43 @@ 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 | ||||
| 	} | ||||
| 	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. | ||||
| 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 | ||||
| 	} | ||||
| 	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) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										74
									
								
								hypha.go
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								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) | ||||
| 		if !skip { | ||||
| 		var ( | ||||
| 			hyphaPartPath           = filepath.Join(path, node.Name()) | ||||
| 			hyphaName, isText, skip = DataFromFilename(hyphaPartPath) | ||||
| 			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{} | ||||
| 				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 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
 Submodule metarrhiza updated: 2c0e43199e...faab70f77e
									
								
							
							
								
								
									
										45
									
								
								mime.go
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								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 | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 bouncepaw
					bouncepaw