package main import ( "io/ioutil" "log" "net/http" "path/filepath" "strconv" "strings" "time" "github.com/bouncepaw/mycorrhiza/cfg" "github.com/gorilla/mux" ) // There are handlers below. See main() for their usage. // Boilerplate code present in many handlers. Good to have it. func HandlerBase(w http.ResponseWriter, rq *http.Request) (Revision, bool) { vars := mux.Vars(rq) revno := RevInMap(vars) return GetRevision(vars["hypha"], revno) } func HandlerGetBinary(w http.ResponseWriter, rq *http.Request) { if rev, ok := HandlerBase(w, rq); ok { rev.ActionGetBinary(w) } } func HandlerRaw(w http.ResponseWriter, rq *http.Request) { if rev, ok := HandlerBase(w, rq); ok { rev.ActionRaw(w) } } func HandlerZen(w http.ResponseWriter, rq *http.Request) { if rev, ok := HandlerBase(w, rq); ok { rev.ActionZen(w) } } func HandlerView(w http.ResponseWriter, rq *http.Request) { if rev, ok := HandlerBase(w, rq); ok { rev.ActionView(w, HyphaPage) } else { // Hypha does not exist log.Println("Hypha does not exist, showing 404") w.WriteHeader(http.StatusNotFound) w.Write([]byte(Hypha404(mux.Vars(rq)["hypha"]))) } } func HandlerHistory(w http.ResponseWriter, rq *http.Request) { w.WriteHeader(http.StatusNotImplemented) log.Println("Attempt to access an unimplemented thing") } func HandlerEdit(w http.ResponseWriter, rq *http.Request) { vars := mux.Vars(rq) ActionEdit(vars["hypha"], w) } func HandlerRewind(w http.ResponseWriter, rq *http.Request) { w.WriteHeader(http.StatusNotImplemented) log.Println("Attempt to access an unimplemented thing") } func HandlerDelete(w http.ResponseWriter, rq *http.Request) { w.WriteHeader(http.StatusNotImplemented) log.Println("Attempt to access an unimplemented thing") } func HandlerRename(w http.ResponseWriter, rq *http.Request) { w.WriteHeader(http.StatusNotImplemented) log.Println("Attempt to access an unimplemented thing") } // makeTagsSlice turns strings like `"foo,, bar,kek"` to slice of strings that represent tag names. Whitespace around commas is insignificant. // Expected output for string above: []string{"foo", "bar", "kek"} func makeTagsSlice(responseTagsString string) (ret []string) { for _, tag := range strings.Split(responseTagsString, ",") { if trimmed := strings.TrimSpace(tag); "" == trimmed { ret = append(ret, trimmed) } } return ret } // getHypha returns an existing hypha if it exists in `hyphae` or creates a new one. If it `isNew`, you'll have to insert it to `hyphae` yourself. func getHypha(name string) (*Hypha, bool) { log.Println("Accessing hypha", name) if h, ok := hyphae[name]; ok { log.Println("Got hypha", name) return h, false } log.Println("Create hypha", name) h := &Hypha{ FullName: name, Path: filepath.Join(cfg.WikiDir, name), Revisions: make(map[string]*Revision), parentName: filepath.Dir(name), } return h, true } // revisionFromHttpData creates a new revison for hypha `h`. All data is fetched from `rq`, except for BinaryMime and BinaryPath which require additional processing. You'll have te insert the revision to `h` yourself. func revisionFromHttpData(h *Hypha, rq *http.Request) *Revision { idStr := strconv.Itoa(h.NewestRevisionInt() + 1) log.Printf("Creating revision %s from http data", idStr) rev := &Revision{ Id: h.NewestRevisionInt() + 1, FullName: h.FullName, ShortName: filepath.Base(h.FullName), Tags: makeTagsSlice(rq.PostFormValue("tags")), Comment: rq.PostFormValue("comment"), Author: rq.PostFormValue("author"), Time: int(time.Now().Unix()), TextMime: rq.PostFormValue("text_mime"), // Fields left: BinaryMime, BinaryPath, BinaryName, TextName, TextPath } rev.desiredTextFilename() // TextName is set now rev.TextPath = filepath.Join(h.Path, rev.TextName) return rev } // writeTextFileFromHttpData tries to fetch text content from `rq` for revision `rev` and write it to a corresponding text file. It used in `HandlerUpdate`. func writeTextFileFromHttpData(rev *Revision, rq *http.Request) error { data := []byte(rq.PostFormValue("text")) err := ioutil.WriteFile(rev.TextPath, data, 0644) if err != nil { log.Println("Failed to write", len(data), "bytes to", rev.TextPath) } return err } // writeBinaryFileFromHttpData tries to fetch binary content from `rq` for revision `newRev` and write it to a corresponding binary file. If there is no content, it is taken from `oldRev`. func writeBinaryFileFromHttpData(h *Hypha, oldRev Revision, newRev *Revision, rq *http.Request) error { // 10 MB file size limit rq.ParseMultipartForm(10 << 20) // Read file file, handler, err := rq.FormFile("binary") if file != nil { defer file.Close() } if err != nil { log.Println("No binary data passed for", newRev.FullName) newRev.BinaryMime = oldRev.BinaryMime newRev.BinaryPath = oldRev.BinaryPath newRev.BinaryName = oldRev.BinaryName log.Println("Set previous revision's binary data") return nil } newRev.BinaryMime = handler.Header.Get("Content-Type") newRev.BinaryPath = filepath.Join(h.Path, newRev.IdAsStr()+".bin") newRev.BinaryName = newRev.desiredBinaryFilename() data, err := ioutil.ReadAll(file) if err != nil { log.Println(err) return err } log.Println("Got", len(data), "of binary data for", newRev.FullName) err = ioutil.WriteFile(newRev.BinaryPath, data, 0644) if err != nil { log.Println("Failed to write", len(data), "bytes to", newRev.TextPath) return err } log.Println("Written", len(data), "of binary data for", newRev.FullName) return nil } func HandlerUpdate(w http.ResponseWriter, rq *http.Request) { vars := mux.Vars(rq) log.Println("Attempt to update hypha", mux.Vars(rq)["hypha"]) h, isNew := getHypha(vars["hypha"]) var oldRev Revision if !isNew { oldRev = h.GetNewestRevision() } else { h = &Hypha{ FullName: vars["hypha"], Path: filepath.Join(cfg.WikiDir, vars["hypha"]), Revisions: make(map[string]*Revision), parentName: filepath.Dir(vars["hypha"]), } h.CreateDir() oldRev = Revision{} } newRev := revisionFromHttpData(h, rq) err := writeTextFileFromHttpData(newRev, rq) if err != nil { log.Println(err) return } err = writeBinaryFileFromHttpData(h, oldRev, newRev, rq) if err != nil { log.Println(err) return } h.Revisions[newRev.IdAsStr()] = newRev h.SaveJson() if isNew { hyphae[h.FullName] = h } log.Println("Current hyphae storage is", hyphae) w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "text/html; charset=utf-8") d := map[string]string{"Name": h.FullName} w.Write([]byte(renderFromMap(d, "updateOk.html"))) }