From 7c41403eee0fd3df04a583b64a5e5dc60b461ddb Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Wed, 1 Sep 2021 03:28:12 +0800 Subject: [PATCH] Implement backlinks updating --- hyphae/backlinks.go | 100 ++++++++++++++++++++++++++++++++++---------- shroom/delete.go | 2 + shroom/rename.go | 2 + shroom/search.go | 1 + shroom/upload.go | 8 ++++ 5 files changed, 91 insertions(+), 22 deletions(-) diff --git a/hyphae/backlinks.go b/hyphae/backlinks.go index 961bea8..f6961cb 100644 --- a/hyphae/backlinks.go +++ b/hyphae/backlinks.go @@ -15,6 +15,25 @@ import ( // Using set here seems like the most appropriate solution type linkSet map[string]struct{} +func toLinkSet(xs []string) linkSet { + result := make(linkSet) + for _, x := range xs { + result[x] = struct{}{} + } + return result +} + +func fetchText(h *Hypha) string { + if h.TextPath == "" { + return "" + } + text, err := os.ReadFile(h.TextPath) + if err == nil { + return string(text) + } + return "" +} + var backlinkIndex = make(map[string]linkSet) var backlinkIndexMutex = sync.Mutex{} @@ -23,41 +42,85 @@ func IndexBacklinks() { // It is safe to ignore the mutex, because there is only one worker. src := FilterTextHyphae(YieldExistingHyphae()) for h := range src { - fileContentsT, errT := os.ReadFile(h.TextPath) - if errT == nil { - links := ExtractHyphaLinksFromContent(h.Name, string(fileContentsT)) - for _, link := range links { - if _, exists := backlinkIndex[link]; !exists { - backlinkIndex[link] = make(linkSet) - } - backlinkIndex[link][h.Name] = struct{}{} + links := ExtractHyphaLinksFromContent(h.Name, fetchText(h)) + for _, link := range links { + if _, exists := backlinkIndex[link]; !exists { + backlinkIndex[link] = make(linkSet) } + backlinkIndex[link][h.Name] = struct{}{} } } } +func BacklinksOnEdit(h *Hypha, oldText string) { + backlinkIndexMutex.Lock() + newLinks := toLinkSet(ExtractHyphaLinks(h)) + oldLinks := toLinkSet(ExtractHyphaLinksFromContent(h.Name, oldText)) + for link := range oldLinks { + if _, exists := newLinks[link]; !exists { + delete(backlinkIndex[link], h.Name) + } + } + for link := range newLinks { + if _, exists := oldLinks[link]; !exists { + if _, exists := backlinkIndex[link]; !exists { + backlinkIndex[link] = make(linkSet) + } + backlinkIndex[link][h.Name] = struct{}{} + } + } + backlinkIndexMutex.Unlock() +} + +func BacklinksOnDelete(h *Hypha, oldText string) { + backlinkIndexMutex.Lock() + oldLinks := ExtractHyphaLinksFromContent(h.Name, oldText) + for _, link := range oldLinks { + if lSet, exists := backlinkIndex[link]; exists { + delete(lSet, h.Name) + } + } + backlinkIndexMutex.Unlock() +} + +func BacklinksOnRename(h *Hypha, oldName string) { + backlinkIndexMutex.Lock() + actualLinks := ExtractHyphaLinks(h) + for _, link := range actualLinks { + if lSet, exists := backlinkIndex[link]; exists { + delete(lSet, oldName) + backlinkIndex[link][h.Name] = struct{}{} + } + } + backlinkIndexMutex.Unlock() +} + +// YieldHyphaBacklinks gets backlinks for a desired hypha, sorts and iterates over them func YieldHyphaBacklinks(query string) <-chan string { hyphaName := util.CanonicalName(query) out := make(chan string) sorted := PathographicSort(out) go func() { - links := backlinkIndex[hyphaName] - for link := range links { - out <- link + links, exists := backlinkIndex[hyphaName] + if exists { + for link := range links { + out <- link + } } close(out) }() return sorted } -// YieldHyphaLinks extracts hypha links from a desired hypha and iterates over them +// YieldHyphaLinks extracts hypha links from a desired hypha, sorts and iterates over them func YieldHyphaLinks(query string) <-chan string { // That is merely a debug function, but it could be useful. // Should we extract them into link-specific subfile? -- chekoopa hyphaName := util.CanonicalName(query) out := make(chan string) go func() { - links := ExtractHyphaLinks(hyphaName) + var h = ByName(hyphaName) + links := ExtractHyphaLinks(h) for _, link := range links { out <- link } @@ -67,15 +130,8 @@ func YieldHyphaLinks(query string) <-chan string { } // ExtractHyphaLinks extracts hypha links from a desired hypha -func ExtractHyphaLinks(hyphaName string) []string { - var h = ByName(hyphaName) - if h.Exists { - fileContentsT, errT := os.ReadFile(h.TextPath) - if errT == nil { - return ExtractHyphaLinksFromContent(hyphaName, string(fileContentsT)) - } - } - return make([]string, 0) +func ExtractHyphaLinks(h *Hypha) []string { + return ExtractHyphaLinksFromContent(h.Name, fetchText(h)) } // ExtractHyphaLinksFromContent extracts hypha links from a provided text diff --git a/shroom/delete.go b/shroom/delete.go index 2341351..6f71146 100644 --- a/shroom/delete.go +++ b/shroom/delete.go @@ -17,12 +17,14 @@ func DeleteHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitl return hop, errtitle } + originalText, _ := FetchTextPart(h) hop. WithFilesRemoved(h.TextPath, h.BinaryPath). WithMsg(fmt.Sprintf("Delete ā€˜%sā€™", h.Name)). WithUser(u). Apply() if !hop.HasErrors() { + hyphae.BacklinksOnDelete(h, originalText) h.Delete() } return hop, "" diff --git a/shroom/rename.go b/shroom/rename.go index a2d44da..e4d3fc2 100644 --- a/shroom/rename.go +++ b/shroom/rename.go @@ -67,11 +67,13 @@ func RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *use Apply() if len(hop.Errs) == 0 { for _, h := range hyphaeToRename { + oldName := h.Name h.RenameTo(replaceName(h.Name)) h.Lock() h.TextPath = replaceName(h.TextPath) h.BinaryPath = replaceName(h.BinaryPath) h.Unlock() + hyphae.BacklinksOnRename(h, oldName) } } return hop, "" diff --git a/shroom/search.go b/shroom/search.go index 1497ad8..953c40e 100644 --- a/shroom/search.go +++ b/shroom/search.go @@ -7,6 +7,7 @@ import ( "github.com/bouncepaw/mycorrhiza/util" ) +// YieldHyphaNamesContainingString picks hyphae with have a string in their title, sorts and iterates over them. func YieldHyphaNamesContainingString(query string) <-chan string { query = util.CanonicalName(query) out := make(chan string) diff --git a/shroom/upload.go b/shroom/upload.go index d8ca6d2..b6e0c2a 100644 --- a/shroom/upload.go +++ b/shroom/upload.go @@ -66,6 +66,7 @@ func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte var ( fullPath = filepath.Join(files.HyphaeDir(), h.Name+ext) originalFullPath = &h.TextPath + originalText = "" // for backlink update ) // Reject if the path is outside the hyphae dir if !strings.HasPrefix(fullPath, files.HyphaeDir()) { @@ -80,6 +81,10 @@ func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte return hop.WithErrAbort(err), err.Error() } + if hop.Type == history.TypeEditText { + originalText, _ = FetchTextPart(h) + } + if err := os.WriteFile(fullPath, data, 0666); err != nil { return hop.WithErrAbort(err), err.Error() } @@ -96,5 +101,8 @@ func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte return hop.Abort(), "No changes" } *originalFullPath = fullPath + if hop.Type == history.TypeEditText { + hyphae.BacklinksOnEdit(h, originalText) + } return hop.WithFiles(fullPath).WithUser(u).Apply(), "" }