Index: LICENSE.txt ================================================================== --- LICENSE.txt +++ LICENSE.txt @@ -1,6 +1,6 @@ -Copyright (c) 2020-2021 Detlef Stern +Copyright (c) 2020-2022 Detlef Stern Licensed under the EUPL Zettelstore is licensed under the European Union Public License, version 1.2 or later (EUPL v. 1.2). The license is available in the official languages of the Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -0.1 +0.2 Index: ast/ast.go ================================================================== --- ast/ast.go +++ ast/ast.go @@ -6,11 +6,11 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. +// Package ast provides the abstract syntax tree for parsed zettel content. package ast import ( "net/url" @@ -25,10 +25,11 @@ Meta *meta.Meta // Original metadata Content domain.Content // Original content Zid id.Zid // Zettel identification. InhMeta *meta.Meta // Metadata of the zettel, with inherited values. Ast *BlockListNode // Zettel abstract syntax tree is a sequence of block nodes. + Syntax string // Syntax / parser that produced the Ast } // Node is the interface, all nodes must implement. type Node interface { WalkChildren(v Visitor) Index: ast/attr.go ================================================================== --- ast/attr.go +++ ast/attr.go @@ -6,16 +6,13 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. package ast -import ( - "strings" -) +import "strings" // Attributes store additional information about some node types. type Attributes struct { Attrs map[string]string } Index: ast/attr_test.go ================================================================== --- ast/attr_test.go +++ ast/attr_test.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. package ast_test import ( "testing" Index: ast/block.go ================================================================== --- ast/block.go +++ ast/block.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. package ast // Definition of Block nodes. // BlockListNode is a list of BlockNodes. Index: ast/inline.go ================================================================== --- ast/inline.go +++ ast/inline.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. package ast // Definitions of inline nodes. // InlineListNode is a list of BlockNodes. @@ -36,11 +35,11 @@ inl = append(inl, &TextNode{Text: word}) } return &InlineListNode{List: inl} } -// WalkChildren walks down to the descriptions. +// WalkChildren walks down to the list. func (iln *InlineListNode) WalkChildren(v Visitor) { for _, bn := range iln.List { Walk(v, bn) } } @@ -199,23 +198,21 @@ // FormatKind specifies the format that is applied to the inline nodes. type FormatKind uint8 // Constants for FormatCode const ( - _ FormatKind = iota - FormatEmph // Emphasized text. - FormatStrong // Strongly emphasized text. - FormatInsert // Inserted text. - FormatDelete // Deleted text. - FormatSuper // Superscripted text. - FormatSub // SubscriptedText. - FormatQuote // Quoted text. - FormatQuotation // Quotation text. - FormatSmall // Smaller text. - FormatSpan // Generic inline container. - FormatMonospace // Monospaced text. - FormatEmphDeprecated // Deprecated kind of emphasized text. + _ FormatKind = iota + FormatEmph // Emphasized text. + FormatStrong // Strongly emphasized text. + FormatInsert // Inserted text. + FormatDelete // Deleted text. + FormatSuper // Superscripted text. + FormatSub // SubscriptedText. + FormatQuote // Quoted text. + FormatQuotation // Quotation text. + FormatSpan // Generic inline container. + FormatMonospace // Monospaced text. ) func (*FormatNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the formatted text. Index: ast/material.go ================================================================== --- ast/material.go +++ ast/material.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. package ast // MaterialNode references the various types of zettel material. type MaterialNode interface { Node Index: ast/ref.go ================================================================== --- ast/ref.go +++ ast/ref.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. package ast import ( "net/url" Index: ast/ref_test.go ================================================================== --- ast/ref_test.go +++ ast/ref_test.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast_test provides the tests for the abstract syntax tree. package ast_test import ( "testing" Index: ast/walk.go ================================================================== --- ast/walk.go +++ ast/walk.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package ast provides the abstract syntax tree. package ast // Visitor is a visitor for walking the AST. type Visitor interface { Visit(node Node) Visitor @@ -19,10 +18,16 @@ // Walk traverses the AST. func Walk(v Visitor, node Node) { if v = v.Visit(node); v == nil { return } + + // Implementation note: + // It is much faster to use interface dispatching than to use a switch statement. + // On my "cpu: Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz", a switch statement + // implementation tooks approx 940-980 ns/op. Interface dispatching is in the + // range of 900-930 ns/op. node.WalkChildren(v) v.Visit(nil) } // WalkItemSlice traverses an item slice. ADDED ast/walk_test.go Index: ast/walk_test.go ================================================================== --- /dev/null +++ ast/walk_test.go @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2021 Detlef Stern +// +// This file is part of zettelstore. +// +// Zettelstore is licensed under the latest version of the EUPL (European Union +// Public License). Please see file LICENSE.txt for your rights and obligations +// under this license. +//----------------------------------------------------------------------------- + +package ast_test + +import ( + "testing" + + "zettelstore.de/z/ast" +) + +func BenchmarkWalk(b *testing.B) { + root := &ast.BlockListNode{ + List: []ast.BlockNode{ + &ast.HeadingNode{ + Inlines: ast.CreateInlineListNodeFromWords("A", "Simple", "Heading"), + }, + &ast.ParaNode{ + Inlines: ast.CreateInlineListNodeFromWords("This", "is", "the", "introduction."), + }, + &ast.NestedListNode{ + Kind: ast.NestedListUnordered, + Items: []ast.ItemSlice{ + []ast.ItemNode{ + &ast.ParaNode{ + Inlines: ast.CreateInlineListNodeFromWords("Item", "1"), + }, + }, + []ast.ItemNode{ + &ast.ParaNode{ + Inlines: ast.CreateInlineListNodeFromWords("Item", "2"), + }, + }, + }, + }, + &ast.ParaNode{ + Inlines: ast.CreateInlineListNodeFromWords("This", "is", "some", "intermediate", "text."), + }, + &ast.ParaNode{ + Inlines: ast.CreateInlineListNode( + &ast.FormatNode{ + Kind: ast.FormatEmph, + Attrs: &ast.Attributes{ + Attrs: map[string]string{ + "": "class", + "color": "green", + }, + }, + Inlines: ast.CreateInlineListNodeFromWords("This", "is", "some", "emphasized", "text."), + }, + &ast.SpaceNode{Lexeme: " "}, + &ast.LinkNode{ + Ref: &ast.Reference{ + Value: "http://zettelstore.de", + }, + Inlines: ast.CreateInlineListNodeFromWords("URL", "text."), + OnlyRef: false, + }, + ), + }, + }, + } + v := benchVisitor{} + b.ResetTimer() + for n := 0; n < b.N; n++ { + ast.Walk(&v, root) + } +} + +type benchVisitor struct{} + +func (bv *benchVisitor) Visit(ast.Node) ast.Visitor { return bv } Index: auth/auth.go ================================================================== --- auth/auth.go +++ auth/auth.go @@ -94,8 +94,11 @@ CanWrite(user, oldMeta, newMeta *meta.Meta) bool // User is allowed to rename zettel CanRename(user, m *meta.Meta) bool - // User is allowed to delete zettel + // User is allowed to delete zettel. CanDelete(user, m *meta.Meta) bool + + // User is allowed to refresh box data. + CanRefresh(user *meta.Meta) bool } Index: auth/impl/impl.go ================================================================== --- auth/impl/impl.go +++ auth/impl/impl.go @@ -68,13 +68,10 @@ // IsReadonly returns true, if the systems is configured to run in read-only-mode. func (a *myAuth) IsReadonly() bool { return a.readonly } const reqHash = jwt.HS512 -// ErrNoUser signals that the meta data has no role value 'user'. -var ErrNoUser = errors.New("auth: meta is no user") - // ErrNoIdent signals that the 'ident' key is missing. var ErrNoIdent = errors.New("auth: missing ident") // ErrOtherKind signals that the token was defined for another token kind. var ErrOtherKind = errors.New("auth: wrong token kind") @@ -82,13 +79,10 @@ // ErrNoZid signals that the 'zid' key is missing. var ErrNoZid = errors.New("auth: missing zettel id") // GetToken returns a token to be used for authentification. func (a *myAuth) GetToken(ident *meta.Meta, d time.Duration, kind auth.TokenKind) ([]byte, error) { - if role, ok := ident.Get(api.KeyRole); !ok || role != api.ValueRoleUser { - return nil, ErrNoUser - } subject, ok := ident.Get(api.KeyUserID) if !ok || subject == "" { return nil, ErrNoIdent } Index: auth/policy/anon.go ================================================================== --- auth/policy/anon.go +++ auth/policy/anon.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package policy provides some interfaces and implementation for authorization policies. package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" @@ -39,12 +38,19 @@ } func (ap *anonPolicy) CanDelete(user, m *meta.Meta) bool { return ap.pre.CanDelete(user, m) && ap.checkVisibility(m) } + +func (ap *anonPolicy) CanRefresh(user *meta.Meta) bool { + if ap.authConfig.GetExpertMode() || ap.authConfig.GetSimpleMode() { + return true + } + return ap.pre.CanRefresh(user) +} func (ap *anonPolicy) checkVisibility(m *meta.Meta) bool { if ap.authConfig.GetVisibility(m) == meta.VisibilityExpert { return ap.authConfig.GetExpertMode() } return true } Index: auth/policy/box.go ================================================================== --- auth/policy/box.go +++ auth/policy/box.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "context" @@ -160,5 +159,13 @@ if pp.policy.CanDelete(user, meta) { return pp.box.DeleteZettel(ctx, zid) } return box.NewErrNotAllowed("Delete", user, zid) } + +func (pp *polBox) Refresh(ctx context.Context) error { + user := pp.auth.GetUser(ctx) + if pp.policy.CanRefresh(user) { + return pp.box.Refresh(ctx) + } + return box.NewErrNotAllowed("Refresh", user, id.Invalid) +} Index: auth/policy/default.go ================================================================== --- auth/policy/default.go +++ auth/policy/default.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "zettelstore.de/c/api" "zettelstore.de/z/auth" @@ -26,10 +25,12 @@ func (d *defaultPolicy) CanWrite(user, oldMeta, _ *meta.Meta) bool { return d.canChange(user, oldMeta) } func (d *defaultPolicy) CanRename(user, m *meta.Meta) bool { return d.canChange(user, m) } func (d *defaultPolicy) CanDelete(user, m *meta.Meta) bool { return d.canChange(user, m) } + +func (*defaultPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } func (d *defaultPolicy) canChange(user, m *meta.Meta) bool { metaRo, ok := m.Get(api.KeyReadOnly) if !ok { return true Index: auth/policy/owner.go ================================================================== --- auth/policy/owner.go +++ auth/policy/owner.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "zettelstore.de/c/api" "zettelstore.de/z/auth" @@ -33,11 +32,11 @@ func (o *ownerPolicy) userCanCreate(user, newMeta *meta.Meta) bool { if o.manager.GetUserRole(user) == meta.UserRoleReader { return false } - if role, ok := newMeta.Get(api.KeyRole); ok && role == api.ValueRoleUser { + if _, ok := newMeta.Get(api.KeyUserID); ok { return false } return true } @@ -59,11 +58,11 @@ return true } if user == nil { return false } - if role, ok := m.Get(api.KeyRole); ok && role == api.ValueRoleUser { + if _, ok := m.Get(api.KeyUserID); ok { // Only the user can read its own zettel return user.Zid == m.Zid } switch o.manager.GetUserRole(user) { case meta.UserRoleReader, meta.UserRoleWriter, meta.UserRoleOwner: @@ -94,11 +93,11 @@ return true } if !o.userCanRead(user, oldMeta, vis) { return false } - if role, ok := oldMeta.Get(api.KeyRole); ok && role == api.ValueRoleUser { + if _, ok := oldMeta.Get(api.KeyUserID); ok { // Here we know, that user.Zid == newMeta.Zid (because of userCanRead) and // user.Zid == newMeta.Zid (because oldMeta.Zid == newMeta.Zid) for _, key := range noChangeUser { if oldMeta.GetDefault(key, "") != newMeta.GetDefault(key, "") { return false @@ -130,10 +129,20 @@ if res, ok := o.checkVisibility(user, o.authConfig.GetVisibility(m)); ok { return res } return o.userIsOwner(user) } + +func (o *ownerPolicy) CanRefresh(user *meta.Meta) bool { + switch userRole := o.manager.GetUserRole(user); userRole { + case meta.UserRoleUnknown: + return o.authConfig.GetSimpleMode() + case meta.UserRoleCreator: + return o.authConfig.GetExpertMode() || o.authConfig.GetSimpleMode() + } + return true +} func (o *ownerPolicy) checkVisibility(user *meta.Meta, vis meta.Visibility) (bool, bool) { if vis == meta.VisibilityExpert { return o.userIsOwner(user) && o.authConfig.GetExpertMode(), true } Index: auth/policy/policy.go ================================================================== --- auth/policy/policy.go +++ auth/policy/policy.go @@ -62,5 +62,9 @@ } func (p *prePolicy) CanDelete(user, m *meta.Meta) bool { return m != nil && p.post.CanDelete(user, m) } + +func (p *prePolicy) CanRefresh(user *meta.Meta) bool { + return p.post.CanRefresh(user) +} Index: auth/policy/policy_test.go ================================================================== --- auth/policy/policy_test.go +++ auth/policy/policy_test.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "fmt" "testing" @@ -25,34 +24,43 @@ t.Parallel() testScene := []struct { readonly bool withAuth bool expert bool + simple bool }{ - {true, true, true}, - {true, true, false}, - {true, false, true}, - {true, false, false}, - {false, true, true}, - {false, true, false}, - {false, false, true}, - {false, false, false}, + {true, true, true, true}, + {true, true, true, false}, + {true, true, false, true}, + {true, true, false, false}, + {true, false, true, true}, + {true, false, true, false}, + {true, false, false, true}, + {true, false, false, false}, + {false, true, true, true}, + {false, true, true, false}, + {false, true, false, true}, + {false, true, false, false}, + {false, false, true, true}, + {false, false, true, false}, + {false, false, false, true}, + {false, false, false, false}, } for _, ts := range testScene { - authzManager := &testAuthzManager{ - readOnly: ts.readonly, - withAuth: ts.withAuth, - } - pol := newPolicy(authzManager, &authConfig{ts.expert}) - name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v", - ts.readonly, ts.withAuth, ts.expert) + pol := newPolicy( + &testAuthzManager{readOnly: ts.readonly, withAuth: ts.withAuth}, + &authConfig{simple: ts.simple, expert: ts.expert}, + ) + name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v/simple=%v", + ts.readonly, ts.withAuth, ts.expert, ts.simple) t.Run(name, func(tt *testing.T) { testCreate(tt, pol, ts.withAuth, ts.readonly) testRead(tt, pol, ts.withAuth, ts.expert) testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert) testRename(tt, pol, ts.withAuth, ts.readonly, ts.expert) testDelete(tt, pol, ts.withAuth, ts.readonly, ts.expert) + testRefresh(tt, pol, ts.withAuth, ts.expert, ts.simple) }) } } type testAuthzManager struct { @@ -82,12 +90,13 @@ } } return meta.UserRoleReader } -type authConfig struct{ expert bool } +type authConfig struct{ simple, expert bool } +func (ac *authConfig) GetSimpleMode() bool { return ac.simple } func (ac *authConfig) GetExpertMode() bool { return ac.expert } func (*authConfig) GetVisibility(m *meta.Meta) meta.Visibility { if vis, ok := m.Get(api.KeyVisibility); ok { return meta.GetVisibility(vis) @@ -562,10 +571,33 @@ tt.Errorf("exp=%v, but got=%v", tc.exp, got) } }) } } + +func testRefresh(t *testing.T, pol auth.Policy, withAuth, expert, simple bool) { + t.Helper() + testCases := []struct { + user *meta.Meta + exp bool + }{ + {newAnon(), (!withAuth && expert) || simple}, + {newCreator(), !withAuth || expert || simple}, + {newReader(), true}, + {newWriter(), true}, + {newOwner(), true}, + {newOwner2(), true}, + } + for _, tc := range testCases { + t.Run("Refresh", func(tt *testing.T) { + got := pol.CanRefresh(tc.user) + if tc.exp != got { + tt.Errorf("exp=%v, but got=%v", tc.exp, got) + } + }) + } +} const ( creatorZid = id.Zid(1013) readerZid = id.Zid(1013) writerZid = id.Zid(1015) @@ -578,39 +610,39 @@ func newAnon() *meta.Meta { return nil } func newCreator() *meta.Meta { user := meta.New(creatorZid) user.Set(api.KeyTitle, "Creator") - user.Set(api.KeyRole, api.ValueRoleUser) + user.Set(api.KeyUserID, "ceator") user.Set(api.KeyUserRole, api.ValueUserRoleCreator) return user } func newReader() *meta.Meta { user := meta.New(readerZid) user.Set(api.KeyTitle, "Reader") - user.Set(api.KeyRole, api.ValueRoleUser) + user.Set(api.KeyUserID, "reader") user.Set(api.KeyUserRole, api.ValueUserRoleReader) return user } func newWriter() *meta.Meta { user := meta.New(writerZid) user.Set(api.KeyTitle, "Writer") - user.Set(api.KeyRole, api.ValueRoleUser) + user.Set(api.KeyUserID, "writer") user.Set(api.KeyUserRole, api.ValueUserRoleWriter) return user } func newOwner() *meta.Meta { user := meta.New(ownerZid) user.Set(api.KeyTitle, "Owner") - user.Set(api.KeyRole, api.ValueRoleUser) + user.Set(api.KeyUserID, "owner") user.Set(api.KeyUserRole, api.ValueUserRoleOwner) return user } func newOwner2() *meta.Meta { user := meta.New(owner2Zid) user.Set(api.KeyTitle, "Owner 2") - user.Set(api.KeyRole, api.ValueRoleUser) + user.Set(api.KeyUserID, "owner-2") user.Set(api.KeyUserRole, api.ValueUserRoleOwner) return user } func newZettel() *meta.Meta { m := meta.New(zettelZid) @@ -678,8 +710,8 @@ return m } func newUserZettel() *meta.Meta { m := meta.New(userZid) m.Set(api.KeyTitle, "Any User") - m.Set(api.KeyRole, api.ValueRoleUser) + m.Set(api.KeyUserID, "any") return m } Index: auth/policy/readonly.go ================================================================== --- auth/policy/readonly.go +++ auth/policy/readonly.go @@ -6,17 +6,17 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package policy provides some interfaces and implementation for authorization policies. package policy import "zettelstore.de/z/domain/meta" type roPolicy struct{} -func (p *roPolicy) CanCreate(user, newMeta *meta.Meta) bool { return false } -func (p *roPolicy) CanRead(user, m *meta.Meta) bool { return true } -func (p *roPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return false } -func (p *roPolicy) CanRename(user, m *meta.Meta) bool { return false } -func (p *roPolicy) CanDelete(user, m *meta.Meta) bool { return false } +func (*roPolicy) CanCreate(_, _ *meta.Meta) bool { return false } +func (*roPolicy) CanRead(_, _ *meta.Meta) bool { return true } +func (*roPolicy) CanWrite(_, _, _ *meta.Meta) bool { return false } +func (*roPolicy) CanRename(_, _ *meta.Meta) bool { return false } +func (*roPolicy) CanDelete(_, _ *meta.Meta) bool { return false } +func (*roPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } Index: box/box.go ================================================================== --- box/box.go +++ box/box.go @@ -71,15 +71,15 @@ // ManagedBox is the interface of managed boxes. type ManagedBox interface { BaseBox - // Apply identifier of every zettel to the given function. - ApplyZid(context.Context, ZidFunc) error + // Apply identifier of every zettel to the given function, if predicate returns true. + ApplyZid(context.Context, ZidFunc, search.RetrievePredicate) error - // Apply metadata of every zettel to the given function. - ApplyMeta(context.Context, MetaFunc) error + // Apply metadata of every zettel to the given function, if predicate returns true. + ApplyMeta(context.Context, MetaFunc, search.RetrievePredicate) error // ReadStats populates st with box statistics ReadStats(st *ManagedBoxStats) } @@ -97,11 +97,17 @@ // Start the box. Now all other functions of the box are allowed. // Starting an already started box is not allowed. Start(ctx context.Context) error // Stop the started box. Now only the Start() function is allowed. - Stop(ctx context.Context) error + Stop(ctx context.Context) +} + +// Refresher allow to refresh their internal data. +type Refresher interface { + // Refresh the box data. + Refresh(context.Context) } // Box is to be used outside the box package and its descendants. type Box interface { BaseBox @@ -115,10 +121,13 @@ // GetAllZettel retrieves a specific zettel from all managed boxes. GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) // GetAllMeta retrieves the meta data of a specific zettel from all managed boxes. GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) + + // Refresh the data from the box and from its managed sub-boxes. + Refresh(context.Context) error } // Stats record stattistics about a box. type Stats struct { // ReadOnly indicates that boxes cannot be modified. Index: box/compbox/compbox.go ================================================================== --- box/compbox/compbox.go +++ box/compbox/compbox.go @@ -19,10 +19,13 @@ "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" + "zettelstore.de/z/kernel" + "zettelstore.de/z/logger" + "zettelstore.de/z/search" ) func init() { manager.Register( " comp", @@ -30,10 +33,11 @@ return getCompBox(cdata.Number, cdata.Enricher), nil }) } type compBox struct { + log *logger.Logger number int enricher box.Enricher } var myConfig *meta.Meta @@ -42,118 +46,149 @@ content func(*meta.Meta) []byte }{ id.MustParse(api.ZidVersion): {genVersionBuildM, genVersionBuildC}, id.MustParse(api.ZidHost): {genVersionHostM, genVersionHostC}, id.MustParse(api.ZidOperatingSystem): {genVersionOSM, genVersionOSC}, + id.MustParse(api.ZidLog): {genLogM, genLogC}, id.MustParse(api.ZidBoxManager): {genManagerM, genManagerC}, id.MustParse(api.ZidMetadataKey): {genKeysM, genKeysC}, + id.MustParse(api.ZidParser): {genParserM, genParserC}, id.MustParse(api.ZidStartupConfiguration): {genConfigZettelM, genConfigZettelC}, } // Get returns the one program box. func getCompBox(boxNumber int, mf box.Enricher) box.ManagedBox { - return &compBox{number: boxNumber, enricher: mf} + return &compBox{ + log: kernel.Main.GetLogger(kernel.BoxService).Clone(). + Str("box", "comp").Int("boxnum", int64(boxNumber)).Child(), + number: boxNumber, + enricher: mf, + } } // Setup remembers important values. func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() } func (*compBox) Location() string { return "" } func (*compBox) CanCreateZettel(context.Context) bool { return false } -func (*compBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { +func (cb *compBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { + cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel") return id.Invalid, box.ErrReadOnly } -func (*compBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { +func (cb *compBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { if gen, ok := myZettel[zid]; ok && gen.meta != nil { if m := gen.meta(zid); m != nil { updateMeta(m) if genContent := gen.content; genContent != nil { + cb.log.Trace().Msg("GetMeta/Content") return domain.Zettel{ Meta: m, Content: domain.NewContent(genContent(m)), }, nil } + cb.log.Trace().Msg("GetMeta/NoContent") return domain.Zettel{Meta: m}, nil } } + cb.log.Trace().Err(box.ErrNotFound).Msg("GetZettel/Err") return domain.Zettel{}, box.ErrNotFound } -func (*compBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { +func (cb *compBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { if gen, ok := myZettel[zid]; ok { if genMeta := gen.meta; genMeta != nil { if m := genMeta(zid); m != nil { updateMeta(m) + cb.log.Trace().Msg("GetMeta") return m, nil } } } + cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta/Err") return nil, box.ErrNotFound } -func (*compBox) ApplyZid(_ context.Context, handle box.ZidFunc) error { +func (cb *compBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.RetrievePredicate) error { + cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyMeta") for zid, gen := range myZettel { + if !constraint(zid) { + continue + } if genMeta := gen.meta; genMeta != nil { if genMeta(zid) != nil { handle(zid) } } } return nil } -func (pp *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error { +func (cb *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error { + cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyMeta") for zid, gen := range myZettel { + if !constraint(zid) { + continue + } if genMeta := gen.meta; genMeta != nil { if m := genMeta(zid); m != nil { updateMeta(m) - pp.enricher.Enrich(ctx, m, pp.number) + cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } } return nil } func (*compBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } -func (*compBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly } +func (cb *compBox) UpdateZettel(context.Context, domain.Zettel) error { + cb.log.Trace().Err(box.ErrReadOnly).Msg("UpdateZettel") + return box.ErrReadOnly +} func (*compBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := myZettel[zid] return !ok } -func (*compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { +func (cb *compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { + err := box.ErrNotFound if _, ok := myZettel[curZid]; ok { - return box.ErrReadOnly + err = box.ErrReadOnly } - return box.ErrNotFound + cb.log.Trace().Err(err).Msg("RenameZettel") + return err } func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } -func (*compBox) DeleteZettel(_ context.Context, zid id.Zid) error { +func (cb *compBox) DeleteZettel(_ context.Context, zid id.Zid) error { + err := box.ErrNotFound if _, ok := myZettel[zid]; ok { - return box.ErrReadOnly + err = box.ErrReadOnly } - return box.ErrNotFound + cb.log.Trace().Err(err).Msg("DeleteZettel") + return err } -func (*compBox) ReadStats(st *box.ManagedBoxStats) { +func (cb *compBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true st.Zettel = len(myZettel) + cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } func updateMeta(m *meta.Meta) { m.Set(api.KeyNoIndex, api.ValueTrue) - m.Set(api.KeySyntax, api.ValueSyntaxZmk) + if _, ok := m.Get(api.KeySyntax); !ok { + m.Set(api.KeySyntax, api.ValueSyntaxZmk) + } m.Set(api.KeyRole, api.ValueRoleConfiguration) m.Set(api.KeyLang, api.ValueLangEN) m.Set(api.KeyReadOnly, api.ValueTrue) if _, ok := m.Get(api.KeyVisibility); !ok { m.Set(api.KeyVisibility, api.ValueVisibilityExpert) } } Index: box/compbox/config.go ================================================================== --- box/compbox/config.go +++ box/compbox/config.go @@ -1,16 +1,15 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020-2021 Detlef Stern +// Copyright (c) 2020-2022 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package compbox provides zettel that have computed content. package compbox import ( "bytes" @@ -29,11 +28,11 @@ return m } func genConfigZettelC(*meta.Meta) []byte { var buf bytes.Buffer - for i, p := range myConfig.Pairs(false) { + for i, p := range myConfig.Pairs() { if i > 0 { buf.WriteByte('\n') } buf.WriteString("; ''") buf.WriteString(p.Key) Index: box/compbox/keys.go ================================================================== --- box/compbox/keys.go +++ box/compbox/keys.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package compbox provides zettel that have computed content. package compbox import ( "bytes" "fmt" ADDED box/compbox/log.go Index: box/compbox/log.go ================================================================== --- /dev/null +++ box/compbox/log.go @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2021 Detlef Stern +// +// This file is part of zettelstore. +// +// Zettelstore is licensed under the latest version of the EUPL (European Union +// Public License). Please see file LICENSE.txt for your rights and obligations +// under this license. +//----------------------------------------------------------------------------- + +package compbox + +import ( + "bytes" + + "zettelstore.de/c/api" + "zettelstore.de/z/domain/id" + "zettelstore.de/z/domain/meta" + "zettelstore.de/z/kernel" +) + +func genLogM(zid id.Zid) *meta.Meta { + m := meta.New(zid) + m.Set(api.KeyTitle, "Zettelstore Log") + m.Set(api.KeySyntax, api.ValueSyntaxText) + return m +} + +func genLogC(*meta.Meta) []byte { + const tsFormat = "2006-01-02 15:04:05.999999" + entries := kernel.Main.RetrieveLogEntries() + var buf bytes.Buffer + for _, entry := range entries { + ts := entry.TS.Format(tsFormat) + buf.WriteString(ts) + for j := len(ts); j < len(tsFormat); j++ { + buf.WriteByte('0') + } + buf.WriteByte(' ') + buf.WriteString(entry.Level.Format()) + buf.WriteByte(' ') + buf.WriteString(entry.Prefix) + buf.WriteByte(' ') + buf.WriteString(entry.Message) + buf.WriteByte('\n') + } + return buf.Bytes() +} Index: box/compbox/manager.go ================================================================== --- box/compbox/manager.go +++ box/compbox/manager.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package compbox provides zettel that have computed content. package compbox import ( "bytes" "fmt" ADDED box/compbox/parser.go Index: box/compbox/parser.go ================================================================== --- /dev/null +++ box/compbox/parser.go @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2021 Detlef Stern +// +// This file is part of zettelstore. +// +// Zettelstore is licensed under the latest version of the EUPL (European Union +// Public License). Please see file LICENSE.txt for your rights and obligations +// under this license. +//----------------------------------------------------------------------------- + +package compbox + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "zettelstore.de/c/api" + "zettelstore.de/z/domain/id" + "zettelstore.de/z/domain/meta" + "zettelstore.de/z/parser" +) + +func genParserM(zid id.Zid) *meta.Meta { + m := meta.New(zid) + m.Set(api.KeyTitle, "Zettelstore Supported Parser") + m.Set(api.KeyVisibility, api.ValueVisibilityLogin) + return m +} + +func genParserC(*meta.Meta) []byte { + var buf bytes.Buffer + buf.WriteString("|=Syntax<|=Alt. Value(s):|=Text Parser?:|=Image Format?:\n") + syntaxes := parser.GetSyntaxes() + sort.Strings(syntaxes) + for _, syntax := range syntaxes { + info := parser.Get(syntax) + if info.Name != syntax { + continue + } + altNames := info.AltNames + sort.Strings(altNames) + fmt.Fprintf( + &buf, "|%v|%v|%v|%v\n", + syntax, strings.Join(altNames, ", "), info.IsTextParser, info.IsImageFormat) + } + return buf.Bytes() +} Index: box/compbox/version.go ================================================================== --- box/compbox/version.go +++ box/compbox/version.go @@ -6,11 +6,10 @@ // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- -// Package compbox provides zettel that have computed content. package compbox import ( "zettelstore.de/c/api" "zettelstore.de/z/domain/id" @@ -25,11 +24,11 @@ return m } func genVersionBuildM(zid id.Zid) *meta.Meta { m := getVersionMeta(zid, "Zettelstore Version") - m.Set(api.KeyVisibility, api.ValueVisibilityPublic) + m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genVersionBuildC(*meta.Meta) []byte { return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) } Index: box/constbox/base.mustache ================================================================== --- box/constbox/base.mustache +++ box/constbox/base.mustache @@ -32,10 +32,13 @@ {{#HasNewZettelLinks}}
Index: box/constbox/constbox.go ================================================================== --- box/constbox/constbox.go +++ box/constbox/constbox.go @@ -20,17 +20,22 @@ "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" + "zettelstore.de/z/kernel" + "zettelstore.de/z/logger" + "zettelstore.de/z/search" ) func init() { manager.Register( " const", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &constBox{ + log: kernel.Main.GetLogger(kernel.BoxService).Clone(). + Str("box", "const").Int("boxnum", int64(cdata.Number)).Child(), number: cdata.Number, zettel: constZettelMap, enricher: cdata.Enricher, }, nil }) @@ -42,80 +47,101 @@ header constHeader content domain.Content } type constBox struct { + log *logger.Logger number int zettel map[id.Zid]constZettel enricher box.Enricher } func (*constBox) Location() string { return "const:" } func (*constBox) CanCreateZettel(context.Context) bool { return false } -func (*constBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { +func (cb *constBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { + cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel") return id.Invalid, box.ErrReadOnly } -func (cp *constBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { - if z, ok := cp.zettel[zid]; ok { +func (cb *constBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { + if z, ok := cb.zettel[zid]; ok { + cb.log.Trace().Msg("GetZettel") return domain.Zettel{Meta: meta.NewWithData(zid, z.header), Content: z.content}, nil } + cb.log.Trace().Err(box.ErrNotFound).Msg("GetZettel") return domain.Zettel{}, box.ErrNotFound } -func (cp *constBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { - if z, ok := cp.zettel[zid]; ok { +func (cb *constBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { + if z, ok := cb.zettel[zid]; ok { + cb.log.Trace().Msg("GetMeta") return meta.NewWithData(zid, z.header), nil } + cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta") return nil, box.ErrNotFound } -func (cp *constBox) ApplyZid(_ context.Context, handle box.ZidFunc) error { - for zid := range cp.zettel { - handle(zid) +func (cb *constBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.RetrievePredicate) error { + cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyZid") + for zid := range cb.zettel { + if constraint(zid) { + handle(zid) + } } return nil } -func (cp *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error { - for zid, zettel := range cp.zettel { - m := meta.NewWithData(zid, zettel.header) - cp.enricher.Enrich(ctx, m, cp.number) - handle(m) +func (cb *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error { + cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyMeta") + for zid, zettel := range cb.zettel { + if constraint(zid) { + m := meta.NewWithData(zid, zettel.header) + cb.enricher.Enrich(ctx, m, cb.number) + handle(m) + } } return nil } func (*constBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } -func (*constBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly } +func (cb *constBox) UpdateZettel(context.Context, domain.Zettel) error { + cb.log.Trace().Err(box.ErrReadOnly).Msg("UpdateZettel") + return box.ErrReadOnly +} -func (cp *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { - _, ok := cp.zettel[zid] +func (cb *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { + _, ok := cb.zettel[zid] return !ok } -func (cp *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { - if _, ok := cp.zettel[curZid]; ok { - return box.ErrReadOnly +func (cb *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { + err := box.ErrNotFound + if _, ok := cb.zettel[curZid]; ok { + err = box.ErrReadOnly } - return box.ErrNotFound + cb.log.Trace().Err(err).Msg("RenameZettel") + return err } + func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } -func (cp *constBox) DeleteZettel(_ context.Context, zid id.Zid) error { - if _, ok := cp.zettel[zid]; ok { - return box.ErrReadOnly +func (cb *constBox) DeleteZettel(_ context.Context, zid id.Zid) error { + err := box.ErrNotFound + if _, ok := cb.zettel[zid]; ok { + err = box.ErrReadOnly } - return box.ErrNotFound + cb.log.Trace().Err(err).Msg("DeleteZettel") + return err } -func (cp *constBox) ReadStats(st *box.ManagedBoxStats) { +func (cb *constBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true - st.Zettel = len(cp.zettel) + st.Zettel = len(cb.zettel) + cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } const syntaxTemplate = "mustache" var constZettelMap = map[id.Zid]constZettel{ @@ -143,21 +169,21 @@ api.KeyTitle: "Zettelstore Contributors", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, - api.KeyVisibility: api.ValueVisibilityPublic, + api.KeyVisibility: api.ValueVisibilityLogin, }, domain.NewContent(contentContributors)}, id.MustParse(api.ZidDependencies): { constHeader{ api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, - api.KeyVisibility: api.ValueVisibilityPublic, + api.KeyVisibility: api.ValueVisibilityLogin, }, domain.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", @@ -310,11 +336,11 @@ }, domain.NewContent(nil)}, id.MustParse(api.ZidTemplateNewUser): { constHeader{ api.KeyTitle: "New User", - api.KeyRole: api.ValueRoleUser, + api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxNone, meta.NewPrefix + api.KeyCredential: "", meta.NewPrefix + api.KeyUserID: "", meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader, api.KeyVisibility: api.ValueVisibilityOwner, Index: box/constbox/delete.mustache ================================================================== --- box/constbox/delete.mustache +++ box/constbox/delete.mustache @@ -18,10 +18,21 @@
  • {{Text}}
  • {{/Incoming}}
    {{/HasIncoming}} +{{#HasUselessFiles}} +
    +

    Warning!

    +

    Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.

    + +
    +{{/HasUselessFiles}}
    {{#MetaPairs}}
    {{Key}}:
    {{Value}}
    {{/MetaPairs}}
    Index: box/constbox/dependencies.zettel ================================================================== --- box/constbox/dependencies.zettel +++ box/constbox/dependencies.zettel @@ -1,9 +1,9 @@ Zettelstore is made with the help of other software and other artifacts. Thank you very much! -This zettel lists all of them, together with their license. +This zettel lists all of them, together with their licenses. === Go runtime and associated libraries ; License : BSD 3-Clause "New" or "Revised" License ``` Index: box/constbox/info.mustache ================================================================== --- box/constbox/info.mustache +++ box/constbox/info.mustache @@ -9,11 +9,10 @@ {{#CanRename}}· Rename{{/CanRename}} {{#CanDelete}}· Delete{{/CanDelete}}

    Interpreted Metadata

    {{#MetaData}}{{/MetaData}}
    {{Key}}{{{Value}}}
    -{{#HasLinks}}

    References

    {{#HasLocLinks}}

    Local

    {{/HasExtLinks}} -{{/HasLinks}} +

    Unlinked

    + +
    + + +

    Parts and encodings

    {{#EvalMatrix}} Index: box/constbox/rename.mustache ================================================================== --- box/constbox/rename.mustache +++ box/constbox/rename.mustache @@ -1,8 +1,8 @@
    -

    Rename Zettel {{.Zid}}

    +

    Rename Zettel {{Zid}}

    Do you really want to rename this zettel?

    {{#HasIncoming}}

    Warning!

    @@ -12,10 +12,21 @@
  • {{Text}}
  • {{/Incoming}}
    {{/HasIncoming}} +{{#HasUselessFiles}} +
    +

    Warning!

    +

    Renaming this zettel will also delete the following files, so that they will not be interpreted as content for a zettel with identifier {{Zid}}.

    + +
    +{{/HasUselessFiles}}
    Index: box/constbox/zettel.mustache ================================================================== --- box/constbox/zettel.mustache +++ box/constbox/zettel.mustache @@ -27,12 +27,12 @@ {{/HasFolgeLinks}} {{#HasBackLinks}}
    {{Header}}