1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-13 05:50:27 +00:00

Split module hyphae into two modules

This commit is contained in:
bouncepaw 2021-02-20 19:03:54 +05:00
parent 345efef35a
commit 0467f3a773
19 changed files with 502 additions and 460 deletions

View File

@ -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() {

View File

@ -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,

View File

@ -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(`<li class="backlinks__entry">
<a class="backlinks__link" href="/hypha/%s">%s</a>`, 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)
}
}
}
}

View File

@ -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
}

View File

@ -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, ""
}

View File

@ -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
}

57
hyphae/iterators.go Normal file
View File

@ -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
}

View File

@ -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: <code>%v</code>", hop.Errs))), "Error"
}
if h.BinaryPath != "" {
h.BinaryPath = ""
}
// If nothing is left of the hypha
if h.TextPath == "" {
h.delete()
}
return hop, ""
}

View File

@ -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(), ""
}

View File

@ -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()

55
shroom/backlink.go Normal file
View File

@ -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(`<li class="backlinks__entry">
<a class="backlinks__link" href="/hypha/%s">%s</a>`, 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())
}
}

92
shroom/can.go Normal file
View File

@ -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,
)
)

29
shroom/delete.go Normal file
View File

@ -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, ""
}

37
shroom/init.go Normal file
View File

@ -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"
}
}

24
shroom/log.go Normal file
View File

@ -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)
}

View File

@ -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 <a href='/hypha/%[1]s'>%[1]s</a> 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 <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>"), "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

40
shroom/unattach.go Normal file
View File

@ -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: <code>%v</code>", hop.Errs))), "Error"
}
if h.BinaryPath != "" {
h.BinaryPath = ""
}
// If nothing is left of the hypha
if h.TextPath == "" {
h.Delete()
}
return hop, ""
}

87
shroom/upload.go Normal file
View File

@ -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(), ""
}

View File

@ -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(`
<div class="binary-container binary-container_with-video">
<video>
<video controls>
<source src="/binary/%[1]s"/>
<p>Your browser does not support video. <a href="/binary/%[1]s">Download video</a></p>
</video>
@ -43,7 +44,7 @@ func (h *Hypha) BinaryHtmlBlock() string {
case ".mp3":
return fmt.Sprintf(`
<div class="binary-container binary-container_with-audio">
<audio>
<audio controls>
<source src="/binary/%[1]s"/>
<p>Your browser does not support audio. <a href="/binary/%[1]s">Download audio</a></p>
</audio>
@ -58,7 +59,7 @@ func (h *Hypha) BinaryHtmlBlock() string {
}
func SetHeaderLinks() {
if userLinksHypha := ByName(util.HeaderLinksHypha); !userLinksHypha.Exists {
if userLinksHypha := hyphae.ByName(util.HeaderLinksHypha); !userLinksHypha.Exists {
util.SetDefaultHeaderLinks()
} else {
contents, err := ioutil.ReadFile(userLinksHypha.TextPath)