258 lines
5.3 KiB
Go
258 lines
5.3 KiB
Go
|
package tui
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"sync"
|
||
|
|
||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-lens/internal/schema/common"
|
||
|
"github.com/gdamore/tcell/v2"
|
||
|
"github.com/rivo/tview"
|
||
|
)
|
||
|
|
||
|
type BucketsView struct {
|
||
|
*tview.Box
|
||
|
|
||
|
mu sync.Mutex
|
||
|
|
||
|
view *tview.TreeView
|
||
|
nodeToUpdate *tview.TreeNode
|
||
|
|
||
|
ui *UI
|
||
|
filter *Filter
|
||
|
}
|
||
|
|
||
|
type bucketNode struct {
|
||
|
bucket *Bucket
|
||
|
filter *Filter
|
||
|
}
|
||
|
|
||
|
func NewBucketsView(ui *UI, filter *Filter) *BucketsView {
|
||
|
return &BucketsView{
|
||
|
Box: tview.NewBox(),
|
||
|
view: tview.NewTreeView(),
|
||
|
ui: ui,
|
||
|
filter: filter,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (v *BucketsView) Mount(_ context.Context) error {
|
||
|
root := tview.NewTreeNode(".")
|
||
|
root.SetExpanded(false)
|
||
|
root.SetSelectable(false)
|
||
|
root.SetReference(&bucketNode{
|
||
|
bucket: &Bucket{NextParser: v.ui.rootParser},
|
||
|
filter: v.filter,
|
||
|
})
|
||
|
|
||
|
v.nodeToUpdate = root
|
||
|
|
||
|
v.view.SetRoot(root)
|
||
|
v.view.SetCurrentNode(root)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (v *BucketsView) Update(ctx context.Context) error {
|
||
|
if v.nodeToUpdate == nil {
|
||
|
return nil
|
||
|
}
|
||
|
defer func() { v.nodeToUpdate = nil }()
|
||
|
|
||
|
ctx, cancel := context.WithCancel(ctx)
|
||
|
defer cancel()
|
||
|
|
||
|
ready := make(chan struct{})
|
||
|
errCh := make(chan error)
|
||
|
|
||
|
tmp := tview.NewTreeNode(v.nodeToUpdate.GetText())
|
||
|
tmp.SetReference(v.nodeToUpdate.GetReference())
|
||
|
|
||
|
node := v.nodeToUpdate.GetReference().(*bucketNode)
|
||
|
|
||
|
go func() {
|
||
|
defer close(ready)
|
||
|
|
||
|
hasBuckets, err := HasBuckets(ctx, v.ui.db, node.bucket.Path)
|
||
|
if err != nil {
|
||
|
errCh <- err
|
||
|
}
|
||
|
|
||
|
// Show the selected bucket's records instead.
|
||
|
if !hasBuckets && node.bucket.NextParser != nil {
|
||
|
v.ui.moveNextPage(NewRecordsView(v.ui, node.bucket, node.filter))
|
||
|
}
|
||
|
|
||
|
if v.nodeToUpdate.IsExpanded() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
err = v.loadNodeChildren(ctx, tmp, node.filter)
|
||
|
if err != nil {
|
||
|
errCh <- err
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
case <-ready:
|
||
|
v.mu.Lock()
|
||
|
v.nodeToUpdate.SetChildren(tmp.GetChildren())
|
||
|
v.nodeToUpdate.SetExpanded(!v.nodeToUpdate.IsExpanded())
|
||
|
v.mu.Unlock()
|
||
|
case err := <-errCh:
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (v *BucketsView) Unmount() {
|
||
|
}
|
||
|
|
||
|
func (v *BucketsView) Draw(screen tcell.Screen) {
|
||
|
x, y, width, height := v.GetInnerRect()
|
||
|
v.view.SetRect(x, y, width, height)
|
||
|
|
||
|
v.view.Draw(screen)
|
||
|
}
|
||
|
|
||
|
func (v *BucketsView) loadNodeChildren(
|
||
|
ctx context.Context, node *tview.TreeNode, filter *Filter,
|
||
|
) error {
|
||
|
parentBucket := node.GetReference().(*bucketNode).bucket
|
||
|
|
||
|
path := parentBucket.Path
|
||
|
parser := parentBucket.NextParser
|
||
|
|
||
|
buffer, err := LoadBuckets(ctx, v.ui.db, path, v.ui.loadBufferSize)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
for item := range buffer {
|
||
|
if item.err != nil {
|
||
|
return item.err
|
||
|
}
|
||
|
bucket := item.val
|
||
|
|
||
|
bucket.Entry, bucket.NextParser, err = parser(bucket.Name, nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
satisfies, err := v.bucketSatisfiesFilter(ctx, bucket, filter)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if !satisfies {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
child := tview.NewTreeNode(bucket.Entry.String()).
|
||
|
SetSelectable(true).
|
||
|
SetExpanded(false).
|
||
|
SetReference(&bucketNode{
|
||
|
bucket: bucket,
|
||
|
filter: filter.Apply(bucket.Entry),
|
||
|
})
|
||
|
|
||
|
node.AddChild(child)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (v *BucketsView) bucketSatisfiesFilter(
|
||
|
ctx context.Context, bucket *Bucket, filter *Filter,
|
||
|
) (bool, error) {
|
||
|
// Does the current bucket satisfies the filter?
|
||
|
filter = filter.Apply(bucket.Entry)
|
||
|
|
||
|
if filter.Result() == common.Yes {
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
if filter.Result() == common.No {
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
ctx, cancel := context.WithCancel(ctx)
|
||
|
defer cancel()
|
||
|
|
||
|
// Check the current bucket's nested buckets if exist
|
||
|
bucketsBuffer, err := LoadBuckets(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
for item := range bucketsBuffer {
|
||
|
if item.err != nil {
|
||
|
return false, item.err
|
||
|
}
|
||
|
b := item.val
|
||
|
|
||
|
b.Entry, b.NextParser, err = bucket.NextParser(b.Name, nil)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
satisfies, err := v.bucketSatisfiesFilter(ctx, b, filter)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
if satisfies {
|
||
|
return true, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check the current bucket's nested records if exist
|
||
|
recordsBuffer, err := LoadRecords(ctx, v.ui.db, bucket.Path, v.ui.loadBufferSize)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
for item := range recordsBuffer {
|
||
|
if item.err != nil {
|
||
|
return false, item.err
|
||
|
}
|
||
|
r := item.val
|
||
|
|
||
|
r.Entry, _, err = bucket.NextParser(r.Key, r.Value)
|
||
|
if err != nil {
|
||
|
return false, err
|
||
|
}
|
||
|
|
||
|
if filter.Apply(r.Entry).Result() == common.Yes {
|
||
|
return true, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
func (v *BucketsView) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||
|
return v.WrapInputHandler(func(event *tcell.EventKey, _ func(tview.Primitive)) {
|
||
|
currentNode := v.view.GetCurrentNode()
|
||
|
if currentNode == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch event.Key() {
|
||
|
case tcell.KeyEnter:
|
||
|
// Expand or collapse the selected bucket's nested buckets,
|
||
|
// otherwise, navigate to that bucket's records.
|
||
|
v.nodeToUpdate = currentNode
|
||
|
case tcell.KeyCtrlR:
|
||
|
// Navigate to the selected bucket's records.
|
||
|
bucketNode := currentNode.GetReference().(*bucketNode)
|
||
|
v.ui.moveNextPage(NewRecordsView(v.ui, bucketNode.bucket, bucketNode.filter))
|
||
|
case tcell.KeyCtrlD:
|
||
|
// Navigate to the selected bucket's detailed view.
|
||
|
bucketNode := currentNode.GetReference().(*bucketNode)
|
||
|
v.ui.moveNextPage(NewDetailedView(bucketNode.bucket.Entry.DetailedString()))
|
||
|
default:
|
||
|
v.view.InputHandler()(event, func(tview.Primitive) {})
|
||
|
}
|
||
|
})
|
||
|
}
|