1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-06-07 05:14:05 +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/history"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
@ -29,7 +30,7 @@ func init() {
func factoryHandlerAsker( func factoryHandlerAsker(
actionPath string, actionPath string,
asker func(*hyphae.Hypha, *user.User) (error, string), asker func(*user.User, *hyphae.Hypha) (error, string),
succTitleTemplate string, succTitleTemplate string,
succPageTemplate func(*http.Request, string, bool) string, succPageTemplate func(*http.Request, string, bool) string,
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
@ -40,7 +41,7 @@ func factoryHandlerAsker(
h = hyphae.ByName(hyphaName) h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq) u = user.FromRequest(rq)
) )
if err, errtitle := asker(h, u); err != nil { if err, errtitle := asker(u, h); err != nil {
HttpErr( HttpErr(
w, w,
http.StatusInternalServerError, http.StatusInternalServerError,
@ -60,27 +61,21 @@ func factoryHandlerAsker(
var handlerUnattachAsk = factoryHandlerAsker( var handlerUnattachAsk = factoryHandlerAsker(
"unattach-ask", "unattach-ask",
func(h *hyphae.Hypha, u *user.User) (error, string) { shroom.CanUnattach,
return h.CanUnattach(u)
},
"Unattach %s?", "Unattach %s?",
templates.UnattachAskHTML, templates.UnattachAskHTML,
) )
var handlerDeleteAsk = factoryHandlerAsker( var handlerDeleteAsk = factoryHandlerAsker(
"delete-ask", "delete-ask",
func(h *hyphae.Hypha, u *user.User) (error, string) { shroom.CanDelete,
return h.CanDelete(u)
},
"Delete %s?", "Delete %s?",
templates.DeleteAskHTML, templates.DeleteAskHTML,
) )
var handlerRenameAsk = factoryHandlerAsker( var handlerRenameAsk = factoryHandlerAsker(
"rename-ask", "rename-ask",
func(h *hyphae.Hypha, u *user.User) (error, string) { shroom.CanRename,
return h.CanRename(u)
},
"Rename %s?", "Rename %s?",
templates.RenameAskHTML, templates.RenameAskHTML,
) )
@ -109,14 +104,14 @@ func factoryHandlerConfirmer(
var handlerUnattachConfirm = factoryHandlerConfirmer( var handlerUnattachConfirm = factoryHandlerConfirmer(
"unattach-confirm", "unattach-confirm",
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) { func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
return h.UnattachHypha(u) return shroom.UnattachHypha(u, h)
}, },
) )
var handlerDeleteConfirm = factoryHandlerConfirmer( var handlerDeleteConfirm = factoryHandlerConfirmer(
"delete-confirm", "delete-confirm",
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) { 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" recursive = rq.PostFormValue("recursive") == "true"
newHypha = hyphae.ByName(newName) 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 err error
u = user.FromRequest(rq) 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, HttpErr(w, http.StatusInternalServerError, hyphaName,
errtitle, errtitle,
err.Error()) err.Error())
return return
} }
if h.Exists { if h.Exists {
textAreaFill, err = h.FetchTextPart() textAreaFill, err = shroom.FetchTextPart(h)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
HttpErr(w, http.StatusInternalServerError, hyphaName, HttpErr(w, http.StatusInternalServerError, hyphaName,
@ -183,7 +178,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
) )
if action != "Preview" { if action != "Preview" {
hop, errtitle = h.UploadText([]byte(textData), u) hop, errtitle = shroom.UploadText(h, []byte(textData), u)
} }
if hop.HasErrors() { if hop.HasErrors() {
@ -220,7 +215,12 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
u = user.FromRequest(rq) u = user.FromRequest(rq)
file, handler, err = rq.FormFile("binary") 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, HttpErr(w, http.StatusInternalServerError, hyphaName,
errtitle, errtitle,
err.Error()) err.Error())
@ -237,7 +237,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
} }
var ( var (
mime = handler.Header.Get("Content-Type") mime = handler.Header.Get("Content-Type")
hop, errtitle = h.UploadBinary(mime, file, u) hop, errtitle = shroom.UploadBinary(h, mime, file, u)
) )
if hop.HasErrors() { if hop.HasErrors() {

View File

@ -13,6 +13,7 @@ import (
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/tree" "github.com/bouncepaw/mycorrhiza/tree"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
@ -100,7 +101,7 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) {
openGraph = md.OpenGraphHTML() openGraph = md.OpenGraphHTML()
} }
if !os.IsNotExist(errB) { if !os.IsNotExist(errB) {
contents = h.BinaryHtmlBlock() + contents contents = shroom.BinaryHtmlBlock(h) + contents
} }
} }
treeHTML, subhyphaeHTML, prevHypha, nextHypha := tree.Tree(hyphaName) treeHTML, subhyphaeHTML, prevHypha, nextHypha := tree.Tree(hyphaName)
@ -112,7 +113,7 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) {
contents, contents,
treeHTML, treeHTML,
subhyphaeHTML, subhyphaeHTML,
h.BackLinkEntriesHTML(), shroom.BackLinkEntriesHTML(h),
prevHypha, nextHypha, prevHypha, nextHypha,
hasAmnt), hasAmnt),
u, 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 sync.Mutex
}{} }{}
// Set the value of hyphae count to zero. // Set the value of hyphae count to zero. Use when reloading hyphae.
func ResetCount() { func ResetCount() {
count.Lock() count.Lock()
count.value = 0 count.value = 0
count.Unlock() 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() { func IncrementCount() {
count.Lock() count.Lock()
count.value++ count.value++
count.Unlock() count.Unlock()
} }
// Decrement the value of hyphae count. // Decrement the value of the hyphae counter. Use when deleting existing hyphae.
func DecrementCount() { func DecrementCount() {
count.Lock() count.Lock()
count.value-- count.value--
@ -33,6 +33,5 @@ func DecrementCount() {
// Count how many hyphae there are. // Count how many hyphae there are.
func Count() int { func Count() int {
// it is concurrent-safe to not lock here, right?
return count.value 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 package hyphae
import ( import (
"errors"
"log" "log"
"regexp" "regexp"
"strings"
"sync" "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. // HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`) var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
@ -56,57 +24,6 @@ type Hypha struct {
var byNames = make(map[string]*Hypha) var byNames = make(map[string]*Hypha)
var byNamesMutex = sync.Mutex{} 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. // EmptyHypha returns an empty hypha struct with given name.
func EmptyHypha(hyphaName string) *Hypha { func EmptyHypha(hyphaName string) *Hypha {
return &Hypha{ return &Hypha{
@ -164,7 +81,7 @@ func (h *Hypha) InsertIfNewKeepExistence() {
} }
} }
func (h *Hypha) delete() { func (h *Hypha) Delete() {
byNamesMutex.Lock() byNamesMutex.Lock()
h.Lock() h.Lock()
delete(byNames, h.Name) delete(byNames, h.Name)
@ -173,7 +90,7 @@ func (h *Hypha) delete() {
h.Unlock() h.Unlock()
} }
func (h *Hypha) renameTo(newName string) { func (h *Hypha) RenameTo(newName string) {
byNamesMutex.Lock() byNamesMutex.Lock()
h.Lock() h.Lock()
delete(byNames, h.Name) delete(byNames, h.Name)
@ -195,3 +112,31 @@ func (h *Hypha) MergeIn(oh *Hypha) {
h.BinaryPath = oh.BinaryPath 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/history"
"github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
@ -85,7 +86,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
log.Println("Rejected", rq.URL) log.Println("Rejected", rq.URL)
return return
} }
hyphae.SetHeaderLinks() shroom.SetHeaderLinks()
http.Redirect(w, rq, "/", http.StatusSeeOther) http.Redirect(w, rq, "/", http.StatusSeeOther)
} }
@ -175,11 +176,11 @@ func main() {
log.Println("Wiki storage directory is", WikiDir) log.Println("Wiki storage directory is", WikiDir)
hyphae.Index(WikiDir) hyphae.Index(WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae") log.Println("Indexed", hyphae.Count(), "hyphae")
hyphae.FindAllBacklinks() shroom.FindAllBacklinks()
log.Println("Found all backlinks") log.Println("Found all backlinks")
history.Start(WikiDir) history.Start(WikiDir)
hyphae.SetHeaderLinks() shroom.SetHeaderLinks()
go handleGemini() 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 ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"regexp" "regexp"
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
func rejectRenameLog(h *Hypha, u *user.User, errmsg string) { func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User) (err error, errtitle 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) {
if nh.Exists { if nh.Exists {
rejectRenameLog(oh, u, fmt.Sprintf("name %s taken already", nh.Name)) 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" 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" 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)) 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" 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. // 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() newHypha.Lock()
defer newHypha.Unlock() defer newHypha.Unlock()
hop = history.Operation(history.TypeRenameHypha) hop = history.Operation(history.TypeRenameHypha)
if err, errtitle := h.CanRename(u); errtitle != "" { if err, errtitle := CanRename(u, h); errtitle != "" {
hop.WithError(err) hop.WithError(err)
return hop, errtitle return hop, errtitle
} }
@ -85,7 +67,7 @@ func (h *Hypha) RenameHypha(newHypha *Hypha, recursive bool, u *user.User) (hop
Apply() Apply()
if len(hop.Errs) == 0 { if len(hop.Errs) == 0 {
for _, h := range hyphaeToRename { for _, h := range hyphaeToRename {
h.renameTo(replaceName(h.Name)) h.RenameTo(replaceName(h.Name))
h.Lock() h.Lock()
h.TextPath = replaceName(h.TextPath) h.TextPath = replaceName(h.TextPath)
h.BinaryPath = replaceName(h.BinaryPath) h.BinaryPath = replaceName(h.BinaryPath)
@ -95,15 +77,15 @@ func (h *Hypha) RenameHypha(newHypha *Hypha, recursive bool, u *user.User) (hop
return hop, "" return hop, ""
} }
func findHyphaeToRename(superhypha *Hypha, recursive bool) []*Hypha { func findHyphaeToRename(superhypha *hyphae.Hypha, recursive bool) []*hyphae.Hypha {
hyphae := []*Hypha{superhypha} hyphae := []*hyphae.Hypha{superhypha}
if recursive { if recursive {
hyphae = append(hyphae, superhypha.Subhyphae()...) hyphae = append(hyphae, superhypha.Subhyphae()...)
} }
return hyphae 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) renameMap := make(map[string]string)
newNames := make([]string, len(hyphaeToRename)) newNames := make([]string, len(hyphaeToRename))
for _, h := range hyphaeToRename { for _, h := range hyphaeToRename {
@ -117,7 +99,7 @@ func renamingPairs(hyphaeToRename []*Hypha, replaceName func(string) string) (ma
} }
h.RUnlock() h.RUnlock()
} }
if firstFailure, ok := AreFreeNames(newNames...); !ok { if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok {
return nil, errors.New("Hypha " + firstFailure + " already exists") return nil, errors.New("Hypha " + firstFailure + " already exists")
} }
return renameMap, nil 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 ( import (
"fmt" "fmt"
@ -6,12 +6,13 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
// FetchTextPart tries to read text file of the given hypha. If there is no file, empty string is returned. // 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 == "" { if h.TextPath == "" {
return "", nil return "", nil
} }
@ -25,7 +26,7 @@ func (h *Hypha) FetchTextPart() (string, error) {
} }
// binaryHtmlBlock creates an html block for binary part of the hypha. // 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) { switch filepath.Ext(h.BinaryPath) {
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico": case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
return fmt.Sprintf(` return fmt.Sprintf(`
@ -35,7 +36,7 @@ func (h *Hypha) BinaryHtmlBlock() string {
case ".ogg", ".webm", ".mp4": case ".ogg", ".webm", ".mp4":
return fmt.Sprintf(` return fmt.Sprintf(`
<div class="binary-container binary-container_with-video"> <div class="binary-container binary-container_with-video">
<video> <video controls>
<source src="/binary/%[1]s"/> <source src="/binary/%[1]s"/>
<p>Your browser does not support video. <a href="/binary/%[1]s">Download video</a></p> <p>Your browser does not support video. <a href="/binary/%[1]s">Download video</a></p>
</video> </video>
@ -43,7 +44,7 @@ func (h *Hypha) BinaryHtmlBlock() string {
case ".mp3": case ".mp3":
return fmt.Sprintf(` return fmt.Sprintf(`
<div class="binary-container binary-container_with-audio"> <div class="binary-container binary-container_with-audio">
<audio> <audio controls>
<source src="/binary/%[1]s"/> <source src="/binary/%[1]s"/>
<p>Your browser does not support audio. <a href="/binary/%[1]s">Download audio</a></p> <p>Your browser does not support audio. <a href="/binary/%[1]s">Download audio</a></p>
</audio> </audio>
@ -58,7 +59,7 @@ func (h *Hypha) BinaryHtmlBlock() string {
} }
func SetHeaderLinks() { func SetHeaderLinks() {
if userLinksHypha := ByName(util.HeaderLinksHypha); !userLinksHypha.Exists { if userLinksHypha := hyphae.ByName(util.HeaderLinksHypha); !userLinksHypha.Exists {
util.SetDefaultHeaderLinks() util.SetDefaultHeaderLinks()
} else { } else {
contents, err := ioutil.ReadFile(userLinksHypha.TextPath) contents, err := ioutil.ReadFile(userLinksHypha.TextPath)