gosuki/pkg/tree/tree.go

295 lines
7.0 KiB
Go
Raw Normal View History

2023-09-16 15:25:25 +00:00
//
// Copyright ⓒ 2023 Chakib Ben Ziane <contact@blob42.xyz> and [`GoSuki` contributors]
// (https://github.com/blob42/gosuki/graphs/contributors).
//
// All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This file is part of GoSuki.
//
// GoSuki is free software: you can redistribute it and/or modify it under the terms of
// the GNU Affero General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// GoSuki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License along with
// gosuki. If not, see <http://www.gnu.org/licenses/>.
2018-11-09 17:25:50 +00:00
package tree
2018-05-27 15:36:03 +00:00
2018-10-25 16:09:03 +00:00
import (
"fmt"
2020-08-12 18:13:01 +00:00
2023-09-16 19:41:33 +00:00
"git.blob42.xyz/gosuki/gosuki/pkg/bookmarks"
"git.blob42.xyz/gosuki/gosuki/internal/index"
"git.blob42.xyz/gosuki/gosuki/internal/logging"
"git.blob42.xyz/gosuki/gosuki/internal/utils"
2022-12-11 22:06:07 +00:00
"github.com/kr/pretty"
2018-10-25 16:09:03 +00:00
"github.com/xlab/treeprint"
)
2022-10-07 20:15:48 +00:00
var log = logging.GetLogger("TREE")
2018-11-09 17:25:50 +00:00
type NodeType int
const (
2022-11-07 19:07:13 +00:00
RootNode NodeType = iota
URLNode
FolderNode
TagNode
)
type Node struct {
Name string
Type NodeType // folder, tag, url
URL string
Tags []string
Desc string
HasChanged bool
NameHash uint64 // hash of the metadata
Parent *Node
Children []*Node
}
2018-10-25 16:09:03 +00:00
func (node *Node) GetRoot() *Node {
var n *Node
if node.Type == RootNode {
return node
}
2018-10-25 16:09:03 +00:00
if node.Parent != nil {
n = node.Parent.GetRoot()
}
return n
2018-10-25 16:09:03 +00:00
}
2022-12-10 01:49:04 +00:00
// Returns the ancestor of this node
func Ancestor(node *Node) *Node {
if node.Parent == nil {
return node
} else {
return Ancestor(node.Parent)
}
}
2022-12-11 22:06:07 +00:00
func (node *Node) DirectChildOf(parent *Node) bool {
2022-12-26 18:15:00 +00:00
if len(parent.Children) == 0 { return false }
var found bool
for _, child := range parent.Children {
if node == child { found = true }
}
return found
2022-12-11 22:06:07 +00:00
}
2022-12-10 01:49:04 +00:00
2023-01-11 11:14:10 +00:00
// Get all parents for node by traversing from leaf to root
func (node *Node) GetFolderParents() []*Node {
var parents []*Node
if node.Parent == nil {
return parents
}
if node.Parent.Type == FolderNode {
parents = append(parents, node.Parent)
}
if node.Parent.Type != RootNode {
parents = append(parents, node.Parent.GetFolderParents()...)
}
return parents
}
// Recursively traverse the tree from a root and find all occurences of [url]
// whose parent is a folder without using url.Parent as a reference
// Returns a list of nodes that match the criteria
func FindParents(root *Node, url *Node, nt NodeType) []*Node {
var parents []*Node
2023-01-11 11:14:10 +00:00
if root == nil || len(root.Children) <= 0 {
return parents
2023-01-11 11:14:10 +00:00
}
if root.Type == nt && FindNode(url, root) {
parents = append(parents, root)
2023-01-11 11:14:10 +00:00
}
for _, child := range root.Children {
parents = append(parents, FindParents(child, url, nt)...)
2023-01-11 11:14:10 +00:00
}
return parents
2023-01-11 11:14:10 +00:00
}
2022-12-10 01:49:04 +00:00
// Finds a node and the tree starting at root
func FindNode(node *Node, root *Node) bool {
if node == root {
return true
} else {
for _, child := range root.Children {
found := FindNode(node, child)
if found { return true }
}
}
return false
}
2023-01-11 11:14:10 +00:00
//Find all occurences of a node in a tree
func GetNodes(root *Node, url *Node) []*Node {
var nodes []*Node
if root == nil || url == nil {
return nodes
}
if root == url {
nodes = append(nodes, root)
}
for _, child := range root.Children {
nodes = append(nodes, GetNodes(child, url)...)
}
return nodes
}
2022-12-11 22:06:07 +00:00
func FindNodeByName(name string, root *Node) bool {
if name == root.Name {
return true
} else {
for _, child := range root.Children {
found := FindNodeByName(name, child)
if found { return true }
}
}
return false
}
2022-12-15 02:22:54 +00:00
// Inserts child node into parent node. Parent will point to child
// and child will point to parent EXCEPT when parent is a TAG node.
// If parent is a Tag node, child should not point back to parent
// as URL nodes should always point to folder parent nodes only.
2022-10-07 20:15:48 +00:00
func AddChild(parent *Node, child *Node) {
2022-11-07 19:07:13 +00:00
log.Debugf("adding child %v: <%s>", child.Type, child.Name)
2022-10-07 20:15:48 +00:00
2022-12-15 02:22:54 +00:00
2022-10-07 20:15:48 +00:00
if len(parent.Children) == 0 {
parent.Children = []*Node{child}
2022-12-15 02:22:54 +00:00
// Do not point back to TAG parent node from child
if parent.Type != TagNode {
child.Parent = parent
}
2022-10-07 20:15:48 +00:00
return
}
for _, n := range parent.Children {
if child == n {
2022-12-11 22:06:07 +00:00
// log.Errorf("<%s> Node already exists", child)
2022-12-15 02:22:54 +00:00
log.Info(pretty.Sprintf("skipping node <%s>, already exists", child.Name))
// update n with child metadata
log.Debugf("updating node <%s> with metadata <%s>", n.Name, child.Name)
n.Name = child.Name
2022-10-07 20:15:48 +00:00
return
2018-11-13 16:11:16 +00:00
}
}
2022-10-07 20:15:48 +00:00
parent.Children = append(parent.Children, child)
2022-12-15 02:22:54 +00:00
if parent.Type != TagNode {
child.Parent = parent
}
2018-11-13 16:11:16 +00:00
}
2018-10-25 16:09:03 +00:00
func PrintTree(root *Node) {
2022-12-10 01:49:04 +00:00
fmt.Println("---")
fmt.Println("PrintTree")
2018-10-25 16:09:03 +00:00
var walk func(node *Node, tree treeprint.Tree)
tree := treeprint.New()
walk = func(node *Node, t treeprint.Tree) {
if len(node.Children) > 0 {
2022-12-10 01:49:04 +00:00
t = t.AddBranch(fmt.Sprintf("%#v <%s>", node.Type, node.Name))
2018-10-25 16:09:03 +00:00
for _, child := range node.Children {
2022-12-10 01:49:04 +00:00
walk(child, t)
2018-10-25 16:09:03 +00:00
}
} else {
2022-12-10 01:49:04 +00:00
t.AddNode(fmt.Sprintf("%#v <%s>", node.Type, node.Name))
2018-10-25 16:09:03 +00:00
}
}
walk(root, tree)
2018-11-09 17:25:50 +00:00
fmt.Println(tree.String())
2022-12-10 01:49:04 +00:00
fmt.Println("---")
2018-10-25 16:09:03 +00:00
}
2018-05-27 15:36:03 +00:00
// Rebuilds the memory url index after parsing all bookmarks.
// Keeps memory index in sync with last known state of browser bookmarks
2018-11-09 17:25:50 +00:00
func WalkBuildIndex(node *Node, index index.HashTree) {
if node.Type == URLNode {
2018-11-09 17:25:50 +00:00
index.Insert(node.URL, node)
2018-05-27 15:36:03 +00:00
//log.Debugf("Inserted URL: %s and Hash: %v", node.URL, node.NameHash)
}
if len(node.Children) > 0 {
for _, node := range node.Children {
2019-03-01 18:14:12 +00:00
WalkBuildIndex(node, index)
2018-05-27 15:36:03 +00:00
}
}
}
// Get all possible tags for this url node The tags make sense only in the
// context of a URL node This will traverse the three breadth first to find all
// parent folders and add them as a tag. URL nodes should already be populated
// with the list of tags that exist under the TAG tree. So we only need to find
// the parent folders and turn them into tags.
2022-12-15 02:22:54 +00:00
func (node *Node) getTags() []string {
if node.Type != URLNode {
2022-12-26 19:55:48 +00:00
return []string{}
2022-12-15 02:22:54 +00:00
}
root := node.GetRoot()
parentFolders := FindParents(root, node, FolderNode)
parentTags := FindParents(root, node, TagNode)
for _, f := range parentFolders {
node.Tags = utils.Extends(node.Tags, f.Name)
}
for _, t := range parentTags {
node.Tags = utils.Extends(node.Tags, t.Name)
2022-12-15 02:22:54 +00:00
}
2022-12-26 19:55:48 +00:00
return node.Tags
2022-12-15 02:22:54 +00:00
}
func (node *Node) GetBookmark() *bookmarks.Bookmark {
2022-12-26 19:55:48 +00:00
if node.Type != URLNode {
return nil
}
return &bookmarks.Bookmark{
2018-11-09 17:25:50 +00:00
URL: node.URL,
Metadata: node.Name,
Desc: node.Desc,
2022-12-15 02:22:54 +00:00
Tags: node.getTags(),
2018-05-27 15:36:03 +00:00
}
}