From 0467f3a773397cb143774d10f2f8b00db2a65b67 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sat, 20 Feb 2021 19:03:54 +0500 Subject: [PATCH] Split module hyphae into two modules --- http_mutators.go | 38 +++++------ http_readers.go | 5 +- hyphae/backlink.go | 72 --------------------- hyphae/count.go | 13 ++-- hyphae/delete.go | 50 -------------- hyphae/hyphae.go | 117 +++++++++------------------------ hyphae/iterators.go | 57 ++++++++++++++++ hyphae/unattach.go | 64 ------------------ hyphae/upload.go | 122 ----------------------------------- main.go | 7 +- shroom/backlink.go | 55 ++++++++++++++++ shroom/can.go | 92 ++++++++++++++++++++++++++ shroom/delete.go | 29 +++++++++ shroom/init.go | 37 +++++++++++ shroom/log.go | 24 +++++++ {hyphae => shroom}/rename.go | 40 ++++-------- shroom/unattach.go | 40 ++++++++++++ shroom/upload.go | 87 +++++++++++++++++++++++++ {hyphae => shroom}/view.go | 13 ++-- 19 files changed, 502 insertions(+), 460 deletions(-) delete mode 100644 hyphae/backlink.go delete mode 100644 hyphae/delete.go create mode 100644 hyphae/iterators.go delete mode 100644 hyphae/unattach.go delete mode 100644 hyphae/upload.go create mode 100644 shroom/backlink.go create mode 100644 shroom/can.go create mode 100644 shroom/delete.go create mode 100644 shroom/init.go create mode 100644 shroom/log.go rename {hyphae => shroom}/rename.go (68%) create mode 100644 shroom/unattach.go create mode 100644 shroom/upload.go rename {hyphae => shroom}/view.go (86%) diff --git a/http_mutators.go b/http_mutators.go index 139a0d5..3e29066 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -8,6 +8,7 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/shroom" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" @@ -29,7 +30,7 @@ func init() { func factoryHandlerAsker( actionPath string, - asker func(*hyphae.Hypha, *user.User) (error, string), + asker func(*user.User, *hyphae.Hypha) (error, string), succTitleTemplate string, succPageTemplate func(*http.Request, string, bool) string, ) func(http.ResponseWriter, *http.Request) { @@ -40,7 +41,7 @@ func factoryHandlerAsker( h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) ) - if err, errtitle := asker(h, u); err != nil { + if err, errtitle := asker(u, h); err != nil { HttpErr( w, http.StatusInternalServerError, @@ -60,27 +61,21 @@ func factoryHandlerAsker( var handlerUnattachAsk = factoryHandlerAsker( "unattach-ask", - func(h *hyphae.Hypha, u *user.User) (error, string) { - return h.CanUnattach(u) - }, + shroom.CanUnattach, "Unattach %s?", templates.UnattachAskHTML, ) var handlerDeleteAsk = factoryHandlerAsker( "delete-ask", - func(h *hyphae.Hypha, u *user.User) (error, string) { - return h.CanDelete(u) - }, + shroom.CanDelete, "Delete %s?", templates.DeleteAskHTML, ) var handlerRenameAsk = factoryHandlerAsker( "rename-ask", - func(h *hyphae.Hypha, u *user.User) (error, string) { - return h.CanRename(u) - }, + shroom.CanRename, "Rename %s?", templates.RenameAskHTML, ) @@ -109,14 +104,14 @@ func factoryHandlerConfirmer( var handlerUnattachConfirm = factoryHandlerConfirmer( "unattach-confirm", func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) { - return h.UnattachHypha(u) + return shroom.UnattachHypha(u, h) }, ) var handlerDeleteConfirm = factoryHandlerConfirmer( "delete-confirm", func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) { - return h.DeleteHypha(u) + return shroom.DeleteHypha(u, h) }, ) @@ -128,7 +123,7 @@ var handlerRenameConfirm = factoryHandlerConfirmer( recursive = rq.PostFormValue("recursive") == "true" newHypha = hyphae.ByName(newName) ) - return oldHypha.RenameHypha(newHypha, recursive, u) + return shroom.RenameHypha(oldHypha, newHypha, recursive, u) }, ) @@ -143,14 +138,14 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { err error u = user.FromRequest(rq) ) - if err, errtitle := h.CanEdit(u); err != nil { + if err, errtitle := shroom.CanEdit(u, h); err != nil { HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, err.Error()) return } if h.Exists { - textAreaFill, err = h.FetchTextPart() + textAreaFill, err = shroom.FetchTextPart(h) if err != nil { log.Println(err) HttpErr(w, http.StatusInternalServerError, hyphaName, @@ -183,7 +178,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { ) if action != "Preview" { - hop, errtitle = h.UploadText([]byte(textData), u) + hop, errtitle = shroom.UploadText(h, []byte(textData), u) } if hop.HasErrors() { @@ -220,7 +215,12 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { u = user.FromRequest(rq) file, handler, err = rq.FormFile("binary") ) - if err, errtitle := h.CanAttach(err, u); err != nil { + if err != nil { + HttpErr(w, http.StatusInternalServerError, hyphaName, + "Error", + err.Error()) + } + if err, errtitle := shroom.CanAttach(u, h); err != nil { HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, err.Error()) @@ -237,7 +237,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { } var ( mime = handler.Header.Get("Content-Type") - hop, errtitle = h.UploadBinary(mime, file, u) + hop, errtitle = shroom.UploadBinary(h, mime, file, u) ) if hop.HasErrors() { diff --git a/http_readers.go b/http_readers.go index c33025c..d99a865 100644 --- a/http_readers.go +++ b/http_readers.go @@ -13,6 +13,7 @@ import ( "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/mimetype" + "github.com/bouncepaw/mycorrhiza/shroom" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/tree" "github.com/bouncepaw/mycorrhiza/user" @@ -100,7 +101,7 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { openGraph = md.OpenGraphHTML() } if !os.IsNotExist(errB) { - contents = h.BinaryHtmlBlock() + contents + contents = shroom.BinaryHtmlBlock(h) + contents } } treeHTML, subhyphaeHTML, prevHypha, nextHypha := tree.Tree(hyphaName) @@ -112,7 +113,7 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { contents, treeHTML, subhyphaeHTML, - h.BackLinkEntriesHTML(), + shroom.BackLinkEntriesHTML(h), prevHypha, nextHypha, hasAmnt), u, diff --git a/hyphae/backlink.go b/hyphae/backlink.go deleted file mode 100644 index 21c79ad..0000000 --- a/hyphae/backlink.go +++ /dev/null @@ -1,72 +0,0 @@ -package hyphae - -import ( - "fmt" - "io/ioutil" - - "github.com/bouncepaw/mycorrhiza/link" - "github.com/bouncepaw/mycorrhiza/markup" - "github.com/bouncepaw/mycorrhiza/util" -) - -func (h *Hypha) BackLinkEntriesHTML() (html string) { - for _, backlinkHypha := range h.BackLinks { - _ = link.Link{} - html += fmt.Sprintf(`
  • - %s`, backlinkHypha.Name, util.BeautifulName(backlinkHypha.Name)) - } - return -} - -func (h *Hypha) outlinksThis(oh *Hypha) bool { - for _, outlink := range h.OutLinks { - if outlink == oh { - return true - } - } - return false -} - -func (h *Hypha) backlinkedBy(oh *Hypha) bool { - for _, backlink := range h.BackLinks { - if backlink == oh { - return true - } - } - return false -} - -// FindAllBacklinks iterates over all hyphae that have text parts, sets their outlinks and then sets backlinks. -func FindAllBacklinks() { - for h := range FilterTextHyphae(YieldExistingHyphae()) { - findBacklinkWorker(h) - } -} - -func findBacklinkWorker(h *Hypha) { - h.Lock() - defer h.Unlock() - - textContents, err := ioutil.ReadFile(h.TextPath) - if err == nil { - for outlink := range markup.Doc(h.Name, string(textContents)).OutLinks() { - outlink := outlink - outlinkHypha := ByName(outlink) - if outlinkHypha == h { - continue - } - - outlinkHypha.Lock() - if !outlinkHypha.backlinkedBy(h) { - outlinkHypha.BackLinks = append(outlinkHypha.BackLinks, h) - outlinkHypha.InsertIfNewKeepExistence() - } - outlinkHypha.Unlock() - - // Insert outlinkHypha if unique - if !h.outlinksThis(outlinkHypha) { - h.OutLinks = append(h.OutLinks, outlinkHypha) - } - } - } -} diff --git a/hyphae/count.go b/hyphae/count.go index d090606..7d20f93 100644 --- a/hyphae/count.go +++ b/hyphae/count.go @@ -10,21 +10,21 @@ var count = struct { sync.Mutex }{} -// Set the value of hyphae count to zero. +// Set the value of hyphae count to zero. Use when reloading hyphae. func ResetCount() { - count.Lock() - count.value = 0 - count.Unlock() + count.Lock() + count.value = 0 + count.Unlock() } -// Increment the value of hyphae count. +// Increment the value of the hyphae counter. Use when creating new hyphae or loading hyphae from disk. func IncrementCount() { count.Lock() count.value++ count.Unlock() } -// Decrement the value of hyphae count. +// Decrement the value of the hyphae counter. Use when deleting existing hyphae. func DecrementCount() { count.Lock() count.value-- @@ -33,6 +33,5 @@ func DecrementCount() { // Count how many hyphae there are. func Count() int { - // it is concurrent-safe to not lock here, right? return count.value } diff --git a/hyphae/delete.go b/hyphae/delete.go deleted file mode 100644 index 3ca6aac..0000000 --- a/hyphae/delete.go +++ /dev/null @@ -1,50 +0,0 @@ -package hyphae - -import ( - "errors" - "fmt" - "log" - - "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/user" -) - -func rejectDeleteLog(h *Hypha, u *user.User, errmsg string) { - log.Printf("Reject delete ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) -} - -// CanDelete checks if given user can delete given hypha. -func (h *Hypha) CanDelete(u *user.User) (err error, errtitle string) { - // First, check if can unattach at all - if !u.CanProceed("delete-confirm") { - rejectDeleteLog(h, u, "no rights") - return errors.New("Not enough rights to delete, you must be a moderator"), "Not enough rights" - } - - if !h.Exists { - rejectDeleteLog(h, u, "does not exist") - return errors.New("Cannot delete this hypha because it does not exist"), "Does not exist" - } - - return nil, "" -} - -// DeleteHypha deletes hypha and makes a history record about that. -func (h *Hypha) DeleteHypha(u *user.User) (hop *history.HistoryOp, errtitle string) { - hop = history.Operation(history.TypeDeleteHypha) - - if err, errtitle := h.CanDelete(u); errtitle != "" { - hop.WithError(err) - return hop, errtitle - } - - hop. - WithFilesRemoved(h.TextPath, h.BinaryPath). - WithMsg(fmt.Sprintf("Delete ‘%s’", h.Name)). - WithUser(u). - Apply() - if len(hop.Errs) == 0 { - h.delete() - } - return hop, "" -} diff --git a/hyphae/hyphae.go b/hyphae/hyphae.go index 1c4496c..ce2a3d9 100644 --- a/hyphae/hyphae.go +++ b/hyphae/hyphae.go @@ -1,44 +1,12 @@ +// The `hyphae` package is for the Hypha type, hypha storage and stuff like that. It shall not depend on mycorrhiza modules other than util. package hyphae import ( - "errors" "log" "regexp" - "strings" "sync" - - "github.com/bouncepaw/mycorrhiza/markup" - "github.com/bouncepaw/mycorrhiza/util" ) -func init() { - markup.HyphaExists = func(hyphaName string) bool { - return ByName(hyphaName).Exists - } - markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { - if h := ByName(hyphaName); h.Exists { - rawText, err = h.FetchTextPart() - if h.BinaryPath != "" { - binaryBlock = h.BinaryHtmlBlock() - } - } else { - err = errors.New("Hypha " + hyphaName + " does not exist") - } - return - } - markup.HyphaIterate = func(λ func(string)) { - for h := range YieldExistingHyphae() { - λ(h.Name) - } - } - markup.HyphaImageForOG = func(hyphaName string) string { - if h := ByName(hyphaName); h.Exists && h.BinaryPath != "" { - return util.URL + "/binary/" + hyphaName - } - return util.URL + "/favicon.ico" - } -} - // HyphaPattern is a pattern which all hyphae must match. var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) @@ -56,57 +24,6 @@ type Hypha struct { var byNames = make(map[string]*Hypha) var byNamesMutex = sync.Mutex{} -// YieldExistingHyphae iterates over all hyphae and yields all existing ones. -func YieldExistingHyphae() chan *Hypha { - ch := make(chan *Hypha) - go func() { - for _, h := range byNames { - if h.Exists { - ch <- h - } - } - close(ch) - }() - return ch -} - -// FilterTextHyphae filters the source channel and yields only those hyphae than have text parts. -func FilterTextHyphae(src chan *Hypha) chan *Hypha { - sink := make(chan *Hypha) - go func() { - for h := range src { - if h.TextPath != "" { - sink <- h - } - } - close(sink) - }() - return sink -} - -// Subhyphae returns slice of subhyphae. -func (h *Hypha) Subhyphae() []*Hypha { - hyphae := []*Hypha{} - for subh := range YieldExistingHyphae() { - if strings.HasPrefix(subh.Name, h.Name+"/") { - hyphae = append(hyphae, subh) - } - } - return hyphae -} - -// AreFreeNames checks if all given `hyphaNames` are not taken. -func AreFreeNames(hyphaNames ...string) (firstFailure string, ok bool) { - for h := range YieldExistingHyphae() { - for _, hn := range hyphaNames { - if hn == h.Name { - return hn, false - } - } - } - return "", true -} - // EmptyHypha returns an empty hypha struct with given name. func EmptyHypha(hyphaName string) *Hypha { return &Hypha{ @@ -164,7 +81,7 @@ func (h *Hypha) InsertIfNewKeepExistence() { } } -func (h *Hypha) delete() { +func (h *Hypha) Delete() { byNamesMutex.Lock() h.Lock() delete(byNames, h.Name) @@ -173,7 +90,7 @@ func (h *Hypha) delete() { h.Unlock() } -func (h *Hypha) renameTo(newName string) { +func (h *Hypha) RenameTo(newName string) { byNamesMutex.Lock() h.Lock() delete(byNames, h.Name) @@ -195,3 +112,31 @@ func (h *Hypha) MergeIn(oh *Hypha) { h.BinaryPath = oh.BinaryPath } } + +// Link related stuff: + +func (h *Hypha) AddOutLink(oh *Hypha) (added bool) { + h.Lock() + defer h.Unlock() + + for _, outlink := range h.OutLinks { + if outlink == oh { + return false + } + } + h.OutLinks = append(h.OutLinks, oh) + return true +} + +func (h *Hypha) AddBackLink(bh *Hypha) (added bool) { + h.Lock() + defer h.Unlock() + + for _, backlink := range h.BackLinks { + if backlink == h { + return false + } + } + h.BackLinks = append(h.BackLinks, bh) + return true +} diff --git a/hyphae/iterators.go b/hyphae/iterators.go new file mode 100644 index 0000000..8e5fcd5 --- /dev/null +++ b/hyphae/iterators.go @@ -0,0 +1,57 @@ +// File `iterators.go` contains stuff that iterates over hyphae. +package hyphae + +import ( + "strings" +) + +// YieldExistingHyphae iterates over all hyphae and yields all existing ones. +func YieldExistingHyphae() chan *Hypha { + ch := make(chan *Hypha) + go func() { + for _, h := range byNames { + if h.Exists { + ch <- h + } + } + close(ch) + }() + return ch +} + +// FilterTextHyphae filters the source channel and yields only those hyphae than have text parts. +func FilterTextHyphae(src chan *Hypha) chan *Hypha { + sink := make(chan *Hypha) + go func() { + for h := range src { + if h.TextPath != "" { + sink <- h + } + } + close(sink) + }() + return sink +} + +// Subhyphae returns slice of subhyphae. +func (h *Hypha) Subhyphae() []*Hypha { + hyphae := []*Hypha{} + for subh := range YieldExistingHyphae() { + if strings.HasPrefix(subh.Name, h.Name+"/") { + hyphae = append(hyphae, subh) + } + } + return hyphae +} + +// AreFreeNames checks if all given `hyphaNames` are not taken. If they are not taken, `ok` is true. If not, `firstFailure` is the name of the first met hypha that is not free. +func AreFreeNames(hyphaNames ...string) (firstFailure string, ok bool) { + for h := range YieldExistingHyphae() { + for _, hn := range hyphaNames { + if hn == h.Name { + return hn, false + } + } + } + return "", true +} diff --git a/hyphae/unattach.go b/hyphae/unattach.go deleted file mode 100644 index 75c7baa..0000000 --- a/hyphae/unattach.go +++ /dev/null @@ -1,64 +0,0 @@ -package hyphae - -import ( - "errors" - "fmt" - "log" - - "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/user" -) - -func rejectUnattachLog(h *Hypha, u *user.User, errmsg string) { - log.Printf("Reject unattach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) -} - -// CanUnattach checks if given user can unattach given hypha. If they can, `errtitle` is an empty string and `err` is nil. If they cannot, `errtitle` is not an empty string, and `err` is an error. -func (h *Hypha) CanUnattach(u *user.User) (err error, errtitle string) { - if !u.CanProceed("unattach-confirm") { - rejectUnattachLog(h, u, "no rights") - return errors.New("Not enough rights to unattach, you must be a trusted editor"), "Not enough rights" - } - - if !h.Exists { - rejectUnattachLog(h, u, "does not exist") - return errors.New("Cannot unattach this hypha because it does not exist"), "Does not exist" - } - - if h.BinaryPath == "" { - rejectUnattachLog(h, u, "no amnt") - return errors.New("Cannot unattach this hypha because it has no attachment"), "No attachment" - } - - return nil, "" -} - -// UnattachHypha unattaches hypha and makes a history record about that. -func (h *Hypha) UnattachHypha(u *user.User) (hop *history.HistoryOp, errtitle string) { - hop = history.Operation(history.TypeUnattachHypha) - - if err, errtitle := h.CanUnattach(u); errtitle != "" { - hop.WithError(err) - return hop, errtitle - } - - hop. - WithFilesRemoved(h.BinaryPath). - WithMsg(fmt.Sprintf("Unattach ‘%s’", h.Name)). - WithUser(u). - Apply() - - if len(hop.Errs) > 0 { - rejectUnattachLog(h, u, "fail") - return hop.WithError(errors.New(fmt.Sprintf("Could not unattach this hypha due to internal server errors: %v", hop.Errs))), "Error" - } - - if h.BinaryPath != "" { - h.BinaryPath = "" - } - // If nothing is left of the hypha - if h.TextPath == "" { - h.delete() - } - return hop, "" -} diff --git a/hyphae/upload.go b/hyphae/upload.go deleted file mode 100644 index fcf2905..0000000 --- a/hyphae/upload.go +++ /dev/null @@ -1,122 +0,0 @@ -package hyphae - -import ( - "errors" - "fmt" - "io/ioutil" - "log" - "mime/multipart" - "os" - "path/filepath" - - "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/mimetype" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" -) - -func rejectEditLog(h *Hypha, u *user.User, errmsg string) { - log.Printf("Reject edit ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) -} - -func rejectAttachLog(h *Hypha, u *user.User, errmsg string) { - log.Printf("Reject attach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) -} - -func (h *Hypha) CanEdit(u *user.User) (err error, errtitle string) { - if !u.CanProceed("edit") { - rejectEditLog(h, u, "no rights") - return errors.New("You must be an editor to edit pages."), "Not enough rights" - } - return nil, "" -} - -func (h *Hypha) CanUploadThat(data []byte, u *user.User) (err error, errtitle string) { - if len(data) == 0 { - return errors.New("No text data passed"), "Empty" - } - return nil, "" -} - -func (h *Hypha) UploadText(textData []byte, u *user.User) (hop *history.HistoryOp, errtitle string) { - hop = history.Operation(history.TypeEditText) - if h.Exists { - hop.WithMsg(fmt.Sprintf("Edit ‘%s’", h.Name)) - } else { - hop.WithMsg(fmt.Sprintf("Create ‘%s’", h.Name)) - } - - if err, errtitle := h.CanEdit(u); err != nil { - return hop.WithError(err), errtitle - } - if err, errtitle := h.CanUploadThat(textData, u); err != nil { - return hop.WithError(err), errtitle - } - - return h.uploadHelp(hop, ".myco", textData, u) -} - -func (h *Hypha) CanAttach(err error, u *user.User) (error, string) { - if !u.CanProceed("upload-binary") { - rejectAttachLog(h, u, "no rights") - return errors.New("You must be an editor to upload attachments."), "Not enough rights" - } - - if err != nil { - rejectAttachLog(h, u, err.Error()) - return errors.New("No binary data passed"), err.Error() - } - return nil, "" -} - -func (h *Hypha) UploadBinary(mime string, file multipart.File, u *user.User) (*history.HistoryOp, string) { - var ( - hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", h.Name, mime)) - data, err = ioutil.ReadAll(file) - ) - - if err != nil { - return hop.WithError(err), err.Error() - } - if err, errtitle := h.CanEdit(u); err != nil { - return hop.WithError(err), errtitle - } - if err, errtitle := h.CanUploadThat(data, u); err != nil { - return hop.WithError(err), errtitle - } - - return h.uploadHelp(hop, mimetype.ToExtension(mime), data, u) -} - -// uploadHelp is a helper function for UploadText and UploadBinary -func (h *Hypha) uploadHelp(hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) { - var ( - fullPath = filepath.Join(util.WikiDir, h.Name+ext) - originalFullPath = &h.TextPath - ) - if hop.Type == history.TypeEditBinary { - originalFullPath = &h.BinaryPath - } - - if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { - return hop.WithError(err), err.Error() - } - - if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { - return hop.WithError(err), err.Error() - } - - if h.Exists && *originalFullPath != fullPath && *originalFullPath != "" { - if err := history.Rename(*originalFullPath, fullPath); err != nil { - return hop.WithError(err), err.Error() - } - log.Println("Move", *originalFullPath, "to", fullPath) - } - - h.InsertIfNew() - if h.Exists && h.TextPath != "" && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) { - return hop.Abort(), "No changes" - } - *originalFullPath = fullPath - return hop.WithFiles(fullPath).WithUser(u).Apply(), "" -} diff --git a/main.go b/main.go index 7db388c..f35ff55 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/mimetype" + "github.com/bouncepaw/mycorrhiza/shroom" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" @@ -85,7 +86,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { log.Println("Rejected", rq.URL) return } - hyphae.SetHeaderLinks() + shroom.SetHeaderLinks() http.Redirect(w, rq, "/", http.StatusSeeOther) } @@ -175,11 +176,11 @@ func main() { log.Println("Wiki storage directory is", WikiDir) hyphae.Index(WikiDir) log.Println("Indexed", hyphae.Count(), "hyphae") - hyphae.FindAllBacklinks() + shroom.FindAllBacklinks() log.Println("Found all backlinks") history.Start(WikiDir) - hyphae.SetHeaderLinks() + shroom.SetHeaderLinks() go handleGemini() diff --git a/shroom/backlink.go b/shroom/backlink.go new file mode 100644 index 0000000..1d8714a --- /dev/null +++ b/shroom/backlink.go @@ -0,0 +1,55 @@ +package shroom + +import ( + "fmt" + "io/ioutil" + "log" + "sync" + + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/link" + "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/util" +) + +func BackLinkEntriesHTML(h *hyphae.Hypha) (html string) { + for _, backlinkHypha := range h.BackLinks { + _ = link.Link{} + html += fmt.Sprintf(`
  • + %s`, backlinkHypha.Name, util.BeautifulName(backlinkHypha.Name)) + } + return +} + +// FindAllBacklinks iterates over all hyphae that have text parts, sets their outlinks and then sets backlinks. +func FindAllBacklinks() { + for h := range hyphae.FilterTextHyphae(hyphae.YieldExistingHyphae()) { + findBacklinkWorker(h) + } +} + +func findBacklinkWorker(h *hyphae.Hypha) { + var ( + wg sync.WaitGroup + textContents, err = ioutil.ReadFile(h.TextPath) + ) + if err == nil { + for outlink := range markup.Doc(h.Name, string(textContents)).OutLinks() { + go func() { + wg.Add(1) + outlinkHypha := hyphae.ByName(outlink) + if outlinkHypha == h { + return + } + + outlinkHypha.AddBackLink(h) + outlinkHypha.InsertIfNewKeepExistence() + h.AddOutLink(outlinkHypha) + wg.Done() + }() + } + wg.Wait() + } else { + log.Println("Error when reading text contents of ‘%s’: %s", h.Name, err.Error()) + } +} diff --git a/shroom/can.go b/shroom/can.go new file mode 100644 index 0000000..152d896 --- /dev/null +++ b/shroom/can.go @@ -0,0 +1,92 @@ +package shroom + +import ( + "errors" + + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/user" +) + +func canFactory( + rejectLogger func(*hyphae.Hypha, *user.User, string), + action string, + dispatcher func(*hyphae.Hypha, *user.User) (string, string), + noRightsMsg string, + notExistsMsg string, + careAboutExistince bool, +) func(*user.User, *hyphae.Hypha) (error, string) { + return func(u *user.User, h *hyphae.Hypha) (error, string) { + if !u.CanProceed(action) { + rejectLogger(h, u, "no rights") + return errors.New(noRightsMsg), "Not enough rights" + } + + if careAboutExistince && !h.Exists { + rejectLogger(h, u, "does not exist") + return errors.New(notExistsMsg), "Does not exist" + } + + if dispatcher == nil { + return nil, "" + } + errmsg, errtitle := dispatcher(h, u) + if errtitle == "" { + return nil, "" + } + return errors.New(errmsg), errtitle + } +} + +var ( + CanDelete = canFactory( + rejectDeleteLog, + "delete-confirm", + nil, + "Not enough rights to delete, you must be a moderator", + "Cannot delete this hypha because it does not exist", + true, + ) + + CanRename = canFactory( + rejectRenameLog, + "rename-confirm", + nil, + "Not enough rights to rename, you must be a trusted editor", + "Cannot rename this hypha because it does not exist", + true, + ) + + CanUnattach = canFactory( + rejectUnattachLog, + "unattach-confirm", + func(h *hyphae.Hypha, u *user.User) (errmsg, errtitle string) { + if h.BinaryPath == "" { + rejectUnattachLog(h, u, "no amnt") + return "Cannot unattach this hypha because it has no attachment", "No attachment" + } + + return "", "" + }, + "Not enough rights to unattach, you must be a trusted editor", + "Cannot unattach this hypha because it does not exist", + true, + ) + + CanEdit = canFactory( + rejectEditLog, + "upload-text", + nil, + "You must be an editor to edit a hypha", + "You cannot edit a hypha that does not exist", + false, + ) + + CanAttach = canFactory( + rejectAttachLog, + "upload-binary", + nil, + "You must be an editor to attach a hypha", + "You cannot attach a hypha that does not exist", + false, + ) +) diff --git a/shroom/delete.go b/shroom/delete.go new file mode 100644 index 0000000..f88f017 --- /dev/null +++ b/shroom/delete.go @@ -0,0 +1,29 @@ +package shroom + +import ( + "fmt" + + "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/user" +) + +// DeleteHypha deletes hypha and makes a history record about that. +func DeleteHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) { + hop = history.Operation(history.TypeDeleteHypha) + + if err, errtitle := CanDelete(u, h); errtitle != "" { + hop.WithError(err).Abort() + return hop, errtitle + } + + hop. + WithFilesRemoved(h.TextPath, h.BinaryPath). + WithMsg(fmt.Sprintf("Delete ‘%s’", h.Name)). + WithUser(u). + Apply() + if !hop.HasErrors() { + h.Delete() + } + return hop, "" +} diff --git a/shroom/init.go b/shroom/init.go new file mode 100644 index 0000000..c3c4cc1 --- /dev/null +++ b/shroom/init.go @@ -0,0 +1,37 @@ +package shroom + +import ( + "errors" + + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/util" +) + +func init() { + markup.HyphaExists = func(hyphaName string) bool { + return hyphae.ByName(hyphaName).Exists + } + markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { + if h := hyphae.ByName(hyphaName); h.Exists { + rawText, err = FetchTextPart(h) + if h.BinaryPath != "" { + binaryBlock = BinaryHtmlBlock(h) + } + } else { + err = errors.New("Hypha " + hyphaName + " does not exist") + } + return + } + markup.HyphaIterate = func(λ func(string)) { + for h := range hyphae.YieldExistingHyphae() { + λ(h.Name) + } + } + markup.HyphaImageForOG = func(hyphaName string) string { + if h := hyphae.ByName(hyphaName); h.Exists && h.BinaryPath != "" { + return util.URL + "/binary/" + hyphaName + } + return util.URL + "/favicon.ico" + } +} diff --git a/shroom/log.go b/shroom/log.go new file mode 100644 index 0000000..0bfee28 --- /dev/null +++ b/shroom/log.go @@ -0,0 +1,24 @@ +package shroom + +import ( + "log" + + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/user" +) + +func rejectDeleteLog(h *hyphae.Hypha, u *user.User, errmsg string) { + log.Printf("Reject delete ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) +} +func rejectRenameLog(h *hyphae.Hypha, u *user.User, errmsg string) { + log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) +} +func rejectUnattachLog(h *hyphae.Hypha, u *user.User, errmsg string) { + log.Printf("Reject unattach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) +} +func rejectEditLog(h *hyphae.Hypha, u *user.User, errmsg string) { + log.Printf("Reject edit ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) +} +func rejectAttachLog(h *hyphae.Hypha, u *user.User, errmsg string) { + log.Printf("Reject attach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) +} diff --git a/hyphae/rename.go b/shroom/rename.go similarity index 68% rename from hyphae/rename.go rename to shroom/rename.go index 4831a21..7689538 100644 --- a/hyphae/rename.go +++ b/shroom/rename.go @@ -1,35 +1,17 @@ -package hyphae +package shroom import ( "errors" "fmt" - "log" "regexp" "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) -func rejectRenameLog(h *Hypha, u *user.User, errmsg string) { - log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg) -} - -func (h *Hypha) CanRename(u *user.User) (err error, errtitle string) { - if !u.CanProceed("rename-confirm") { - rejectRenameLog(h, u, "no rights") - return errors.New("Not enough rights to rename, you must be a trusted editor"), "Not enough rights" - } - - if !h.Exists { - rejectRenameLog(h, u, "does not exist") - return errors.New("Cannot rename this hypha because it does not exist"), "Does not exist" - } - - return nil, "" -} - -func canRenameThisToThat(oh *Hypha, nh *Hypha, u *user.User) (err error, errtitle string) { +func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User) (err error, errtitle string) { if nh.Exists { rejectRenameLog(oh, u, fmt.Sprintf("name ‘%s’ taken already", nh.Name)) return errors.New(fmt.Sprintf("Hypha named %[1]s already exists, cannot rename", nh.Name)), "Name taken" @@ -40,7 +22,7 @@ func canRenameThisToThat(oh *Hypha, nh *Hypha, u *user.User) (err error, errtitl return errors.New("No new name is given"), "No name given" } - if !HyphaPattern.MatchString(nh.Name) { + if !hyphae.HyphaPattern.MatchString(nh.Name) { rejectRenameLog(oh, u, fmt.Sprintf("new name ‘%s’ invalid", nh.Name)) return errors.New("Invalid new name. Names cannot contain characters ^?!:#@><*|\"\\'&%"), "Invalid name" } @@ -49,12 +31,12 @@ func canRenameThisToThat(oh *Hypha, nh *Hypha, u *user.User) (err error, errtitl } // RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way. -func (h *Hypha) RenameHypha(newHypha *Hypha, recursive bool, u *user.User) (hop *history.HistoryOp, errtitle string) { +func RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *user.User) (hop *history.HistoryOp, errtitle string) { newHypha.Lock() defer newHypha.Unlock() hop = history.Operation(history.TypeRenameHypha) - if err, errtitle := h.CanRename(u); errtitle != "" { + if err, errtitle := CanRename(u, h); errtitle != "" { hop.WithError(err) return hop, errtitle } @@ -85,7 +67,7 @@ func (h *Hypha) RenameHypha(newHypha *Hypha, recursive bool, u *user.User) (hop Apply() if len(hop.Errs) == 0 { for _, h := range hyphaeToRename { - h.renameTo(replaceName(h.Name)) + h.RenameTo(replaceName(h.Name)) h.Lock() h.TextPath = replaceName(h.TextPath) h.BinaryPath = replaceName(h.BinaryPath) @@ -95,15 +77,15 @@ func (h *Hypha) RenameHypha(newHypha *Hypha, recursive bool, u *user.User) (hop return hop, "" } -func findHyphaeToRename(superhypha *Hypha, recursive bool) []*Hypha { - hyphae := []*Hypha{superhypha} +func findHyphaeToRename(superhypha *hyphae.Hypha, recursive bool) []*hyphae.Hypha { + hyphae := []*hyphae.Hypha{superhypha} if recursive { hyphae = append(hyphae, superhypha.Subhyphae()...) } return hyphae } -func renamingPairs(hyphaeToRename []*Hypha, replaceName func(string) string) (map[string]string, error) { +func renamingPairs(hyphaeToRename []*hyphae.Hypha, replaceName func(string) string) (map[string]string, error) { renameMap := make(map[string]string) newNames := make([]string, len(hyphaeToRename)) for _, h := range hyphaeToRename { @@ -117,7 +99,7 @@ func renamingPairs(hyphaeToRename []*Hypha, replaceName func(string) string) (ma } h.RUnlock() } - if firstFailure, ok := AreFreeNames(newNames...); !ok { + if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok { return nil, errors.New("Hypha " + firstFailure + " already exists") } return renameMap, nil diff --git a/shroom/unattach.go b/shroom/unattach.go new file mode 100644 index 0000000..5ed5b0f --- /dev/null +++ b/shroom/unattach.go @@ -0,0 +1,40 @@ +package shroom + +import ( + "errors" + "fmt" + + "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/user" +) + +// UnattachHypha unattaches hypha and makes a history record about that. +func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) { + hop = history.Operation(history.TypeUnattachHypha) + + if err, errtitle := CanUnattach(u, h); errtitle != "" { + hop.WithError(err).Abort() + return hop, errtitle + } + + hop. + WithFilesRemoved(h.BinaryPath). + WithMsg(fmt.Sprintf("Unattach ‘%s’", h.Name)). + WithUser(u). + Apply() + + if len(hop.Errs) > 0 { + rejectUnattachLog(h, u, "fail") + return hop.WithError(errors.New(fmt.Sprintf("Could not unattach this hypha due to internal server errors: %v", hop.Errs))), "Error" + } + + if h.BinaryPath != "" { + h.BinaryPath = "" + } + // If nothing is left of the hypha + if h.TextPath == "" { + h.Delete() + } + return hop, "" +} diff --git a/shroom/upload.go b/shroom/upload.go new file mode 100644 index 0000000..08634b2 --- /dev/null +++ b/shroom/upload.go @@ -0,0 +1,87 @@ +package shroom + +import ( + "errors" + "fmt" + "io/ioutil" + "log" + "mime/multipart" + "os" + "path/filepath" + + "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/mimetype" + "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/util" +) + +func UploadText(h *hyphae.Hypha, data []byte, u *user.User) (hop *history.HistoryOp, errtitle string) { + hop = history.Operation(history.TypeEditText) + if h.Exists { + hop.WithMsg(fmt.Sprintf("Edit ‘%s’", h.Name)) + } else { + hop.WithMsg(fmt.Sprintf("Create ‘%s’", h.Name)) + } + + if err, errtitle := CanEdit(u, h); err != nil { + return hop.WithError(err), errtitle + } + if len(data) == 0 { + return hop.WithError(errors.New("No data passed")), "Empty" + } + + return uploadHelp(h, hop, ".myco", data, u) +} + +func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.User) (*history.HistoryOp, string) { + var ( + hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", h.Name, mime)) + data, err = ioutil.ReadAll(file) + ) + + if err != nil { + return hop.WithError(err), err.Error() + } + if err, errtitle := CanAttach(u, h); err != nil { + return hop.WithError(err), errtitle + } + if len(data) == 0 { + return hop.WithError(errors.New("No data passed")), "Empty" + } + + return uploadHelp(h, hop, mimetype.ToExtension(mime), data, u) +} + +// uploadHelp is a helper function for UploadText and UploadBinary +func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) { + var ( + fullPath = filepath.Join(util.WikiDir, h.Name+ext) + originalFullPath = &h.TextPath + ) + if hop.Type == history.TypeEditBinary { + originalFullPath = &h.BinaryPath + } + + if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { + return hop.WithError(err), err.Error() + } + + if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { + return hop.WithError(err), err.Error() + } + + if h.Exists && *originalFullPath != fullPath && *originalFullPath != "" { + if err := history.Rename(*originalFullPath, fullPath); err != nil { + return hop.WithError(err), err.Error() + } + log.Println("Move", *originalFullPath, "to", fullPath) + } + + h.InsertIfNew() + if h.Exists && h.TextPath != "" && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) { + return hop.Abort(), "No changes" + } + *originalFullPath = fullPath + return hop.WithFiles(fullPath).WithUser(u).Apply(), "" +} diff --git a/hyphae/view.go b/shroom/view.go similarity index 86% rename from hyphae/view.go rename to shroom/view.go index e53ab4f..eca6751 100644 --- a/hyphae/view.go +++ b/shroom/view.go @@ -1,4 +1,4 @@ -package hyphae +package shroom import ( "fmt" @@ -6,12 +6,13 @@ import ( "os" "path/filepath" + "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/util" ) // FetchTextPart tries to read text file of the given hypha. If there is no file, empty string is returned. -func (h *Hypha) FetchTextPart() (string, error) { +func FetchTextPart(h *hyphae.Hypha) (string, error) { if h.TextPath == "" { return "", nil } @@ -25,7 +26,7 @@ func (h *Hypha) FetchTextPart() (string, error) { } // binaryHtmlBlock creates an html block for binary part of the hypha. -func (h *Hypha) BinaryHtmlBlock() string { +func BinaryHtmlBlock(h *hyphae.Hypha) string { switch filepath.Ext(h.BinaryPath) { case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico": return fmt.Sprintf(` @@ -35,7 +36,7 @@ func (h *Hypha) BinaryHtmlBlock() string { case ".ogg", ".webm", ".mp4": return fmt.Sprintf(`
    -