mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-01-07 02:10:26 +00:00
Start the Great Refactoring
This commit is contained in:
parent
5cafaaa3d8
commit
ee02211b3e
6
flag.go
6
flag.go
@ -42,9 +42,9 @@ func parseCliArgs() {
|
|||||||
util.URL = "http://0.0.0.0:" + util.ServerPort
|
util.URL = "http://0.0.0.0:" + util.ServerPort
|
||||||
}
|
}
|
||||||
|
|
||||||
util.HomePage = CanonicalName(util.HomePage)
|
util.HomePage = util.CanonicalName(util.HomePage)
|
||||||
util.UserHypha = CanonicalName(util.UserHypha)
|
util.UserHypha = util.CanonicalName(util.UserHypha)
|
||||||
util.HeaderLinksHypha = CanonicalName(util.HeaderLinksHypha)
|
util.HeaderLinksHypha = util.CanonicalName(util.HeaderLinksHypha)
|
||||||
|
|
||||||
switch util.AuthMethod {
|
switch util.AuthMethod {
|
||||||
case "none":
|
case "none":
|
||||||
|
13
gemini.go
13
gemini.go
@ -11,6 +11,7 @@ import (
|
|||||||
"git.sr.ht/~adnano/go-gemini"
|
"git.sr.ht/~adnano/go-gemini"
|
||||||
"git.sr.ht/~adnano/go-gemini/certificate"
|
"git.sr.ht/~adnano/go-gemini/certificate"
|
||||||
|
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
@ -28,13 +29,13 @@ Visit home hypha:
|
|||||||
func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
|
func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
var (
|
||||||
hyphaName = geminiHyphaNameFromRq(rq, "page", "hypha")
|
hyphaName = geminiHyphaNameFromRq(rq, "page", "hypha")
|
||||||
data, hyphaExists = HyphaStorage[hyphaName]
|
h = hyphae.ByName(hyphaName)
|
||||||
hasAmnt = hyphaExists && data.BinaryPath != ""
|
hasAmnt = h.Exists && h.BinaryPath != ""
|
||||||
contents string
|
contents string
|
||||||
)
|
)
|
||||||
if hyphaExists {
|
if h.Exists {
|
||||||
fileContentsT, errT := ioutil.ReadFile(data.TextPath)
|
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
|
||||||
if errT == nil {
|
if errT == nil {
|
||||||
md := markup.Doc(hyphaName, string(fileContentsT))
|
md := markup.Doc(hyphaName, string(fileContentsT))
|
||||||
contents = md.AsGemtext()
|
contents = md.AsGemtext()
|
||||||
|
@ -134,3 +134,11 @@ func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
|||||||
}
|
}
|
||||||
return hop
|
return hop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hop *HistoryOp) HasErrors() bool {
|
||||||
|
return len(hop.Errs) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hop *HistoryOp) FirstErrorText() string {
|
||||||
|
return hop.Errs[0].Error()
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -39,7 +40,7 @@ func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
|
|||||||
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
|
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
var (
|
||||||
username = CanonicalName(rq.PostFormValue("username"))
|
username = util.CanonicalName(rq.PostFormValue("username"))
|
||||||
password = rq.PostFormValue("password")
|
password = rq.PostFormValue("password")
|
||||||
err = user.LoginDataHTTP(w, rq, username, password)
|
err = user.LoginDataHTTP(w, rq, username, password)
|
||||||
)
|
)
|
||||||
|
349
http_mutators.go
349
http_mutators.go
@ -5,6 +5,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/markup"
|
"github.com/bouncepaw/mycorrhiza/markup"
|
||||||
"github.com/bouncepaw/mycorrhiza/templates"
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
"github.com/bouncepaw/mycorrhiza/user"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
@ -25,182 +27,146 @@ func init() {
|
|||||||
http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm)
|
http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerUnattachAsk(w http.ResponseWriter, rq *http.Request) {
|
func factoryHandlerAsker(
|
||||||
log.Println(rq.URL)
|
actionPath string,
|
||||||
var (
|
asker func(*hyphae.Hypha, *user.User) (error, string),
|
||||||
hyphaName = HyphaNameFromRq(rq, "unattach-ask")
|
succTitleTemplate string,
|
||||||
hd, isOld = HyphaStorage[hyphaName]
|
succPageTemplate func(*http.Request, string, bool) string,
|
||||||
hasAmnt = hd != nil && hd.BinaryPath != ""
|
) func(http.ResponseWriter, *http.Request) {
|
||||||
)
|
return func(w http.ResponseWriter, rq *http.Request) {
|
||||||
if !hasAmnt {
|
log.Println(rq.URL)
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
|
var (
|
||||||
log.Println("Rejected (no amnt):", rq.URL)
|
hyphaName = HyphaNameFromRq(rq, actionPath)
|
||||||
return
|
h = hyphae.ByName(hyphaName)
|
||||||
} else if ok := user.CanProceed(rq, "unattach-confirm"); !ok {
|
u = user.FromRequest(rq)
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
|
)
|
||||||
log.Println("Rejected (no rights):", rq.URL)
|
if err, errtitle := asker(h, u); err != nil {
|
||||||
return
|
HttpErr(
|
||||||
}
|
w,
|
||||||
util.HTTP200Page(w, base("Unattach "+hyphaName+"?", templates.UnattachAskHTML(rq, hyphaName, isOld), user.FromRequest(rq)))
|
http.StatusInternalServerError,
|
||||||
}
|
hyphaName,
|
||||||
|
errtitle,
|
||||||
func handlerUnattachConfirm(w http.ResponseWriter, rq *http.Request) {
|
err.Error())
|
||||||
log.Println(rq.URL)
|
return
|
||||||
var (
|
|
||||||
hyphaName = HyphaNameFromRq(rq, "unattach-confirm")
|
|
||||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
|
||||||
hasAmnt = hyphaData != nil && hyphaData.BinaryPath != ""
|
|
||||||
u = user.FromRequest(rq)
|
|
||||||
)
|
|
||||||
if !u.CanProceed("unattach-confirm") {
|
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
|
|
||||||
log.Println("Rejected (no rights):", rq.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !hasAmnt {
|
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
|
|
||||||
log.Println("Rejected (no amnt):", rq.URL)
|
|
||||||
return
|
|
||||||
} else if !isOld {
|
|
||||||
// The precondition is to have the hypha in the first place.
|
|
||||||
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
|
|
||||||
"Error: no such hypha",
|
|
||||||
"Could not unattach this hypha because it does not exist")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if hop := hyphaData.UnattachHypha(hyphaName, u); len(hop.Errs) != 0 {
|
|
||||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
|
||||||
"Error: could not unattach hypha",
|
|
||||||
fmt.Sprintf("Could not unattach this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
|
||||||
log.Println(rq.URL)
|
|
||||||
var (
|
|
||||||
hyphaName = HyphaNameFromRq(rq, "rename-ask")
|
|
||||||
_, isOld = HyphaStorage[hyphaName]
|
|
||||||
u = user.FromRequest(rq)
|
|
||||||
)
|
|
||||||
if !u.CanProceed("rename-confirm") {
|
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
|
|
||||||
log.Println("Rejected", rq.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
util.HTTP200Page(w, base("Rename "+hyphaName+"?", templates.RenameAskHTML(rq, hyphaName, isOld), u))
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
|
|
||||||
log.Println(rq.URL)
|
|
||||||
var (
|
|
||||||
hyphaName = HyphaNameFromRq(rq, "rename-confirm")
|
|
||||||
_, isOld = HyphaStorage[hyphaName]
|
|
||||||
newName = CanonicalName(rq.PostFormValue("new-name"))
|
|
||||||
_, newNameIsUsed = HyphaStorage[newName]
|
|
||||||
recursive = rq.PostFormValue("recursive") == "true"
|
|
||||||
u = user.FromRequest(rq)
|
|
||||||
)
|
|
||||||
switch {
|
|
||||||
case !u.CanProceed("rename-confirm"):
|
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
|
|
||||||
log.Println("Rejected", rq.URL)
|
|
||||||
case newNameIsUsed:
|
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists",
|
|
||||||
fmt.Sprintf("Hypha named <a href='/page/%s'>%s</a> already exists.", hyphaName, hyphaName))
|
|
||||||
case newName == "":
|
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no name",
|
|
||||||
"No new name is given.")
|
|
||||||
case !isOld:
|
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no such hypha",
|
|
||||||
"Cannot rename a hypha that does not exist yet.")
|
|
||||||
case !HyphaPattern.MatchString(newName):
|
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name",
|
|
||||||
"Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>")
|
|
||||||
default:
|
|
||||||
if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 {
|
|
||||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
|
||||||
"Error: could not rename hypha",
|
|
||||||
fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs))
|
|
||||||
} else {
|
|
||||||
http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
|
|
||||||
}
|
}
|
||||||
|
util.HTTP200Page(
|
||||||
|
w,
|
||||||
|
base(
|
||||||
|
fmt.Sprintf(succTitleTemplate, hyphaName),
|
||||||
|
succPageTemplate(rq, hyphaName, h.Exists),
|
||||||
|
u))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerDeleteAsk shows a delete dialog.
|
var handlerUnattachAsk = factoryHandlerAsker(
|
||||||
func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) {
|
"unattach-ask",
|
||||||
log.Println(rq.URL)
|
func(h *hyphae.Hypha, u *user.User) (error, string) {
|
||||||
var (
|
return h.CanUnattach(u)
|
||||||
hyphaName = HyphaNameFromRq(rq, "delete-ask")
|
},
|
||||||
_, isOld = HyphaStorage[hyphaName]
|
"Unattach %s?",
|
||||||
u = user.FromRequest(rq)
|
templates.UnattachAskHTML,
|
||||||
)
|
)
|
||||||
if !u.CanProceed("delete-ask") {
|
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
|
var handlerDeleteAsk = factoryHandlerAsker(
|
||||||
log.Println("Rejected", rq.URL)
|
"delete-ask",
|
||||||
return
|
func(h *hyphae.Hypha, u *user.User) (error, string) {
|
||||||
|
return h.CanDelete(u)
|
||||||
|
},
|
||||||
|
"Delete %s?",
|
||||||
|
templates.DeleteAskHTML,
|
||||||
|
)
|
||||||
|
|
||||||
|
var handlerRenameAsk = factoryHandlerAsker(
|
||||||
|
"rename-ask",
|
||||||
|
func(h *hyphae.Hypha, u *user.User) (error, string) {
|
||||||
|
return h.CanRename(u)
|
||||||
|
},
|
||||||
|
"Rename %s?",
|
||||||
|
templates.RenameAskHTML,
|
||||||
|
)
|
||||||
|
|
||||||
|
func factoryHandlerConfirmer(
|
||||||
|
actionPath string,
|
||||||
|
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string),
|
||||||
|
) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
log.Println(rq.URL)
|
||||||
|
var (
|
||||||
|
hyphaName = HyphaNameFromRq(rq, "unattach-confirm")
|
||||||
|
h = hyphae.ByName(hyphaName)
|
||||||
|
u = user.FromRequest(rq)
|
||||||
|
)
|
||||||
|
if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() {
|
||||||
|
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||||
|
errtitle,
|
||||||
|
hop.FirstErrorText())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(rq, hyphaName, isOld), u))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerDeleteConfirm deletes a hypha for sure
|
var handlerUnattachConfirm = factoryHandlerConfirmer(
|
||||||
func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) {
|
"unattach-confirm",
|
||||||
log.Println(rq.URL)
|
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||||
var (
|
return h.UnattachHypha(u)
|
||||||
hyphaName = HyphaNameFromRq(rq, "delete-confirm")
|
},
|
||||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
)
|
||||||
u = user.FromRequest(rq)
|
|
||||||
)
|
var handlerDeleteConfirm = factoryHandlerConfirmer(
|
||||||
if !u.CanProceed("delete-confirm") {
|
"delete-confirm",
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
|
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||||
log.Println("Rejected", rq.URL)
|
return h.DeleteHypha(u)
|
||||||
return
|
},
|
||||||
}
|
)
|
||||||
if !isOld {
|
|
||||||
// The precondition is to have the hypha in the first place.
|
var handlerRenameConfirm = factoryHandlerConfirmer(
|
||||||
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
|
"rename-confirm",
|
||||||
"Error: no such hypha",
|
func(oldHypha *hyphae.Hypha, u *user.User, rq *http.Request) (*history.HistoryOp, string) {
|
||||||
"Could not delete this hypha because it does not exist.")
|
var (
|
||||||
return
|
newName = util.CanonicalName(rq.PostFormValue("new-name"))
|
||||||
}
|
recursive = rq.PostFormValue("recursive") == "true"
|
||||||
if hop := hyphaData.DeleteHypha(hyphaName, u); len(hop.Errs) != 0 {
|
newHypha = hyphae.ByName(newName)
|
||||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
)
|
||||||
"Error: could not delete hypha",
|
return oldHypha.RenameHypha(newHypha, recursive, u)
|
||||||
fmt.Sprintf("Could not delete this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
|
},
|
||||||
return
|
)
|
||||||
}
|
|
||||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handlerEdit shows the edit form. It doesn't edit anything actually.
|
// handlerEdit shows the edit form. It doesn't edit anything actually.
|
||||||
func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "edit")
|
hyphaName = HyphaNameFromRq(rq, "edit")
|
||||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
h = hyphae.ByName(hyphaName)
|
||||||
warning string
|
warning string
|
||||||
textAreaFill string
|
textAreaFill string
|
||||||
err error
|
err error
|
||||||
u = user.FromRequest(rq)
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if !u.CanProceed("edit") {
|
if err, errtitle := h.CanEdit(u); err != nil {
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||||
log.Println("Rejected", rq.URL)
|
errtitle,
|
||||||
|
err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if isOld {
|
if h.Exists {
|
||||||
textAreaFill, err = FetchTextPart(hyphaData)
|
textAreaFill, err = h.FetchTextPart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", "Could not fetch text data")
|
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||||
|
"Error",
|
||||||
|
"Could not fetch text data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warning = `<p>You are creating a new hypha.</p>`
|
warning = `<p class="warning warning_new-hypha">You are creating a new hypha.</p>`
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning), u))
|
util.HTTP200Page(
|
||||||
|
w,
|
||||||
|
base(
|
||||||
|
"Edit "+hyphaName,
|
||||||
|
templates.EditHTML(rq, hyphaName, textAreaFill, warning),
|
||||||
|
u))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerUploadText uploads a new text part for the hypha.
|
// handlerUploadText uploads a new text part for the hypha.
|
||||||
@ -208,62 +174,75 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
||||||
|
h = hyphae.ByName(hyphaName)
|
||||||
textData = rq.PostFormValue("text")
|
textData = rq.PostFormValue("text")
|
||||||
action = rq.PostFormValue("action")
|
action = rq.PostFormValue("action")
|
||||||
u = user.FromRequest(rq)
|
u = user.FromRequest(rq)
|
||||||
|
hop *history.HistoryOp
|
||||||
|
errtitle string
|
||||||
)
|
)
|
||||||
if !u.CanProceed("upload-text") {
|
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
if action != "Preview" {
|
||||||
log.Println("Rejected", rq.URL)
|
hop, errtitle = h.UploadText([]byte(textData), u)
|
||||||
return
|
}
|
||||||
}
|
|
||||||
if textData == "" {
|
if hop.HasErrors() {
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
|
HttpErr(w, http.StatusForbidden, hyphaName,
|
||||||
|
errtitle,
|
||||||
|
hop.FirstErrorText())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == "Preview" {
|
if action == "Preview" {
|
||||||
util.HTTP200Page(w, base("Preview "+hyphaName, templates.PreviewHTML(rq, hyphaName, textData, "", markup.Doc(hyphaName, textData).AsHTML()), u))
|
util.HTTP200Page(
|
||||||
} else if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 {
|
w,
|
||||||
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
|
base(
|
||||||
|
"Preview "+hyphaName,
|
||||||
|
templates.PreviewHTML(
|
||||||
|
rq,
|
||||||
|
hyphaName,
|
||||||
|
textData,
|
||||||
|
"",
|
||||||
|
markup.Doc(hyphaName, textData).AsHTML()),
|
||||||
|
u))
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerUploadBinary uploads a new binary part for the hypha.
|
// handlerUploadBinary uploads a new binary part for the hypha.
|
||||||
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
|
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
|
||||||
hyphaName = HyphaNameFromRq(rq, "upload-binary")
|
|
||||||
u = user.FromRequest(rq)
|
|
||||||
)
|
|
||||||
if !u.CanProceed("upload-binary") {
|
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.")
|
|
||||||
log.Println("Rejected", rq.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rq.ParseMultipartForm(10 << 20) // Set upload limit
|
rq.ParseMultipartForm(10 << 20) // Set upload limit
|
||||||
file, handler, err := rq.FormFile("binary")
|
var (
|
||||||
if file != nil {
|
hyphaName = HyphaNameFromRq(rq, "upload-binary")
|
||||||
defer file.Close()
|
h = hyphae.ByName(hyphaName)
|
||||||
|
u = user.FromRequest(rq)
|
||||||
|
file, handler, err = rq.FormFile("binary")
|
||||||
|
)
|
||||||
|
if err, errtitle := h.CanAttach(err, u); err != nil {
|
||||||
|
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||||
|
errtitle,
|
||||||
|
err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// If file is not passed:
|
// If file is not passed:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If file is passed:
|
// If file is passed:
|
||||||
|
if file != nil {
|
||||||
|
defer file.Close()
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
mime = handler.Header.Get("Content-Type")
|
mime = handler.Header.Get("Content-Type")
|
||||||
hop = UploadBinary(hyphaName, mime, file, u)
|
hop, errtitle = h.UploadBinary(mime, file, u)
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(hop.Errs) != 0 {
|
if hop.HasErrors() {
|
||||||
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
|
HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, hop.FirstErrorText())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"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/templates"
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
@ -33,7 +34,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
|||||||
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
|
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
|
||||||
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
||||||
revHash = shorterUrl[:firstSlashIndex]
|
revHash = shorterUrl[:firstSlashIndex]
|
||||||
hyphaName = CanonicalName(shorterUrl[firstSlashIndex+1:])
|
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||||
contents = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`)
|
contents = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`)
|
||||||
TextPath = hyphaName + ".myco"
|
TextPath = hyphaName + ".myco"
|
||||||
textContents, err = history.FileAtRevision(TextPath, revHash)
|
textContents, err = history.FileAtRevision(TextPath, revHash)
|
||||||
@ -42,7 +43,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
contents = markup.Doc(hyphaName, textContents).AsHTML()
|
contents = markup.Doc(hyphaName, textContents).AsHTML()
|
||||||
}
|
}
|
||||||
treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith)
|
treeHTML, _, _ := tree.Tree(hyphaName)
|
||||||
page := templates.RevisionHTML(
|
page := templates.RevisionHTML(
|
||||||
rq,
|
rq,
|
||||||
hyphaName,
|
hyphaName,
|
||||||
@ -60,10 +61,10 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
|||||||
func handlerText(w http.ResponseWriter, rq *http.Request) {
|
func handlerText(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
hyphaName := HyphaNameFromRq(rq, "text")
|
hyphaName := HyphaNameFromRq(rq, "text")
|
||||||
if data, ok := HyphaStorage[hyphaName]; ok {
|
if h := hyphae.ByName(hyphaName); h.Exists {
|
||||||
log.Println("Serving", data.TextPath)
|
log.Println("Serving", h.TextPath)
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
http.ServeFile(w, rq, data.TextPath)
|
http.ServeFile(w, rq, h.TextPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,10 +72,10 @@ func handlerText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
func handlerBinary(w http.ResponseWriter, rq *http.Request) {
|
func handlerBinary(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
hyphaName := HyphaNameFromRq(rq, "binary")
|
hyphaName := HyphaNameFromRq(rq, "binary")
|
||||||
if data, ok := HyphaStorage[hyphaName]; ok {
|
if h := hyphae.ByName(hyphaName); h.Exists {
|
||||||
log.Println("Serving", data.BinaryPath)
|
log.Println("Serving", h.BinaryPath)
|
||||||
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(data.BinaryPath)))
|
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath)))
|
||||||
http.ServeFile(w, rq, data.BinaryPath)
|
http.ServeFile(w, rq, h.BinaryPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,26 +83,26 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
|
|||||||
func handlerHypha(w http.ResponseWriter, rq *http.Request) {
|
func handlerHypha(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "page", "hypha")
|
hyphaName = HyphaNameFromRq(rq, "page", "hypha")
|
||||||
data, hyphaExists = HyphaStorage[hyphaName]
|
h = hyphae.ByName(hyphaName)
|
||||||
hasAmnt = hyphaExists && data.BinaryPath != ""
|
hasAmnt = h.Exists && h.BinaryPath != ""
|
||||||
contents string
|
contents string
|
||||||
openGraph string
|
openGraph string
|
||||||
u = user.FromRequest(rq)
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if hyphaExists {
|
if h.Exists {
|
||||||
fileContentsT, errT := ioutil.ReadFile(data.TextPath)
|
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
|
||||||
_, errB := os.Stat(data.BinaryPath)
|
_, errB := os.Stat(h.BinaryPath)
|
||||||
if errT == nil {
|
if errT == nil {
|
||||||
md := markup.Doc(hyphaName, string(fileContentsT))
|
md := markup.Doc(hyphaName, string(fileContentsT))
|
||||||
contents = md.AsHTML()
|
contents = md.AsHTML()
|
||||||
openGraph = md.OpenGraphHTML()
|
openGraph = md.OpenGraphHTML()
|
||||||
}
|
}
|
||||||
if !os.IsNotExist(errB) {
|
if !os.IsNotExist(errB) {
|
||||||
contents = binaryHtmlBlock(hyphaName, data) + contents
|
contents = h.BinaryHtmlBlock() + contents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName, IterateHyphaNamesWith)
|
treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName)
|
||||||
util.HTTP200Page(w,
|
util.HTTP200Page(w,
|
||||||
templates.BaseHTML(
|
templates.BaseHTML(
|
||||||
util.BeautifulName(hyphaName),
|
util.BeautifulName(hyphaName),
|
||||||
|
326
hypha.go
326
hypha.go
@ -1,326 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"mime/multipart"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/markup"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/mimetype"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
markup.HyphaExists = func(hyphaName string) bool {
|
|
||||||
_, hyphaExists := HyphaStorage[hyphaName]
|
|
||||||
return hyphaExists
|
|
||||||
}
|
|
||||||
markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
|
|
||||||
if hyphaData, ok := HyphaStorage[hyphaName]; ok {
|
|
||||||
rawText, err = FetchTextPart(hyphaData)
|
|
||||||
if hyphaData.BinaryPath != "" {
|
|
||||||
binaryBlock = binaryHtmlBlock(hyphaName, hyphaData)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = errors.New("Hypha " + hyphaName + " does not exist")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
markup.HyphaIterate = IterateHyphaNamesWith
|
|
||||||
markup.HyphaImageForOG = func(hyphaName string) string {
|
|
||||||
if hd, isOld := GetHyphaData(hyphaName); isOld && hd.BinaryPath != "" {
|
|
||||||
return util.URL + "/binary/" + hyphaName
|
|
||||||
}
|
|
||||||
return util.URL + "/favicon.ico"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
|
|
||||||
func GetHyphaData(hyphaName string) (hyphaData *HyphaData, isOld bool) {
|
|
||||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
|
||||||
if hyphaData == nil {
|
|
||||||
hyphaData = &HyphaData{}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types.
|
|
||||||
type HyphaData hyphae.Hypha
|
|
||||||
|
|
||||||
// uploadHelp is a helper function for UploadText and UploadBinary
|
|
||||||
func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp {
|
|
||||||
var (
|
|
||||||
hyphaData, isOld = GetHyphaData(hyphaName)
|
|
||||||
fullPath = filepath.Join(WikiDir, hyphaName+ext)
|
|
||||||
originalFullPath = &hyphaData.TextPath
|
|
||||||
)
|
|
||||||
if hop.Type == history.TypeEditBinary {
|
|
||||||
originalFullPath = &hyphaData.BinaryPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
|
|
||||||
return hop.WithError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
|
|
||||||
return hop.WithError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isOld && *originalFullPath != fullPath && *originalFullPath != "" {
|
|
||||||
if err := history.Rename(*originalFullPath, fullPath); err != nil {
|
|
||||||
return hop.WithError(err)
|
|
||||||
}
|
|
||||||
log.Println("Move", *originalFullPath, "to", fullPath)
|
|
||||||
}
|
|
||||||
// New hyphae must be added to the hypha storage
|
|
||||||
if !isOld {
|
|
||||||
HyphaStorage[hyphaName] = hyphaData
|
|
||||||
hyphae.IncrementCount()
|
|
||||||
}
|
|
||||||
*originalFullPath = fullPath
|
|
||||||
if isOld && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
|
|
||||||
return hop.Abort()
|
|
||||||
}
|
|
||||||
return hop.WithFiles(fullPath).
|
|
||||||
WithUser(u).
|
|
||||||
Apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadText loads a new text part from `textData` for hypha `hyphaName`.
|
|
||||||
func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp {
|
|
||||||
return uploadHelp(
|
|
||||||
history.
|
|
||||||
Operation(history.TypeEditText).
|
|
||||||
WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)),
|
|
||||||
hyphaName, ".myco", []byte(textData), u)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`.
|
|
||||||
func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *history.HistoryOp {
|
|
||||||
var (
|
|
||||||
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mime))
|
|
||||||
data, err = ioutil.ReadAll(file)
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return hop.WithError(err).Apply()
|
|
||||||
}
|
|
||||||
return uploadHelp(hop, hyphaName, mimetype.ToExtension(mime), data, u)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteHypha deletes hypha and makes a history record about that.
|
|
||||||
func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.HistoryOp {
|
|
||||||
hop := history.Operation(history.TypeDeleteHypha).
|
|
||||||
WithFilesRemoved(hd.TextPath, hd.BinaryPath).
|
|
||||||
WithMsg(fmt.Sprintf("Delete ‘%s’", hyphaName)).
|
|
||||||
WithUser(u).
|
|
||||||
Apply()
|
|
||||||
if len(hop.Errs) == 0 {
|
|
||||||
delete(HyphaStorage, hyphaName)
|
|
||||||
hyphae.DecrementCount()
|
|
||||||
}
|
|
||||||
return hop
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnattachHypha unattaches hypha and makes a history record about that.
|
|
||||||
func (hd *HyphaData) UnattachHypha(hyphaName string, u *user.User) *history.HistoryOp {
|
|
||||||
hop := history.Operation(history.TypeUnattachHypha).
|
|
||||||
WithFilesRemoved(hd.BinaryPath).
|
|
||||||
WithMsg(fmt.Sprintf("Unattach ‘%s’", hyphaName)).
|
|
||||||
WithUser(u).
|
|
||||||
Apply()
|
|
||||||
if len(hop.Errs) == 0 {
|
|
||||||
hd, ok := HyphaStorage[hyphaName]
|
|
||||||
if ok {
|
|
||||||
if hd.BinaryPath != "" {
|
|
||||||
hd.BinaryPath = ""
|
|
||||||
}
|
|
||||||
// If nothing is left of the hypha
|
|
||||||
if hd.TextPath == "" {
|
|
||||||
delete(HyphaStorage, hyphaName)
|
|
||||||
hyphae.DecrementCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hop
|
|
||||||
}
|
|
||||||
|
|
||||||
func findHyphaeToRename(hyphaName string, recursive bool) []string {
|
|
||||||
hyphae := []string{hyphaName}
|
|
||||||
if recursive {
|
|
||||||
hyphae = append(hyphae, util.FindSubhyphae(hyphaName, IterateHyphaNamesWith)...)
|
|
||||||
}
|
|
||||||
return hyphae
|
|
||||||
}
|
|
||||||
|
|
||||||
func renamingPairs(hyphaNames []string, replaceName func(string) string) (map[string]string, error) {
|
|
||||||
renameMap := make(map[string]string)
|
|
||||||
for _, hn := range hyphaNames {
|
|
||||||
if hd, ok := HyphaStorage[hn]; ok {
|
|
||||||
if _, nameUsed := HyphaStorage[replaceName(hn)]; nameUsed {
|
|
||||||
return nil, errors.New("Hypha " + replaceName(hn) + " already exists")
|
|
||||||
}
|
|
||||||
if hd.TextPath != "" {
|
|
||||||
renameMap[hd.TextPath] = replaceName(hd.TextPath)
|
|
||||||
}
|
|
||||||
if hd.BinaryPath != "" {
|
|
||||||
renameMap[hd.BinaryPath] = replaceName(hd.BinaryPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return renameMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// word Data is plural here
|
|
||||||
func relocateHyphaData(hyphaNames []string, replaceName func(string) string) {
|
|
||||||
for _, hyphaName := range hyphaNames {
|
|
||||||
if hd, ok := HyphaStorage[hyphaName]; ok {
|
|
||||||
hd.TextPath = replaceName(hd.TextPath)
|
|
||||||
hd.BinaryPath = replaceName(hd.BinaryPath)
|
|
||||||
HyphaStorage[replaceName(hyphaName)] = hd
|
|
||||||
delete(HyphaStorage, hyphaName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
|
|
||||||
var (
|
|
||||||
re = regexp.MustCompile(`(?i)` + hyphaName)
|
|
||||||
replaceName = func(str string) string {
|
|
||||||
return re.ReplaceAllString(CanonicalName(str), newName)
|
|
||||||
}
|
|
||||||
hyphaNames = findHyphaeToRename(hyphaName, recursive)
|
|
||||||
renameMap, err = renamingPairs(hyphaNames, replaceName)
|
|
||||||
renameMsg = "Rename ‘%s’ to ‘%s’"
|
|
||||||
hop = history.Operation(history.TypeRenameHypha)
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
hop.Errs = append(hop.Errs, err)
|
|
||||||
return hop
|
|
||||||
}
|
|
||||||
if recursive {
|
|
||||||
renameMsg += " recursively"
|
|
||||||
}
|
|
||||||
hop.WithFilesRenamed(renameMap).
|
|
||||||
WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)).
|
|
||||||
WithUser(u).
|
|
||||||
Apply()
|
|
||||||
if len(hop.Errs) == 0 {
|
|
||||||
relocateHyphaData(hyphaNames, replaceName)
|
|
||||||
}
|
|
||||||
return hop
|
|
||||||
}
|
|
||||||
|
|
||||||
// binaryHtmlBlock creates an html block for binary part of the hypha.
|
|
||||||
func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
|
|
||||||
switch filepath.Ext(hd.BinaryPath) {
|
|
||||||
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
|
||||||
return fmt.Sprintf(`
|
|
||||||
<div class="binary-container binary-container_with-img">
|
|
||||||
<a href="/binary/%[1]s"><img src="/binary/%[1]s"/></a>
|
|
||||||
</div>`, hyphaName)
|
|
||||||
case ".ogg", ".webm", ".mp4":
|
|
||||||
return fmt.Sprintf(`
|
|
||||||
<div class="binary-container binary-container_with-video">
|
|
||||||
<video>
|
|
||||||
<source src="/binary/%[1]s"/>
|
|
||||||
<p>Your browser does not support video. See video's <a href="/binary/%[1]s">direct url</a></p>
|
|
||||||
</video>
|
|
||||||
`, hyphaName)
|
|
||||||
case ".mp3":
|
|
||||||
return fmt.Sprintf(`
|
|
||||||
<div class="binary-container binary-container_with-audio">
|
|
||||||
<audio>
|
|
||||||
<source src="/binary/%[1]s"/>
|
|
||||||
<p>Your browser does not support audio. See audio's <a href="/binary/%[1]s">direct url</a></p>
|
|
||||||
</audio>
|
|
||||||
`, hyphaName)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf(`
|
|
||||||
<div class="binary-container binary-container_with-nothing">
|
|
||||||
<p>This hypha's media cannot be rendered. <a href="/binary/%s">Download it</a></p>
|
|
||||||
</div>
|
|
||||||
`, hyphaName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchTextPart tries to read text file in the `d`. If there is no file, empty string is returned.
|
|
||||||
func FetchTextPart(d *HyphaData) (string, error) {
|
|
||||||
if d.TextPath == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
_, err := os.Stat(d.TextPath)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return "", nil
|
|
||||||
} else if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
text, err := ioutil.ReadFile(d.TextPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(text), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setHeaderLinks() {
|
|
||||||
if userLinksHypha, ok := GetHyphaData(util.HeaderLinksHypha); !ok {
|
|
||||||
util.SetDefaultHeaderLinks()
|
|
||||||
} else {
|
|
||||||
contents, err := ioutil.ReadFile(userLinksHypha.TextPath)
|
|
||||||
if err != nil || len(contents) == 0 {
|
|
||||||
util.SetDefaultHeaderLinks()
|
|
||||||
} else {
|
|
||||||
text := string(contents)
|
|
||||||
util.ParseHeaderLinks(text, markup.Rocketlink)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HyphaToTemporaryWorkaround(h *hyphae.Hypha) *HyphaData {
|
|
||||||
return &HyphaData{
|
|
||||||
Name: h.Name,
|
|
||||||
TextPath: h.TextPath,
|
|
||||||
BinaryPath: h.BinaryPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes.
|
|
||||||
func (h *HyphaData) MergeIn(oh *hyphae.Hypha) {
|
|
||||||
if h.TextPath == "" && oh.TextPath != "" {
|
|
||||||
h.TextPath = oh.TextPath
|
|
||||||
}
|
|
||||||
if oh.BinaryPath != "" {
|
|
||||||
if h.BinaryPath != "" {
|
|
||||||
log.Println("There is a file collision for binary part of a hypha:", h.BinaryPath, "and", oh.BinaryPath, "-- going on with the latter")
|
|
||||||
}
|
|
||||||
h.BinaryPath = oh.BinaryPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive.
|
|
||||||
func Index(path string) {
|
|
||||||
ch := make(chan *hyphae.Hypha, 5)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
hyphae.Index(path, 0, ch)
|
|
||||||
close(ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for h := range ch {
|
|
||||||
if oldHypha, ok := HyphaStorage[h.Name]; ok {
|
|
||||||
oldHypha.MergeIn(h)
|
|
||||||
} else {
|
|
||||||
HyphaStorage[h.Name] = HyphaToTemporaryWorkaround(h)
|
|
||||||
hyphae.IncrementCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
52
hyphae/delete.go
Normal file
52
hyphae/delete.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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) {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
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, ""
|
||||||
|
}
|
@ -9,8 +9,28 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Index finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller.
|
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
|
||||||
func Index(path string, nestLevel uint, ch chan *Hypha) {
|
func Index(path string) {
|
||||||
|
ch := make(chan *Hypha, 5)
|
||||||
|
|
||||||
|
go func(ch chan *Hypha) {
|
||||||
|
indexHelper(path, 0, ch)
|
||||||
|
close(ch)
|
||||||
|
}(ch)
|
||||||
|
|
||||||
|
for h := range ch {
|
||||||
|
// At this time it is safe to ignore the mutex, because there is only one worker.
|
||||||
|
if oldHypha, ok := byNames[h.Name]; ok {
|
||||||
|
oldHypha.MergeIn(h)
|
||||||
|
} else {
|
||||||
|
byNames[h.Name] = h
|
||||||
|
IncrementCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexHelper finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller.
|
||||||
|
func indexHelper(path string, nestLevel uint, ch chan *Hypha) {
|
||||||
nodes, err := ioutil.ReadDir(path)
|
nodes, err := ioutil.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -22,14 +42,14 @@ func Index(path string, nestLevel uint, ch chan *Hypha) {
|
|||||||
util.IsCanonicalName(node.Name()) &&
|
util.IsCanonicalName(node.Name()) &&
|
||||||
node.Name() != ".git" &&
|
node.Name() != ".git" &&
|
||||||
!(nestLevel == 0 && node.Name() == "static") {
|
!(nestLevel == 0 && node.Name() == "static") {
|
||||||
Index(filepath.Join(path, node.Name()), nestLevel+1, ch)
|
indexHelper(filepath.Join(path, node.Name()), nestLevel+1, ch)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hyphaPartPath = filepath.Join(path, node.Name())
|
hyphaPartPath = filepath.Join(path, node.Name())
|
||||||
hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath)
|
hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath)
|
||||||
hypha = &Hypha{Name: hyphaName}
|
hypha = &Hypha{Name: hyphaName, Exists: true}
|
||||||
)
|
)
|
||||||
if !skip {
|
if !skip {
|
||||||
if isText {
|
if isText {
|
||||||
@ -39,6 +59,5 @@ func Index(path string, nestLevel uint, ch chan *Hypha) {
|
|||||||
}
|
}
|
||||||
ch <- hypha
|
ch <- hypha
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
166
hyphae/hyphae.go
166
hyphae/hyphae.go
@ -1,9 +1,47 @@
|
|||||||
package hyphae
|
package hyphae
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"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.
|
||||||
|
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
|
||||||
|
|
||||||
type Hypha struct {
|
type Hypha struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
@ -11,33 +49,125 @@ type Hypha struct {
|
|||||||
Exists bool
|
Exists bool
|
||||||
TextPath string
|
TextPath string
|
||||||
BinaryPath string
|
BinaryPath string
|
||||||
OutLinks []*Hypha
|
OutLinks []*Hypha // not used yet
|
||||||
BackLinks []*Hypha
|
BackLinks []*Hypha // not used yet
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
var byNames = make(map[string]*Hypha)
|
||||||
// Insert inserts the hypha into the mycelium. It overwrites the previous record, if there was any, and returns false. If the was no previous record, return true.
|
var byNamesMutex = sync.Mutex{}
|
||||||
|
|
||||||
|
// YieldExistingHyphae iterates over all hyphae and yields all existing ones.
|
||||||
|
func YieldExistingHyphae() chan *Hypha {
|
||||||
|
ch := make(chan *Hypha)
|
||||||
|
go func(ch chan *Hypha) {
|
||||||
|
for _, h := range byNames {
|
||||||
|
if h.Exists {
|
||||||
|
ch <- h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}(ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{
|
||||||
|
Name: hyphaName,
|
||||||
|
Exists: false,
|
||||||
|
TextPath: "",
|
||||||
|
BinaryPath: "",
|
||||||
|
OutLinks: make([]*Hypha, 0),
|
||||||
|
BackLinks: make([]*Hypha, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByName returns a hypha by name. If h.Exists, the returned hypha pointer is known to be part of the hypha index (byNames map).
|
||||||
|
func ByName(hyphaName string) (h *Hypha) {
|
||||||
|
byNamesMutex.Lock()
|
||||||
|
defer byNamesMutex.Unlock()
|
||||||
|
|
||||||
|
h, exists := byNames[hyphaName]
|
||||||
|
if exists {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return EmptyHypha(hyphaName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert inserts the hypha into the storage. It overwrites the previous record, if there was any, and returns false. If the was no previous record, return true.
|
||||||
func (h *Hypha) Insert() (justCreated bool) {
|
func (h *Hypha) Insert() (justCreated bool) {
|
||||||
var hp *Hypha
|
var hp *Hypha
|
||||||
hp, justCreated = ByName(h.Name)
|
hp = ByName(h.Name)
|
||||||
|
|
||||||
mycm.Lock()
|
byNamesMutex.Lock()
|
||||||
defer mycm.Unlock()
|
defer byNamesMutex.Unlock()
|
||||||
if justCreated {
|
if hp.Exists {
|
||||||
mycm.byNames[hp.Name] = h
|
|
||||||
} else {
|
|
||||||
hp = h
|
hp = h
|
||||||
|
} else {
|
||||||
|
byNames[hp.Name] = h
|
||||||
}
|
}
|
||||||
|
|
||||||
return justCreated
|
return !hp.Exists
|
||||||
}*/
|
}
|
||||||
|
|
||||||
// PhaseOut marks the hypha as non-existent. This is an idempotent operation.
|
func (h *Hypha) InsertIfNew() (justCreated bool) {
|
||||||
func (h *Hypha) PhaseOut() {
|
if h.Exists {
|
||||||
|
return h.Insert()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) delete() {
|
||||||
|
byNamesMutex.Lock()
|
||||||
h.Lock()
|
h.Lock()
|
||||||
h.Exists = false
|
delete(byNames, h.Name)
|
||||||
h.OutLinks = make([]*Hypha, 0)
|
DecrementCount()
|
||||||
h.TextPath = ""
|
byNamesMutex.Unlock()
|
||||||
h.BinaryPath = ""
|
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Hypha) renameTo(newName string) {
|
||||||
|
byNamesMutex.Lock()
|
||||||
|
h.Lock()
|
||||||
|
delete(byNames, h.Name)
|
||||||
|
h.Name = newName
|
||||||
|
byNames[h.Name] = h
|
||||||
|
byNamesMutex.Unlock()
|
||||||
|
h.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes.
|
||||||
|
func (h *Hypha) MergeIn(oh *Hypha) {
|
||||||
|
if h.TextPath == "" && oh.TextPath != "" {
|
||||||
|
h.TextPath = oh.TextPath
|
||||||
|
}
|
||||||
|
if oh.BinaryPath != "" {
|
||||||
|
if h.BinaryPath != "" {
|
||||||
|
log.Println("There is a file collision for binary part of a hypha:", h.BinaryPath, "and", oh.BinaryPath, "-- going on with the latter")
|
||||||
|
}
|
||||||
|
h.BinaryPath = oh.BinaryPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
122
hyphae/rename.go
Normal file
122
hyphae/rename.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package hyphae
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"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) {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
if nh.Name == "" {
|
||||||
|
rejectRenameLog(oh, u, "no new name given")
|
||||||
|
return errors.New("No new name is given"), "No name given"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !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>^?!:#@><*|\"\\'&%</code>"), "Invalid name"
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
newHypha.Lock()
|
||||||
|
defer newHypha.Unlock()
|
||||||
|
hop = history.Operation(history.TypeRenameHypha)
|
||||||
|
|
||||||
|
if err, errtitle := h.CanRename(u); errtitle != "" {
|
||||||
|
hop.WithError(err)
|
||||||
|
return hop, errtitle
|
||||||
|
}
|
||||||
|
if err, errtitle := canRenameThisToThat(h, newHypha, u); errtitle != "" {
|
||||||
|
hop.WithError(err)
|
||||||
|
return hop, errtitle
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
re = regexp.MustCompile(`(?i)` + h.Name)
|
||||||
|
replaceName = func(str string) string {
|
||||||
|
return re.ReplaceAllString(util.CanonicalName(str), newHypha.Name)
|
||||||
|
}
|
||||||
|
hyphaeToRename = findHyphaeToRename(h, recursive)
|
||||||
|
renameMap, err = renamingPairs(hyphaeToRename, replaceName)
|
||||||
|
renameMsg = "Rename ‘%s’ to ‘%s’"
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
hop.Errs = append(hop.Errs, err)
|
||||||
|
return hop, hop.FirstErrorText()
|
||||||
|
}
|
||||||
|
if recursive && len(hyphaeToRename) > 0 {
|
||||||
|
renameMsg += " recursively"
|
||||||
|
}
|
||||||
|
hop.WithFilesRenamed(renameMap).
|
||||||
|
WithMsg(fmt.Sprintf(renameMsg, h.Name, newHypha.Name)).
|
||||||
|
WithUser(u).
|
||||||
|
Apply()
|
||||||
|
if len(hop.Errs) == 0 {
|
||||||
|
for _, h := range hyphaeToRename {
|
||||||
|
h.renameTo(replaceName(h.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hop, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findHyphaeToRename(superhypha *Hypha, recursive bool) []*Hypha {
|
||||||
|
hyphae := []*Hypha{superhypha}
|
||||||
|
if recursive {
|
||||||
|
hyphae = append(hyphae, superhypha.Subhyphae()...)
|
||||||
|
}
|
||||||
|
return hyphae
|
||||||
|
}
|
||||||
|
|
||||||
|
func renamingPairs(hyphaeToRename []*Hypha, replaceName func(string) string) (map[string]string, error) {
|
||||||
|
renameMap := make(map[string]string)
|
||||||
|
newNames := make([]string, len(hyphaeToRename))
|
||||||
|
for _, h := range hyphaeToRename {
|
||||||
|
h.RLock()
|
||||||
|
newNames = append(newNames, replaceName(h.Name))
|
||||||
|
if h.TextPath != "" {
|
||||||
|
renameMap[h.TextPath] = replaceName(h.TextPath)
|
||||||
|
}
|
||||||
|
if h.BinaryPath != "" {
|
||||||
|
renameMap[h.BinaryPath] = replaceName(h.BinaryPath)
|
||||||
|
}
|
||||||
|
h.RUnlock()
|
||||||
|
}
|
||||||
|
if firstFailure, ok := AreFreeNames(newNames...); !ok {
|
||||||
|
return nil, errors.New("Hypha " + firstFailure + " already exists")
|
||||||
|
}
|
||||||
|
return renameMap, nil
|
||||||
|
}
|
66
hyphae/unattach.go
Normal file
66
hyphae/unattach.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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) {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
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, ""
|
||||||
|
}
|
122
hyphae/upload.go
Normal file
122
hyphae/upload.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
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()
|
||||||
|
*originalFullPath = fullPath
|
||||||
|
if h.Exists && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
|
||||||
|
return hop.Abort(), "No changes"
|
||||||
|
}
|
||||||
|
return hop.WithFiles(fullPath).WithUser(u).Apply(), ""
|
||||||
|
}
|
72
hyphae/view.go
Normal file
72
hyphae/view.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package hyphae
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
if h.TextPath == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
text, err := ioutil.ReadFile(h.TextPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", nil
|
||||||
|
} else if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(text), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// binaryHtmlBlock creates an html block for binary part of the hypha.
|
||||||
|
func (h *Hypha) BinaryHtmlBlock() string {
|
||||||
|
switch filepath.Ext(h.BinaryPath) {
|
||||||
|
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
<div class="binary-container binary-container_with-img">
|
||||||
|
<a href="/binary/%[1]s"><img src="/binary/%[1]s"/></a>
|
||||||
|
</div>`, h.Name)
|
||||||
|
case ".ogg", ".webm", ".mp4":
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
<div class="binary-container binary-container_with-video">
|
||||||
|
<video>
|
||||||
|
<source src="/binary/%[1]s"/>
|
||||||
|
<p>Your browser does not support video. <a href="/binary/%[1]s">Download video</a></p>
|
||||||
|
</video>
|
||||||
|
`, h.Name)
|
||||||
|
case ".mp3":
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
<div class="binary-container binary-container_with-audio">
|
||||||
|
<audio>
|
||||||
|
<source src="/binary/%[1]s"/>
|
||||||
|
<p>Your browser does not support audio. <a href="/binary/%[1]s">Download audio</a></p>
|
||||||
|
</audio>
|
||||||
|
`, h.Name)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
<div class="binary-container binary-container_with-nothing">
|
||||||
|
<p><a href="/binary/%s">Download media</a></p>
|
||||||
|
</div>
|
||||||
|
`, h.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetHeaderLinks() {
|
||||||
|
if userLinksHypha := ByName(util.HeaderLinksHypha); !userLinksHypha.Exists {
|
||||||
|
util.SetDefaultHeaderLinks()
|
||||||
|
} else {
|
||||||
|
contents, err := ioutil.ReadFile(userLinksHypha.TextPath)
|
||||||
|
if err != nil || len(contents) == 0 {
|
||||||
|
util.SetDefaultHeaderLinks()
|
||||||
|
} else {
|
||||||
|
text := string(contents)
|
||||||
|
util.ParseHeaderLinks(text, markup.Rocketlink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
main.go
36
main.go
@ -10,7 +10,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
@ -24,19 +23,6 @@ import (
|
|||||||
// WikiDir is a rooted path to the wiki storage directory.
|
// WikiDir is a rooted path to the wiki storage directory.
|
||||||
var WikiDir string
|
var WikiDir string
|
||||||
|
|
||||||
// HyphaPattern is a pattern which all hyphae must match.
|
|
||||||
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
|
|
||||||
|
|
||||||
// HyphaStorage is a mapping between canonical hypha names and their meta information.
|
|
||||||
var HyphaStorage = make(map[string]*HyphaData)
|
|
||||||
|
|
||||||
// IterateHyphaNamesWith is a closure to be passed to subpackages to let them iterate all hypha names read-only.
|
|
||||||
func IterateHyphaNamesWith(f func(string)) {
|
|
||||||
for hyphaName := range HyphaStorage {
|
|
||||||
f(hyphaName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HttpErr is used by many handlers to signal errors in a compact way.
|
// HttpErr is used by many handlers to signal errors in a compact way.
|
||||||
func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
|
func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
|
||||||
log.Println(errMsg, "for", name)
|
log.Println(errMsg, "for", name)
|
||||||
@ -64,8 +50,8 @@ func handlerList(w http.ResponseWriter, rq *http.Request) {
|
|||||||
pageCount = hyphae.Count()
|
pageCount = hyphae.Count()
|
||||||
u = user.FromRequest(rq)
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
for hyphaName, data := range HyphaStorage {
|
for h := range hyphae.YieldExistingHyphae() {
|
||||||
tbody += templates.HyphaListRowHTML(hyphaName, mimetype.FromExtension(filepath.Ext(data.BinaryPath)), data.BinaryPath != "")
|
tbody += templates.HyphaListRowHTML(h.Name, mimetype.FromExtension(filepath.Ext(h.BinaryPath)), h.BinaryPath != "")
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u))
|
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u))
|
||||||
}
|
}
|
||||||
@ -82,10 +68,9 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
hyphae.ResetCount()
|
hyphae.ResetCount()
|
||||||
HyphaStorage = make(map[string]*HyphaData)
|
|
||||||
log.Println("Wiki storage directory is", WikiDir)
|
log.Println("Wiki storage directory is", WikiDir)
|
||||||
log.Println("Start indexing hyphae...")
|
log.Println("Start indexing hyphae...")
|
||||||
Index(WikiDir)
|
hyphae.Index(WikiDir)
|
||||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@ -98,7 +83,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
|
|||||||
log.Println("Rejected", rq.URL)
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setHeaderLinks()
|
hyphae.SetHeaderLinks()
|
||||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,14 +92,13 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
|||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var randomHyphaName string
|
var randomHyphaName string
|
||||||
i := rand.Intn(hyphae.Count())
|
i := rand.Intn(hyphae.Count())
|
||||||
for hyphaName := range HyphaStorage {
|
for h := range hyphae.YieldExistingHyphae() {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
randomHyphaName = hyphaName
|
randomHyphaName = h.Name
|
||||||
break
|
|
||||||
}
|
}
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
|
http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -187,11 +171,11 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Println("Wiki storage directory is", WikiDir)
|
log.Println("Wiki storage directory is", WikiDir)
|
||||||
Index(WikiDir)
|
hyphae.Index(WikiDir)
|
||||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||||
|
|
||||||
history.Start(WikiDir)
|
history.Start(WikiDir)
|
||||||
setHeaderLinks()
|
hyphae.SetHeaderLinks()
|
||||||
|
|
||||||
go handleGemini()
|
go handleGemini()
|
||||||
|
|
||||||
@ -212,7 +196,7 @@ func main() {
|
|||||||
http.HandleFunc("/static/common.css", handlerStyle)
|
http.HandleFunc("/static/common.css", handlerStyle)
|
||||||
http.HandleFunc("/static/icon/", handlerIcon)
|
http.HandleFunc("/static/icon/", handlerIcon)
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
||||||
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
http.Redirect(w, rq, "/hypha/"+util.HomePage, http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
http.HandleFunc("/robots.txt", handlerRobotsTxt)
|
http.HandleFunc("/robots.txt", handlerRobotsTxt)
|
||||||
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
|
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
|
||||||
|
14
name.go
14
name.go
@ -11,23 +11,13 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isCanonicalName checks if the `name` is canonical.
|
|
||||||
func isCanonicalName(name string) bool {
|
|
||||||
return HyphaPattern.MatchString(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores.
|
|
||||||
func CanonicalName(name string) string {
|
|
||||||
return strings.ToLower(strings.ReplaceAll(name, " ", "_"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// naviTitle turns `canonicalName` into html string with each hypha path parts higlighted as links.
|
// naviTitle turns `canonicalName` into html string with each hypha path parts higlighted as links.
|
||||||
// TODO: rework as a template
|
// TODO: rework as a template
|
||||||
func naviTitle(canonicalName string) string {
|
func naviTitle(canonicalName string) string {
|
||||||
var (
|
var (
|
||||||
html = fmt.Sprintf(`<h1 class="navi-title" id="navi-title">
|
html = fmt.Sprintf(`<h1 class="navi-title" id="navi-title">
|
||||||
<a href="/page/%s">%s</a><span aria-hidden="true" class="navi-title__colon">:</span>`, util.HomePage, util.SiteNavIcon)
|
<a href="/hypha/%s">%s</a><span aria-hidden="true" class="navi-title__colon">:</span>`, util.HomePage, util.SiteNavIcon)
|
||||||
prevAcc = `/page/`
|
prevAcc = `/hypha/`
|
||||||
parts = strings.Split(canonicalName, "/")
|
parts = strings.Split(canonicalName, "/")
|
||||||
rel = "up"
|
rel = "up"
|
||||||
)
|
)
|
||||||
|
25
tree/tree.go
25
tree/tree.go
@ -6,6 +6,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,21 +82,21 @@ func mainFamilyFromPool(hyphaName string, subhyphaePool map[string]bool) *mainFa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
|
// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
|
||||||
func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next string) {
|
func Tree(hyphaName string) (html, prev, next string) {
|
||||||
var (
|
var (
|
||||||
// One of the siblings is the hypha with name `hyphaName`
|
// One of the siblings is the hypha with name `hyphaName`
|
||||||
siblings = findSiblings(hyphaName, hyphaIterator)
|
siblings = findSiblings(hyphaName)
|
||||||
subhyphaePool = make(map[string]bool)
|
subhyphaePool = make(map[string]bool)
|
||||||
I int
|
I int
|
||||||
)
|
)
|
||||||
hyphaIterator(func(otherHyphaName string) {
|
for h := range hyphae.YieldExistingHyphae() {
|
||||||
for _, s := range siblings {
|
for _, s := range siblings {
|
||||||
s.checkThisChild(otherHyphaName)
|
s.checkThisChild(h.Name)
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(otherHyphaName, hyphaName+"/") {
|
if strings.HasPrefix(h.Name, hyphaName+"/") {
|
||||||
subhyphaePool[otherHyphaName] = true
|
subhyphaePool[h.Name] = true
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
for i, s := range siblings {
|
for i, s := range siblings {
|
||||||
if s.name == hyphaName {
|
if s.name == hyphaName {
|
||||||
I = i
|
I = i
|
||||||
@ -116,13 +117,13 @@ func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next
|
|||||||
return fmt.Sprintf(`<ul class="navitree">%s</ul>`, html), prev, next
|
return fmt.Sprintf(`<ul class="navitree">%s</ul>`, html), prev, next
|
||||||
}
|
}
|
||||||
|
|
||||||
func findSiblings(hyphaName string, hyphaIterator func(func(string))) []*sibling {
|
func findSiblings(hyphaName string) []*sibling {
|
||||||
siblings := []*sibling{&sibling{name: hyphaName, hasChildren: true}}
|
siblings := []*sibling{&sibling{name: hyphaName, hasChildren: true}}
|
||||||
hyphaIterator(func(otherHyphaName string) {
|
for h := range hyphae.YieldExistingHyphae() {
|
||||||
if path.Dir(hyphaName) == path.Dir(otherHyphaName) && hyphaName != otherHyphaName {
|
if path.Dir(hyphaName) == path.Dir(h.Name) && hyphaName != h.Name {
|
||||||
siblings = append(siblings, &sibling{name: otherHyphaName, hasChildren: false})
|
siblings = append(siblings, &sibling{name: h.Name, hasChildren: false})
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
sort.Slice(siblings, func(i, j int) bool {
|
sort.Slice(siblings, func(i, j int) bool {
|
||||||
return siblings[i].name < siblings[j].name
|
return siblings[i].name < siblings[j].name
|
||||||
})
|
})
|
||||||
|
11
util/util.go
11
util/util.go
@ -41,17 +41,6 @@ func HTTP200Page(w http.ResponseWriter, page string) {
|
|||||||
w.Write([]byte(page))
|
w.Write([]byte(page))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindSubhyphae finds names of existing hyphae given the `hyphaIterator`.
|
|
||||||
func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string {
|
|
||||||
subhyphae := make([]string, 0)
|
|
||||||
hyphaIterator(func(otherHyphaName string) {
|
|
||||||
if strings.HasPrefix(otherHyphaName, hyphaName+"/") {
|
|
||||||
subhyphae = append(subhyphae, otherHyphaName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return subhyphae
|
|
||||||
}
|
|
||||||
|
|
||||||
func RandomString(n int) (string, error) {
|
func RandomString(n int) (string, error) {
|
||||||
bytes := make([]byte, n)
|
bytes := make([]byte, n)
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user