diff --git a/handlers.go b/handlers.go index 5f3dfac..56a6399 100644 --- a/handlers.go +++ b/handlers.go @@ -2,8 +2,13 @@ package main import ( "github.com/gorilla/mux" + "io/ioutil" "log" "net/http" + "path/filepath" + "strconv" + "strings" + "time" ) // Boilerplate code present in many handlers. Good to have it. @@ -62,7 +67,118 @@ func HandlerRename(w http.ResponseWriter, r *http.Request) { log.Println("Attempt to access an unimplemented thing") } -func HandlerUpdate(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - log.Println("Attempt to access an unimplemented thing") +func makeTagsSlice(responseTagsString string) (ret []string) { + // `responseTagsString` is string like "foo,, bar,kek". Whitespace around commas is insignificant. Expected output: []string{"foo", "bar", "kek"} + for _, tag := range strings.Split(responseTagsString, ",") { + if trimmed := strings.TrimSpace(tag); "" == trimmed { + ret = append(ret, trimmed) + } + } + return ret +} + +// Return an existing hypha it exists in `hyphae` or create 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(rootWikiDir, name), + Revisions: make(map[string]*Revision), + parentName: filepath.Dir(name), + } + return h, true +} + +// Create a new revison for hypha `h`. All data is fetched from `r`, except for BinaryMime and BinaryPath which require additional processing. You'll have te insert the revision to `h` yourself. +func revisionFromHttpData(h *Hypha, r *http.Request) *Revision { + idStr := strconv.Itoa(h.NewestRevisionInt() + 1) + log.Println(idStr) + rev := &Revision{ + Id: h.NewestRevisionInt() + 1, + FullName: h.FullName, + Tags: makeTagsSlice(r.PostFormValue("tags")), + Comment: r.PostFormValue("comment"), + Author: r.PostFormValue("author"), + Time: int(time.Now().Unix()), + TextMime: r.PostFormValue("text_mime"), + TextPath: filepath.Join(h.Path, idStr+".txt"), + // Left: BinaryMime, BinaryPath + } + return rev +} + +func writeTextFileFromHttpData(rev *Revision, r *http.Request) error { + data := []byte(r.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 +} + +func writeBinaryFileFromHttpData(h *Hypha, oldRev Revision, newRev *Revision, r *http.Request) error { + // 10 MB file size limit + r.ParseMultipartForm(10 << 20) + // Read file + file, handler, err := r.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 + 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") + 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, r *http.Request) { + vars := mux.Vars(r) + log.Println("Attempt to update hypha", mux.Vars(r)["hypha"]) + h, isNew := getHypha(vars["hypha"]) + oldRev := h.GetNewestRevision() + newRev := revisionFromHttpData(h, r) + + if isNew { + h.CreateDir() + } + err := writeTextFileFromHttpData(newRev, r) + if err != nil { + log.Println(err) + return + } + err = writeBinaryFileFromHttpData(h, oldRev, newRev, r) + if err != nil { + log.Println(err) + return + } + + h.Revisions[newRev.IdAsStr()] = newRev + h.SaveJson() + + log.Println("Current hyphae storage is", hyphae) + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Saved successfully")) } diff --git a/hypha.go b/hypha.go index b29b602..1189a57 100644 --- a/hypha.go +++ b/hypha.go @@ -1,20 +1,24 @@ package main import ( + "encoding/json" "fmt" "io/ioutil" + "log" "net/http" + "os" + "path/filepath" "strconv" "strings" ) type Hypha struct { - FullName string - Path string + FullName string `json:"-"` + Path string `json:"-"` ViewCount int `json:"views"` Deleted bool `json:"deleted"` Revisions map[string]*Revision `json:"revisions"` - ChildrenNames []string + ChildrenNames []string `json:"-"` parentName string } @@ -39,7 +43,15 @@ func (h *Hypha) Name() string { return h.FullName } +func (h *Hypha) GetNewestRevision() Revision { + return *h.Revisions[h.NewestRevision()] +} + func (h *Hypha) NewestRevision() string { + return strconv.Itoa(h.NewestRevisionInt()) +} + +func (h *Hypha) NewestRevisionInt() int { var largest int for k, _ := range h.Revisions { rev, _ := strconv.Atoi(k) @@ -47,16 +59,38 @@ func (h *Hypha) NewestRevision() string { largest = rev } } - return strconv.Itoa(largest) + return largest +} + +func (h *Hypha) MetaJsonPath() string { + return filepath.Join(h.Path, "meta.json") +} + +func (h *Hypha) CreateDir() error { + return os.MkdirAll(h.Path, 0644) } func (h *Hypha) ParentName() string { return h.parentName } +func (h *Hypha) SaveJson() { + data, err := json.Marshal(h) + if err != nil { + log.Println("Failed to create JSON of hypha.", err) + return + } + err = ioutil.WriteFile(h.MetaJsonPath(), data, 0644) + if err != nil { + log.Println("Failed to save JSON of hypha.", err) + return + } + log.Println("Saved JSON data of", h.FullName) +} + func ActionEdit(hyphaName string, w http.ResponseWriter) { w.Header().Set("Content-Type", "text/html; charset=utf-8") - var initContents, initTextMime, initBinaryMime, initTags string + var initContents, initTextMime, initTags string hypha, ok := hyphae[hyphaName] if !ok { initContents = "Describe " + hyphaName + "here." @@ -71,10 +105,9 @@ func ActionEdit(hyphaName string, w http.ResponseWriter) { } initContents = string(contents) initTextMime = newestRev.TextMime - initBinaryMime = newestRev.BinaryMime initTags = strings.Join(newestRev.Tags, ",") } w.WriteHeader(http.StatusOK) - w.Write([]byte(EditHyphaPage(hyphaName, initTextMime, initBinaryMime, initContents, initTags))) + w.Write([]byte(EditHyphaPage(hyphaName, initTextMime, initContents, initTags))) } diff --git a/main.go b/main.go index 0f3189d..b75717d 100644 --- a/main.go +++ b/main.go @@ -101,7 +101,9 @@ func main() { r.Queries("action", "rename", "to", hyphaPattern).Path(hyphaUrl). HandlerFunc(HandlerRename) - r.Queries("action", "update").Path(hyphaUrl). + r.Queries( + "action", "update", + ).Path(hyphaUrl).Methods("POST"). HandlerFunc(HandlerUpdate) r.HandleFunc(hyphaUrl, HandlerView) diff --git a/render.go b/render.go index d8267d2..2ffd963 100644 --- a/render.go +++ b/render.go @@ -24,12 +24,15 @@ func Layout(f map[string]string) string { return fmt.Sprintf(template, f["title"], f["head"], f["header"], f["main"], f["sidebar"], FooterText, f["bodyBottom"]) } -func EditHyphaPage(name, text_mime, binary_mime, content, tags string) string { +func EditHyphaPage(name, text_mime, content, tags string) string { template := ` @@ -67,7 +66,7 @@ func EditHyphaPage(name, text_mime, binary_mime, content, tags string) string { "title": fmt.Sprintf(TitleTemplate, "Edit "+name), "head": DefaultStyles, "header": `

Edit ` + name + `

`, - "main": fmt.Sprintf(template, content, text_mime, binary_mime, "Update "+name, tags), + "main": fmt.Sprintf(template, content, text_mime, "Update "+name, tags), "sidebar": "", "footer": FooterText, } diff --git a/revision.go b/revision.go index ee27137..a546cf7 100644 --- a/revision.go +++ b/revision.go @@ -6,11 +6,13 @@ import ( "io/ioutil" "log" "net/http" + "strconv" ) +// In different places, revision variable is called `r`. But when there is an http.Request as well, the revision becomes `rev`. TODO: name them consistently. type Revision struct { - Id int - FullName string + Id int `json:"-"` + FullName string `json:"-"` Tags []string `json:"tags"` ShortName string `json:"name"` Comment string `json:"comment"` @@ -18,8 +20,12 @@ type Revision struct { Time int `json:"time"` TextMime string `json:"text_mime"` BinaryMime string `json:"binary_mime"` - TextPath string - BinaryPath string + TextPath string `json:"-"` + BinaryPath string `json:"-"` +} + +func (r *Revision) IdAsStr() string { + return strconv.Itoa(r.Id) } // During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data. diff --git a/w/m/Fruit/Apple/2.bin b/w/m/Fruit/Apple/2.bin new file mode 100644 index 0000000..7127c67 Binary files /dev/null and b/w/m/Fruit/Apple/2.bin differ diff --git a/w/m/Fruit/Apple/2.txt b/w/m/Fruit/Apple/2.txt new file mode 100644 index 0000000..77b6494 --- /dev/null +++ b/w/m/Fruit/Apple/2.txt @@ -0,0 +1,3 @@ +Красное яблоко. Для съёмки использовалась белая бумага позади и над яблоком, и фотовспышка SB-600 в 1/4 мощности. + +Source: https://commons.wikimedia.org/wiki/File:Red_Apple.jpg \ No newline at end of file diff --git a/w/m/Fruit/Apple/3.txt b/w/m/Fruit/Apple/3.txt new file mode 100644 index 0000000..84903ab --- /dev/null +++ b/w/m/Fruit/Apple/3.txt @@ -0,0 +1,6 @@ +Mycorrhiza is pure happiness + +Красное яблоко. Для съёмки использовалась белая бумага позади и над яблоком, и фотовспышка SB-600 в 1/4 мощности. + +Source: https://commons.wikimedia.org/wiki/File:Red_Apple.jpg + \ No newline at end of file diff --git a/w/m/Fruit/Apple/meta.json b/w/m/Fruit/Apple/meta.json index 732f242..b47c26a 100644 --- a/w/m/Fruit/Apple/meta.json +++ b/w/m/Fruit/Apple/meta.json @@ -1,13 +1 @@ -{ -"revisions":{ - "1":{ - "name": "Apple", - "time": 1591639464, - "author": "bouncepaw", - "comment": "add apple pic hehehe", - "tags": ["img"], - "text_mime": "text/plain", - "binary_mime": "image/jpeg" - } -} -} +{"views":0,"deleted":false,"revisions":{"1":{"tags":["img"],"name":"Apple","comment":"add apple pic hehehe","author":"bouncepaw","time":1591639464,"text_mime":"text/plain","binary_mime":"image/jpeg"},"2":{"tags":null,"name":"","comment":"Update Fruit/Apple","author":"","time":1592570366,"text_mime":"text/plain","binary_mime":"image/jpeg"},"3":{"tags":null,"name":"","comment":"Test fs dumping","author":"","time":1592570926,"text_mime":"text/plain","binary_mime":"image/jpeg"}}} \ No newline at end of file