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(`
-