DELETED .deepsource.toml
Index: .deepsource.toml
==================================================================
--- .deepsource.toml
+++ .deepsource.toml
@@ -1,8 +0,0 @@
-version = 1
-
-[[analyzers]]
-name = "go"
-enabled = true
-
- [analyzers.meta]
-import_paths = ["github.com/zettelstore/zettelstore"]
Index: .fossil-settings/ignore-glob
==================================================================
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -1,2 +1,3 @@
bin/*
releases/*
+parser/pikchr/*.out
Index: LICENSE.txt
==================================================================
--- LICENSE.txt
+++ LICENSE.txt
@@ -1,6 +1,6 @@
-Copyright (c) 2020-2022 Detlef Stern
+Copyright (c) 2020-present 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: Makefile
==================================================================
--- Makefile
+++ Makefile
@@ -1,9 +1,9 @@
-## Copyright (c) 2020-2021 Detlef Stern
+## Copyright (c) 2020-present Detlef Stern
##
-## This file is part of zettelstore.
+## 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.
Index: README.md
==================================================================
--- README.md
+++ README.md
@@ -21,6 +21,6 @@
The software, including the manual, is licensed
under the [European Union Public License 1.2 (or
later)](https://zettelstore.de/home/file?name=LICENSE.txt&ci=trunk).
-[Stay tuned](https://twitter.com/zettelstore)…
+[Stay tuned](https://mastodon.social/tags/Zettelstore) …
Index: VERSION
==================================================================
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
-0.5.0
+0.14.0
Index: ast/ast.go
==================================================================
--- ast/ast.go
+++ ast/ast.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -12,20 +12,20 @@
package ast
import (
"net/url"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// ZettelNode is the root node of the abstract syntax tree.
// It is *not* part of the visitor pattern.
type ZettelNode struct {
Meta *meta.Meta // Original metadata
- Content domain.Content // Original content
+ Content zettel.Content // Original content
Zid id.Zid // Zettel identification.
InhMeta *meta.Meta // Metadata of the zettel, with inherited values.
Ast BlockSlice // Zettel abstract syntax tree is a sequence of block nodes.
Syntax string // Syntax / parser that produced the Ast
}
@@ -82,7 +82,8 @@
RefStateSelf // Reference to same zettel with a fragment
RefStateFound // Reference to an existing internal zettel, URL is ajusted
RefStateBroken // Reference to a non-existing internal zettel
RefStateHosted // Reference to local hosted non-Zettel, without URL change
RefStateBased // Reference to local non-Zettel, to be prefixed
+ RefStateQuery // Reference to a zettel query
RefStateExternal // Reference to external material
)
Index: ast/block.go
==================================================================
--- ast/block.go
+++ ast/block.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -8,11 +8,11 @@
// under this license.
//-----------------------------------------------------------------------------
package ast
-import "zettelstore.de/c/attrs"
+import "zettelstore.de/client.fossil/attrs"
// Definition of Block nodes.
// BlockSlice is a slice of BlockNodes.
type BlockSlice []BlockNode
@@ -88,15 +88,10 @@
func (*VerbatimNode) itemNode() { /* Just a marker */ }
// WalkChildren does nothing.
func (*VerbatimNode) WalkChildren(Visitor) { /* No children*/ }
-// Supported syntax values for VerbatimEval.
-const (
- VerbatimEvalSyntaxDraw = "draw"
-)
-
//--------------------------------------------------------------------------
// RegionNode encapsulates a region of block nodes.
type RegionNode struct {
Kind RegionKind
@@ -270,11 +265,12 @@
//--------------------------------------------------------------------------
// TranscludeNode specifies block content from other zettel to embedded in
// current zettel
type TranscludeNode struct {
- Ref *Reference
+ Attrs attrs.Attributes
+ Ref *Reference
}
func (*TranscludeNode) blockNode() { /* Just a marker */ }
// WalkChildren does nothing.
@@ -283,14 +279,14 @@
//--------------------------------------------------------------------------
// BLOBNode contains just binary data that must be interpreted according to
// a syntax.
type BLOBNode struct {
- Title string
- Syntax string
- Blob []byte
+ Description InlineSlice
+ Syntax string
+ Blob []byte
}
func (*BLOBNode) blockNode() { /* Just a marker */ }
// WalkChildren does nothing.
func (*BLOBNode) WalkChildren(Visitor) { /* No children*/ }
Index: ast/inline.go
==================================================================
--- ast/inline.go
+++ ast/inline.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -11,11 +11,11 @@
package ast
import (
"unicode/utf8"
- "zettelstore.de/c/attrs"
+ "zettelstore.de/client.fossil/attrs"
)
// Definitions of inline nodes.
// InlineSlice is a list of BlockNodes.
@@ -53,22 +53,10 @@
func (*TextNode) inlineNode() { /* Just a marker */ }
// WalkChildren does nothing.
func (*TextNode) WalkChildren(Visitor) { /* No children*/ }
-// --------------------------------------------------------------------------
-
-// TagNode contains a tag.
-type TagNode struct {
- Tag string // The text itself.
-}
-
-func (*TagNode) inlineNode() { /* Just a marker */ }
-
-// WalkChildren does nothing.
-func (*TagNode) WalkChildren(Visitor) { /* No children*/ }
-
// --------------------------------------------------------------------------
// SpaceNode tracks inter-word space characters.
type SpaceNode struct {
Lexeme string
Index: ast/ref.go
==================================================================
--- ast/ref.go
+++ ast/ref.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -10,20 +10,26 @@
package ast
import (
"net/url"
+ "strings"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/zettel/id"
)
+
+// QueryPrefix is the prefix that denotes a query expression.
+const QueryPrefix = "query:"
// ParseReference parses a string and returns a reference.
func ParseReference(s string) *Reference {
- switch s {
- case "", "00000000000000":
+ if invalidReference(s) {
return &Reference{URL: nil, Value: s, State: RefStateInvalid}
}
+ if strings.HasPrefix(s, QueryPrefix) {
+ return &Reference{URL: nil, Value: s[len(QueryPrefix):], State: RefStateQuery}
+ }
if state, ok := localState(s); ok {
if state == RefStateBased {
s = s[1:]
}
u, err := url.Parse(s)
@@ -33,20 +39,25 @@
}
u, err := url.Parse(s)
if err != nil {
return &Reference{URL: nil, Value: s, State: RefStateInvalid}
}
- if len(u.Scheme)+len(u.Opaque)+len(u.Host) == 0 && u.User == nil {
+ if !externalURL(u) {
if _, err = id.Parse(u.Path); err == nil {
return &Reference{URL: u, Value: s, State: RefStateZettel}
}
if u.Path == "" && u.Fragment != "" {
return &Reference{URL: u, Value: s, State: RefStateSelf}
}
}
return &Reference{URL: u, Value: s, State: RefStateExternal}
}
+
+func invalidReference(s string) bool { return s == "" || s == "00000000000000" }
+func externalURL(u *url.URL) bool {
+ return u.Scheme != "" || u.Opaque != "" || u.Host != "" || u.User != nil
+}
func localState(path string) (RefState, bool) {
if len(path) > 0 && path[0] == '/' {
if len(path) > 1 && path[1] == '/' {
return RefStateBased, true
@@ -64,10 +75,13 @@
// String returns the string representation of a reference.
func (r Reference) String() string {
if r.URL != nil {
return r.URL.String()
+ }
+ if r.State == RefStateQuery {
+ return QueryPrefix + r.Value
}
return r.Value
}
// IsValid returns true if reference is valid
Index: ast/ref_test.go
==================================================================
--- ast/ref_test.go
+++ ast/ref_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
Index: ast/walk.go
==================================================================
--- ast/walk.go
+++ ast/walk.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
Index: ast/walk_test.go
==================================================================
--- ast/walk_test.go
+++ ast/walk_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -11,11 +11,11 @@
package ast_test
import (
"testing"
- "zettelstore.de/c/attrs"
+ "zettelstore.de/client.fossil/attrs"
"zettelstore.de/z/ast"
)
func BenchmarkWalk(b *testing.B) {
root := ast.BlockSlice{
Index: auth/auth.go
==================================================================
--- auth/auth.go
+++ auth/auth.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -14,13 +14,12 @@
import (
"time"
"zettelstore.de/z/box"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
- "zettelstore.de/z/web/server"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// BaseManager allows to check some base auth modes.
type BaseManager interface {
// IsReadonly returns true, if the systems is configured to run in read-only-mode.
@@ -41,12 +40,12 @@
type TokenKind int
// Allowed values of token kind
const (
_ TokenKind = iota
- KindJSON
- KindHTML
+ KindAPI
+ KindwebUI
)
// TokenData contains some important elements from a token.
type TokenData struct {
Token []byte
@@ -77,11 +76,11 @@
// Manager is the main interface for providing the service.
type Manager interface {
TokenManager
AuthzManager
- BoxWithPolicy(auth server.Auth, unprotectedBox box.Box, rtConfig config.Config) (box.Box, Policy)
+ BoxWithPolicy(unprotectedBox box.Box, rtConfig config.Config) (box.Box, Policy)
}
// Policy is an interface for checking access authorization.
type Policy interface {
// User is allowed to create a new zettel.
Index: auth/cred/cred.go
==================================================================
--- auth/cred/cred.go
+++ auth/cred/cred.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -13,11 +13,11 @@
import (
"bytes"
"golang.org/x/crypto/bcrypt"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/zettel/id"
)
// HashCredential returns a hashed vesion of the given credential
func HashCredential(zid id.Zid, ident, credential string) (string, error) {
fullCredential := createFullCredential(zid, ident, credential)
@@ -42,12 +42,12 @@
return false, err
}
func createFullCredential(zid id.Zid, ident, credential string) []byte {
var buf bytes.Buffer
- buf.WriteString(zid.String())
+ buf.Write(zid.Bytes())
buf.WriteByte(' ')
buf.WriteString(ident)
buf.WriteByte(' ')
buf.WriteString(credential)
return buf.Bytes()
}
ADDED auth/impl/digest.go
Index: auth/impl/digest.go
==================================================================
--- auth/impl/digest.go
+++ auth/impl/digest.go
@@ -0,0 +1,83 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2023-present 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 impl
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/hmac"
+ "encoding/base64"
+
+ "zettelstore.de/sx.fossil"
+ "zettelstore.de/sx.fossil/sxreader"
+)
+
+var encoding = base64.RawURLEncoding
+
+const digestAlg = crypto.SHA384
+
+func sign(claim sx.Object, secret []byte) ([]byte, error) {
+ var buf bytes.Buffer
+ sx.Print(&buf, claim)
+ token := make([]byte, encoding.EncodedLen(buf.Len()))
+ encoding.Encode(token, buf.Bytes())
+
+ digest := hmac.New(digestAlg.New, secret)
+ _, err := digest.Write(buf.Bytes())
+ if err != nil {
+ return nil, err
+ }
+ dig := digest.Sum(nil)
+ encDig := make([]byte, encoding.EncodedLen(len(dig)))
+ encoding.Encode(encDig, dig)
+
+ token = append(token, '.')
+ token = append(token, encDig...)
+ return token, nil
+}
+
+func check(token []byte, secret []byte) (sx.Object, error) {
+ i := bytes.IndexByte(token, '.')
+ if i <= 0 || 1024 < i {
+ return nil, ErrMalformedToken
+ }
+ buf := make([]byte, len(token))
+ n, err := encoding.Decode(buf, token[:i])
+ if err != nil {
+ return nil, err
+ }
+ rdr := sxreader.MakeReader(bytes.NewReader(buf[:n]))
+ obj, err := rdr.Read()
+ if err != nil {
+ return nil, err
+ }
+
+ var objBuf bytes.Buffer
+ _, err = sx.Print(&objBuf, obj)
+ if err != nil {
+ return nil, err
+ }
+
+ digest := hmac.New(digestAlg.New, secret)
+ _, err = digest.Write(objBuf.Bytes())
+ if err != nil {
+ return nil, err
+ }
+
+ n, err = encoding.Decode(buf, token[i+1:])
+ if err != nil {
+ return nil, err
+ }
+ if !hmac.Equal(buf[:n], digest.Sum(nil)) {
+ return nil, ErrMalformedToken
+ }
+ return obj, nil
+}
Index: auth/impl/impl.go
==================================================================
--- auth/impl/impl.go
+++ auth/impl/impl.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -15,21 +15,20 @@
"errors"
"hash/fnv"
"io"
"time"
- "github.com/pascaldekloe/jwt"
-
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
+ "zettelstore.de/client.fossil/sexp"
+ "zettelstore.de/sx.fossil"
"zettelstore.de/z/auth"
"zettelstore.de/z/auth/policy"
"zettelstore.de/z/box"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/kernel"
- "zettelstore.de/z/web/server"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
type myAuth struct {
readonly bool
owner id.Zid
@@ -66,11 +65,12 @@
}
// 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
+// ErrMalformedToken signals a broken token.
+var ErrMalformedToken = errors.New("auth: malformed token")
// 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.
@@ -85,68 +85,66 @@
if !ok || subject == "" {
return nil, ErrNoIdent
}
now := time.Now().Round(time.Second)
- claims := jwt.Claims{
- Registered: jwt.Registered{
- Subject: subject,
- Expires: jwt.NewNumericTime(now.Add(d)),
- Issued: jwt.NewNumericTime(now),
- },
- Set: map[string]interface{}{
- "zid": ident.Zid.String(),
- "_tk": int(kind),
- },
- }
- token, err := claims.HMACSign(reqHash, a.secret)
- if err != nil {
- return nil, err
- }
- return token, nil
+ sClaim := sx.MakeList(
+ sx.Int64(kind),
+ sx.String(subject),
+ sx.Int64(now.Unix()),
+ sx.Int64(now.Add(d).Unix()),
+ sx.Int64(ident.Zid),
+ )
+ return sign(sClaim, a.secret)
}
// ErrTokenExpired signals an exired token
var ErrTokenExpired = errors.New("auth: token expired")
// CheckToken checks the validity of the token and returns relevant data.
-func (a *myAuth) CheckToken(token []byte, k auth.TokenKind) (auth.TokenData, error) {
- h, err := jwt.NewHMAC(reqHash, a.secret)
- if err != nil {
- return auth.TokenData{}, err
- }
- claims, err := h.Check(token)
- if err != nil {
- return auth.TokenData{}, err
- }
- now := time.Now().Round(time.Second)
- expires := claims.Expires.Time()
- if expires.Before(now) {
- return auth.TokenData{}, ErrTokenExpired
- }
- ident := claims.Subject
+func (a *myAuth) CheckToken(tok []byte, k auth.TokenKind) (auth.TokenData, error) {
+ var tokenData auth.TokenData
+
+ obj, err := check(tok, a.secret)
+ if err != nil {
+ return tokenData, err
+ }
+
+ tokenData.Token = tok
+ err = setupTokenData(obj, k, &tokenData)
+ return tokenData, err
+}
+
+func setupTokenData(obj sx.Object, k auth.TokenKind, tokenData *auth.TokenData) error {
+ vals, err := sexp.ParseList(obj, "isiii")
+ if err != nil {
+ return ErrMalformedToken
+ }
+ if auth.TokenKind(vals[0].(sx.Int64)) != k {
+ return ErrOtherKind
+ }
+ ident := vals[1].(sx.String)
if ident == "" {
- return auth.TokenData{}, ErrNoIdent
- }
- if zidS, ok := claims.Set["zid"].(string); ok {
- if zid, err2 := id.Parse(zidS); err2 == nil {
- if kind, ok2 := claims.Set["_tk"].(float64); ok2 {
- if auth.TokenKind(kind) == k {
- return auth.TokenData{
- Token: token,
- Now: now,
- Issued: claims.Issued.Time(),
- Expires: expires,
- Ident: ident,
- Zid: zid,
- }, nil
- }
- }
- return auth.TokenData{}, ErrOtherKind
- }
- }
- return auth.TokenData{}, ErrNoZid
+ return ErrNoIdent
+ }
+ issued := time.Unix(int64(vals[2].(sx.Int64)), 0)
+ expires := time.Unix(int64(vals[3].(sx.Int64)), 0)
+ now := time.Now().Round(time.Second)
+ if expires.Before(now) {
+ return ErrTokenExpired
+ }
+ zid := id.Zid(vals[4].(sx.Int64))
+ if !zid.IsValid() {
+ return ErrNoZid
+ }
+
+ tokenData.Ident = ident.String()
+ tokenData.Issued = issued
+ tokenData.Now = now
+ tokenData.Expires = expires
+ tokenData.Zid = zid
+ return nil
}
func (a *myAuth) Owner() id.Zid { return a.owner }
func (a *myAuth) IsOwner(zid id.Zid) bool {
@@ -172,8 +170,8 @@
}
}
return meta.UserRoleReader
}
-func (a *myAuth) BoxWithPolicy(auth server.Auth, unprotectedBox box.Box, rtConfig config.Config) (box.Box, auth.Policy) {
- return policy.BoxWithPolicy(auth, a, unprotectedBox, rtConfig)
+func (a *myAuth) BoxWithPolicy(unprotectedBox box.Box, rtConfig config.Config) (box.Box, auth.Policy) {
+ return policy.BoxWithPolicy(a, unprotectedBox, rtConfig)
}
Index: auth/policy/anon.go
==================================================================
--- auth/policy/anon.go
+++ auth/policy/anon.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -11,11 +11,11 @@
package policy
import (
"zettelstore.de/z/auth"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/meta"
)
type anonPolicy struct {
authConfig config.AuthConfig
pre auth.Policy
Index: auth/policy/box.go
==================================================================
--- auth/policy/box.go
+++ auth/policy/box.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -14,39 +14,32 @@
"context"
"zettelstore.de/z/auth"
"zettelstore.de/z/box"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
- "zettelstore.de/z/search"
+ "zettelstore.de/z/query"
"zettelstore.de/z/web/server"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// BoxWithPolicy wraps the given box inside a policy box.
-func BoxWithPolicy(
- auth server.Auth,
- manager auth.AuthzManager,
- box box.Box,
- authConfig config.AuthConfig,
-) (box.Box, auth.Policy) {
+func BoxWithPolicy(manager auth.AuthzManager, box box.Box, authConfig config.AuthConfig) (box.Box, auth.Policy) {
pol := newPolicy(manager, authConfig)
- return newBox(auth, box, pol), pol
+ return newBox(box, pol), pol
}
// polBox implements a policy box.
type polBox struct {
- auth server.Auth
box box.Box
policy auth.Policy
}
// newBox creates a new policy box.
-func newBox(auth server.Auth, box box.Box, policy auth.Policy) box.Box {
+func newBox(box box.Box, policy auth.Policy) box.Box {
return &polBox{
- auth: auth,
box: box,
policy: policy,
}
}
@@ -56,77 +49,73 @@
func (pp *polBox) CanCreateZettel(ctx context.Context) bool {
return pp.box.CanCreateZettel(ctx)
}
-func (pp *polBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
- user := pp.auth.GetUser(ctx)
+func (pp *polBox) CreateZettel(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) {
+ user := server.GetUser(ctx)
if pp.policy.CanCreate(user, zettel.Meta) {
return pp.box.CreateZettel(ctx, zettel)
}
return id.Invalid, box.NewErrNotAllowed("Create", user, id.Invalid)
}
-func (pp *polBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
- zettel, err := pp.box.GetZettel(ctx, zid)
+func (pp *polBox) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) {
+ z, err := pp.box.GetZettel(ctx, zid)
if err != nil {
- return domain.Zettel{}, err
+ return zettel.Zettel{}, err
}
- user := pp.auth.GetUser(ctx)
- if pp.policy.CanRead(user, zettel.Meta) {
- return zettel, nil
+ user := server.GetUser(ctx)
+ if pp.policy.CanRead(user, z.Meta) {
+ return z, nil
}
- return domain.Zettel{}, box.NewErrNotAllowed("GetZettel", user, zid)
+ return zettel.Zettel{}, box.NewErrNotAllowed("GetZettel", user, zid)
}
-func (pp *polBox) GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) {
+func (pp *polBox) GetAllZettel(ctx context.Context, zid id.Zid) ([]zettel.Zettel, error) {
return pp.box.GetAllZettel(ctx, zid)
}
+
+func (pp *polBox) FetchZids(ctx context.Context) (id.Set, error) {
+ return nil, box.NewErrNotAllowed("fetch-zids", server.GetUser(ctx), id.Invalid)
+}
func (pp *polBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
m, err := pp.box.GetMeta(ctx, zid)
if err != nil {
return nil, err
}
- user := pp.auth.GetUser(ctx)
+ user := server.GetUser(ctx)
if pp.policy.CanRead(user, m) {
return m, nil
}
return nil, box.NewErrNotAllowed("GetMeta", user, zid)
}
-func (pp *polBox) GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) {
- return pp.box.GetAllMeta(ctx, zid)
-}
-
-func (pp *polBox) FetchZids(ctx context.Context) (id.Set, error) {
- return nil, box.NewErrNotAllowed("fetch-zids", pp.auth.GetUser(ctx), id.Invalid)
-}
-
-func (pp *polBox) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) {
- user := pp.auth.GetUser(ctx)
+func (pp *polBox) SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *query.Query) ([]*meta.Meta, error) {
+ user := server.GetUser(ctx)
canRead := pp.policy.CanRead
- s = s.AddPreMatch(func(m *meta.Meta) bool { return canRead(user, m) })
- return pp.box.SelectMeta(ctx, s)
+ q = q.SetPreMatch(func(m *meta.Meta) bool { return canRead(user, m) })
+ return pp.box.SelectMeta(ctx, metaSeq, q)
}
-func (pp *polBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
+func (pp *polBox) CanUpdateZettel(ctx context.Context, zettel zettel.Zettel) bool {
return pp.box.CanUpdateZettel(ctx, zettel)
}
-func (pp *polBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
+func (pp *polBox) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error {
zid := zettel.Meta.Zid
- user := pp.auth.GetUser(ctx)
+ user := server.GetUser(ctx)
if !zid.IsValid() {
- return &box.ErrInvalidID{Zid: zid}
+ return box.ErrInvalidZid{Zid: zid.String()}
}
// Write existing zettel
- oldMeta, err := pp.box.GetMeta(ctx, zid)
+ oldZettel, err := pp.box.GetZettel(ctx, zid)
if err != nil {
return err
}
- if pp.policy.CanWrite(user, oldMeta, zettel.Meta) {
+ if pp.policy.CanWrite(user, oldZettel.Meta, zettel.Meta) {
return pp.box.UpdateZettel(ctx, zettel)
}
return box.NewErrNotAllowed("Write", user, zid)
}
@@ -133,16 +122,16 @@
func (pp *polBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool {
return pp.box.AllowRenameZettel(ctx, zid)
}
func (pp *polBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error {
- meta, err := pp.box.GetMeta(ctx, curZid)
+ z, err := pp.box.GetZettel(ctx, curZid)
if err != nil {
return err
}
- user := pp.auth.GetUser(ctx)
- if pp.policy.CanRename(user, meta) {
+ user := server.GetUser(ctx)
+ if pp.policy.CanRename(user, z.Meta) {
return pp.box.RenameZettel(ctx, curZid, newZid)
}
return box.NewErrNotAllowed("Rename", user, curZid)
}
@@ -149,23 +138,23 @@
func (pp *polBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool {
return pp.box.CanDeleteZettel(ctx, zid)
}
func (pp *polBox) DeleteZettel(ctx context.Context, zid id.Zid) error {
- meta, err := pp.box.GetMeta(ctx, zid)
+ z, err := pp.box.GetZettel(ctx, zid)
if err != nil {
return err
}
- user := pp.auth.GetUser(ctx)
- if pp.policy.CanDelete(user, meta) {
+ user := server.GetUser(ctx)
+ if pp.policy.CanDelete(user, z.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)
+ user := server.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
@@ -1,21 +1,21 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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 policy
import (
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/auth"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/meta"
)
type defaultPolicy struct {
manager auth.AuthzManager
}
Index: auth/policy/owner.go
==================================================================
--- auth/policy/owner.go
+++ auth/policy/owner.go
@@ -1,22 +1,22 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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 policy
import (
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/auth"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/meta"
)
type ownerPolicy struct {
manager auth.AuthzManager
authConfig config.AuthConfig
Index: auth/policy/policy.go
==================================================================
--- auth/policy/policy.go
+++ auth/policy/policy.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -12,11 +12,11 @@
package policy
import (
"zettelstore.de/z/auth"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/meta"
)
// newPolicy creates a policy based on given constraints.
func newPolicy(manager auth.AuthzManager, authConfig config.AuthConfig) auth.Policy {
var pol auth.Policy
Index: auth/policy/policy_test.go
==================================================================
--- auth/policy/policy_test.go
+++ auth/policy/policy_test.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -12,14 +12,14 @@
import (
"fmt"
"testing"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/auth"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func TestPolicies(t *testing.T) {
t.Parallel()
testScene := []struct {
Index: auth/policy/readonly.go
==================================================================
--- auth/policy/readonly.go
+++ auth/policy/readonly.go
@@ -1,22 +1,22 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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 policy
-import "zettelstore.de/z/domain/meta"
+import "zettelstore.de/z/zettel/meta"
type roPolicy struct{}
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
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -14,19 +14,17 @@
import (
"context"
"errors"
"fmt"
"io"
- "net/url"
- "strconv"
"time"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
- "zettelstore.de/z/search"
+ "zettelstore.de/client.fossil/api"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// BaseBox is implemented by all Zettel boxes.
type BaseBox interface {
// Location returns some information where the box is located.
@@ -36,23 +34,20 @@
// CanCreateZettel returns true, if box could possibly create a new zettel.
CanCreateZettel(ctx context.Context) bool
// CreateZettel creates a new zettel.
// Returns the new zettel id (and an error indication).
- CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error)
+ CreateZettel(ctx context.Context, zettel zettel.Zettel) (id.Zid, error)
// GetZettel retrieves a specific zettel.
- GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error)
-
- // GetMeta retrieves just the meta data of a specific zettel.
- GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error)
+ GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error)
// CanUpdateZettel returns true, if box could possibly update the given zettel.
- CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool
+ CanUpdateZettel(ctx context.Context, zettel zettel.Zettel) bool
// UpdateZettel updates an existing zettel.
- UpdateZettel(ctx context.Context, zettel domain.Zettel) error
+ UpdateZettel(ctx context.Context, zettel zettel.Zettel) error
// AllowRenameZettel returns true, if box will not disallow renaming the zettel.
AllowRenameZettel(ctx context.Context, zid id.Zid) bool
// RenameZettel changes the current Zid to a new Zid.
@@ -72,16 +67,19 @@
type MetaFunc func(*meta.Meta)
// ManagedBox is the interface of managed boxes.
type ManagedBox interface {
BaseBox
+
+ // HasZettel returns true, if box conains zettel with given identifier.
+ HasZettel(context.Context, id.Zid) bool
// Apply identifier of every zettel to the given function, if predicate returns true.
- ApplyZid(context.Context, ZidFunc, search.RetrievePredicate) error
+ ApplyZid(context.Context, ZidFunc, query.RetrievePredicate) error
// Apply metadata of every zettel to the given function, if predicate returns true.
- ApplyMeta(context.Context, MetaFunc, search.RetrievePredicate) error
+ ApplyMeta(context.Context, MetaFunc, query.RetrievePredicate) error
// ReadStats populates st with box statistics
ReadStats(st *ManagedBoxStats)
}
@@ -91,15 +89,33 @@
ReadOnly bool
// Zettel is the number of zettel managed by the box.
Zettel int
}
+
+// StartState enumerates the possible states of starting and stopping a box.
+//
+// StartStateStopped -> StartStateStarting -> StartStateStarted -> StateStateStopping -> StartStateStopped.
+//
+// Other transitions are also possible.
+type StartState uint8
+
+// Constant values of StartState
+const (
+ StartStateStopped StartState = iota
+ StartStateStarting
+ StartStateStarted
+ StartStateStopping
+)
// StartStopper performs simple lifecycle management.
type StartStopper interface {
+ // State the current status of the box.
+ State() StartState
+
// Start the box. Now all other functions of the box are allowed.
- // Starting an already started box is not allowed.
+ // Starting a box, which is not in state StartStateStopped is not allowed.
Start(ctx context.Context) error
// Stop the started box. Now only the Start() function is allowed.
Stop(ctx context.Context)
}
@@ -114,19 +130,20 @@
type Box interface {
BaseBox
// FetchZids returns the set of all zettel identifer managed by the box.
FetchZids(ctx context.Context) (id.Set, error)
+
+ // GetMeta returns the metadata of the zettel with the given identifier.
+ GetMeta(context.Context, id.Zid) (*meta.Meta, error)
// SelectMeta returns a list of metadata that comply to the given selection criteria.
- SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error)
+ // If `metaSeq` is `nil`, the box assumes metadata of all available zettel.
+ SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *query.Query) ([]*meta.Meta, error)
// 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)
+ GetAllZettel(ctx context.Context, zid id.Zid) ([]zettel.Zettel, error)
// Refresh the data from the box and from its managed sub-boxes.
Refresh(context.Context) error
}
@@ -181,18 +198,18 @@
type UpdateReason uint8
// Values for Reason
const (
_ UpdateReason = iota
+ OnReady // Box is started and fully operational
OnReload // Box was reloaded
- OnUpdate // A zettel was created or changed
- OnDelete // A zettel was removed
+ OnZettel // Something with a zettel happened
)
// UpdateInfo contains all the data about a changed zettel.
type UpdateInfo struct {
- Box Box
+ Box BaseBox
Reason UpdateReason
Zid id.Zid
}
// UpdateFunc is a function to be called when a change is detected.
@@ -226,10 +243,18 @@
// DoNotEnrich determines if the context is marked to not enrich metadata.
func DoNotEnrich(ctx context.Context) bool {
_, ok := ctx.Value(ctxNoEnrichKey).(*ctxNoEnrichType)
return ok
}
+
+// NoEnrichQuery provides a context that signals not to enrich, if the query does not need this.
+func NoEnrichQuery(ctx context.Context, q *query.Query) context.Context {
+ if q.EnrichNeeded() {
+ return ctx
+ }
+ return NoEnrichContext(ctx)
+}
// ErrNotAllowed is returned if the caller is not allowed to perform the operation.
type ErrNotAllowed struct {
Op string
User *meta.Meta
@@ -248,28 +273,22 @@
func (err *ErrNotAllowed) Error() string {
if err.User == nil {
if err.Zid.IsValid() {
return fmt.Sprintf(
"operation %q on zettel %v not allowed for not authorized user",
- err.Op,
- err.Zid.String())
+ err.Op, err.Zid)
}
return fmt.Sprintf("operation %q not allowed for not authorized user", err.Op)
}
if err.Zid.IsValid() {
return fmt.Sprintf(
"operation %q on zettel %v not allowed for user %v/%v",
- err.Op,
- err.Zid.String(),
- err.User.GetDefault(api.KeyUserID, "?"),
- err.User.Zid.String())
+ err.Op, err.Zid, err.User.GetDefault(api.KeyUserID, "?"), err.User.Zid)
}
return fmt.Sprintf(
"operation %q not allowed for user %v/%v",
- err.Op,
- err.User.GetDefault(api.KeyUserID, "?"),
- err.User.Zid.String())
+ err.Op, err.User.GetDefault(api.KeyUserID, "?"), err.User.Zid)
}
// Is return true, if the error is of type ErrNotAllowed.
func (*ErrNotAllowed) Is(error) bool { return true }
@@ -280,44 +299,23 @@
var ErrStopped = errors.New("box is stopped")
// ErrReadOnly is returned if there is an attepmt to write to a read-only box.
var ErrReadOnly = errors.New("read-only box")
-// ErrNotFound is returned if a zettel was not found in the box.
-var ErrNotFound = errors.New("zettel not found")
+// ErrZettelNotFound is returned if a zettel was not found in the box.
+type ErrZettelNotFound struct{ Zid id.Zid }
+
+func (eznf ErrZettelNotFound) Error() string { return "zettel not found: " + eznf.Zid.String() }
+
+//var ErrZettelNotFound = errors.New("zettel not found")
// ErrConflict is returned if a box operation detected a conflict..
// One example: if calculating a new zettel identifier takes too long.
var ErrConflict = errors.New("conflict")
// ErrCapacity is returned if a box has reached its capacity.
var ErrCapacity = errors.New("capacity exceeded")
-// ErrInvalidID is returned if the zettel id is not appropriate for the box operation.
-type ErrInvalidID struct{ Zid id.Zid }
-
-func (err *ErrInvalidID) Error() string { return "invalid Zettel id: " + err.Zid.String() }
-
-// GetQueryBool is a helper function to extract bool values from a box URI.
-func GetQueryBool(u *url.URL, key string) bool {
- _, ok := u.Query()[key]
- return ok
-}
-
-// GetQueryInt is a helper function to extract int values of a specified range from a box URI.
-func GetQueryInt(u *url.URL, key string, min, def, max int) int {
- sVal := u.Query().Get(key)
- if sVal == "" {
- return def
- }
- iVal, err := strconv.Atoi(sVal)
- if err != nil {
- return def
- }
- if iVal < min {
- return min
- }
- if iVal > max {
- return max
- }
- return iVal
-}
+// ErrInvalidZid is returned if the zettel id is not appropriate for the box operation.
+type ErrInvalidZid struct{ Zid string }
+
+func (err ErrInvalidZid) Error() string { return "invalid Zettel id: " + err.Zid }
Index: box/compbox/compbox.go
==================================================================
--- box/compbox/compbox.go
+++ box/compbox/compbox.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -13,19 +13,19 @@
import (
"context"
"net/url"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"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"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func init() {
manager.Register(
" comp",
@@ -70,50 +70,42 @@
func (*compBox) Location() string { return "" }
func (*compBox) CanCreateZettel(context.Context) bool { return false }
-func (cb *compBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) {
+func (cb *compBox) CreateZettel(context.Context, zettel.Zettel) (id.Zid, error) {
cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel")
return id.Invalid, box.ErrReadOnly
}
-func (cb *compBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
+func (cb *compBox) GetZettel(_ context.Context, zid id.Zid) (zettel.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 (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 (cb *compBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.RetrievePredicate) error {
- cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyMeta")
+ cb.log.Trace().Msg("GetZettel/Content")
+ return zettel.Zettel{
+ Meta: m,
+ Content: zettel.NewContent(genContent(m)),
+ }, nil
+ }
+ cb.log.Trace().Msg("GetZettel/NoContent")
+ return zettel.Zettel{Meta: m}, nil
+ }
+ }
+ err := box.ErrZettelNotFound{Zid: zid}
+ cb.log.Trace().Err(err).Msg("GetZettel/Err")
+ return zettel.Zettel{}, err
+}
+
+func (*compBox) HasZettel(_ context.Context, zid id.Zid) bool {
+ _, found := myZettel[zid]
+ return found
+}
+
+func (cb *compBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error {
+ cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyZid")
for zid, gen := range myZettel {
if !constraint(zid) {
continue
}
if genMeta := gen.meta; genMeta != nil {
@@ -123,11 +115,11 @@
}
}
return nil
}
-func (cb *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error {
+func (cb *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error {
cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyMeta")
for zid, gen := range myZettel {
if !constraint(zid) {
continue
}
@@ -140,37 +132,39 @@
}
}
return nil
}
-func (*compBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false }
+func (*compBox) CanUpdateZettel(context.Context, zettel.Zettel) bool { return false }
-func (cb *compBox) UpdateZettel(context.Context, domain.Zettel) error {
+func (cb *compBox) UpdateZettel(context.Context, zettel.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 (cb *compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error {
- err := box.ErrNotFound
+func (cb *compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) (err error) {
if _, ok := myZettel[curZid]; ok {
err = box.ErrReadOnly
+ } else {
+ err = box.ErrZettelNotFound{Zid: curZid}
}
cb.log.Trace().Err(err).Msg("RenameZettel")
return err
}
func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false }
-func (cb *compBox) DeleteZettel(_ context.Context, zid id.Zid) error {
- err := box.ErrNotFound
+func (cb *compBox) DeleteZettel(_ context.Context, zid id.Zid) (err error) {
if _, ok := myZettel[zid]; ok {
err = box.ErrReadOnly
+ } else {
+ err = box.ErrZettelNotFound{Zid: zid}
}
cb.log.Trace().Err(err).Msg("DeleteZettel")
return err
}
@@ -180,14 +174,14 @@
cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats")
}
func updateMeta(m *meta.Meta) {
if _, ok := m.Get(api.KeySyntax); !ok {
- m.Set(api.KeySyntax, api.ValueSyntaxZmk)
+ m.Set(api.KeySyntax, meta.SyntaxZmk)
}
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,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -11,21 +11,23 @@
package compbox
import (
"bytes"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
+ "zettelstore.de/z/kernel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func genConfigZettelM(zid id.Zid) *meta.Meta {
if myConfig == nil {
return nil
}
m := meta.New(zid)
m.Set(api.KeyTitle, "Zettelstore Startup Configuration")
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string))
m.Set(api.KeyVisibility, api.ValueVisibilityExpert)
return m
}
func genConfigZettelC(*meta.Meta) []byte {
Index: box/compbox/keys.go
==================================================================
--- box/compbox/keys.go
+++ box/compbox/keys.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -12,18 +12,20 @@
import (
"bytes"
"fmt"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
+ "zettelstore.de/z/kernel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func genKeysM(zid id.Zid) *meta.Meta {
m := meta.New(zid)
m.Set(api.KeyTitle, "Zettelstore Supported Metadata Keys")
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string))
m.Set(api.KeyVisibility, api.ValueVisibilityLogin)
return m
}
func genKeysC(*meta.Meta) []byte {
@@ -30,9 +32,9 @@
keys := meta.GetSortedKeyDescriptions()
var buf bytes.Buffer
buf.WriteString("|=Name<|=Type<|=Computed?:|=Property?:\n")
for _, kd := range keys {
fmt.Fprintf(&buf,
- "|%v|%v|%v|%v\n", kd.Name, kd.Type.Name, kd.IsComputed(), kd.IsProperty())
+ "|[[%v|query:%v?]]|%v|%v|%v\n", kd.Name, kd.Name, kd.Type.Name, kd.IsComputed(), kd.IsProperty())
}
return buf.Bytes()
}
Index: box/compbox/log.go
==================================================================
--- box/compbox/log.go
+++ box/compbox/log.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -11,20 +11,22 @@
package compbox
import (
"bytes"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/kernel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func genLogM(zid id.Zid) *meta.Meta {
m := meta.New(zid)
m.Set(api.KeyTitle, "Zettelstore Log")
- m.Set(api.KeySyntax, api.ValueSyntaxText)
+ m.Set(api.KeySyntax, meta.SyntaxText)
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string))
+ m.Set(api.KeyModified, kernel.Main.GetLastLogTime().Local().Format(id.ZidLayout))
return m
}
func genLogC(*meta.Meta) []byte {
const tsFormat = "2006-01-02 15:04:05.999999"
Index: box/compbox/manager.go
==================================================================
--- box/compbox/manager.go
+++ box/compbox/manager.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -12,19 +12,20 @@
import (
"bytes"
"fmt"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/kernel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func genManagerM(zid id.Zid) *meta.Meta {
m := meta.New(zid)
m.Set(api.KeyTitle, "Zettelstore Box Manager")
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string))
return m
}
func genManagerC(*meta.Meta) []byte {
kvl := kernel.Main.GetServiceStatistics(kernel.BoxService)
Index: box/compbox/parser.go
==================================================================
--- box/compbox/parser.go
+++ box/compbox/parser.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -14,26 +14,28 @@
"bytes"
"fmt"
"sort"
"strings"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
+ "zettelstore.de/z/kernel"
"zettelstore.de/z/parser"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func genParserM(zid id.Zid) *meta.Meta {
m := meta.New(zid)
m.Set(api.KeyTitle, "Zettelstore Supported Parser")
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string))
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")
+ buf.WriteString("|=Syntax<|=Alt. Value(s):|=Text Parser?:|=Text Format?:|=Image Format?:\n")
syntaxes := parser.GetSyntaxes()
sort.Strings(syntaxes)
for _, syntax := range syntaxes {
info := parser.Get(syntax)
if info.Name != syntax {
@@ -40,10 +42,10 @@
continue
}
altNames := info.AltNames
sort.Strings(altNames)
fmt.Fprintf(
- &buf, "|%v|%v|%v|%v\n",
- syntax, strings.Join(altNames, ", "), info.IsTextParser, info.IsImageFormat)
+ &buf, "|%v|%v|%v|%v|%v\n",
+ syntax, strings.Join(altNames, ", "), info.IsASTParser, info.IsTextFormat, info.IsImageFormat)
}
return buf.Bytes()
}
Index: box/compbox/version.go
==================================================================
--- box/compbox/version.go
+++ box/compbox/version.go
@@ -1,22 +1,22 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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 (
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/kernel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func getVersionMeta(zid id.Zid, title string) *meta.Meta {
m := meta.New(zid)
m.Set(api.KeyTitle, title)
@@ -24,30 +24,35 @@
return m
}
func genVersionBuildM(zid id.Zid) *meta.Meta {
m := getVersionMeta(zid, "Zettelstore Version")
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string))
m.Set(api.KeyVisibility, api.ValueVisibilityLogin)
return m
}
func genVersionBuildC(*meta.Meta) []byte {
return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string))
}
func genVersionHostM(zid id.Zid) *meta.Meta {
- return getVersionMeta(zid, "Zettelstore Host")
+ m := getVersionMeta(zid, "Zettelstore Host")
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string))
+ return m
}
func genVersionHostC(*meta.Meta) []byte {
return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreHostname).(string))
}
func genVersionOSM(zid id.Zid) *meta.Meta {
- return getVersionMeta(zid, "Zettelstore Operating System")
+ m := getVersionMeta(zid, "Zettelstore Operating System")
+ m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string))
+ return m
}
func genVersionOSC(*meta.Meta) []byte {
goOS := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreGoOS).(string)
goArch := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreGoArch).(string)
result := make([]byte, 0, len(goOS)+len(goArch)+1)
result = append(result, goOS...)
result = append(result, '/')
return append(result, goArch...)
}
Index: box/constbox/base.css
==================================================================
--- box/constbox/base.css
+++ box/constbox/base.css
@@ -81,11 +81,11 @@
h3 { font-size:1.15rem; margin:.75rem 0 }
h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold }
h5 { font-size:1.05rem; margin:.8rem 0 }
h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter }
p { margin: .5rem 0 0 0 }
- ol,ul { padding-left: 1.1rem }
+ p.zs-tag-zettel { margin-top: .5rem; margin-left: 0.5rem }
li,figure,figcaption,dl { margin: 0 }
dt { margin: .5rem 0 0 0 }
dt+dd { margin-top: 0 }
dd { margin: .5rem 0 0 2rem }
dd > p:first-child { margin: 0 0 0 0 }
@@ -101,17 +101,17 @@
table {
border-collapse: collapse;
border-spacing: 0;
max-width: 100%;
}
- th,td {
+ thead>tr>td { border-bottom: 2px solid hsl(0, 0%, 70%); font-weight: bold }
+ tfoot>tr>td { border-top: 2px solid hsl(0, 0%, 70%); font-weight: bold }
+ td {
text-align: left;
padding: .25rem .5rem;
+ border-bottom: 1px solid hsl(0, 0%, 85%)
}
- td { border-bottom: 1px solid hsl(0, 0%, 85%) }
- thead th { border-bottom: 2px solid hsl(0, 0%, 70%) }
- tfoot th { border-top: 2px solid hsl(0, 0%, 70%) }
main form {
padding: 0 .5em;
margin: .5em 0 0 0;
}
main form:after {
@@ -137,12 +137,17 @@
border-bottom:1px solid #ccc;
width:100%;
}
input.zs-primary { float:right }
input.zs-secondary { float:left }
+ input.zs-upload {
+ padding-left: 1em;
+ padding-right: 1em;
+ }
a:not([class]) { text-decoration-skip-ink: auto }
a.broken { text-decoration: line-through }
+ a.external::after { content: "➚"; display: inline-block }
img { max-width: 100% }
img.right { float: right }
ol.zs-endnotes {
padding-top: .5rem;
border-top: 1px solid;
@@ -183,11 +188,10 @@
border: 1px solid black;
border-radius: .25rem;
padding: .1rem .2rem;
font-size: 95%;
}
- .zs-example { border-style: dotted !important }
.zs-info {
background-color: lightblue;
padding: .5rem 1rem;
}
.zs-warning {
@@ -197,13 +201,13 @@
.zs-error {
background-color: lightpink;
border-style: none !important;
font-weight: bold;
}
- td.left,th.left { text-align:left }
- td.center,th.center { text-align:center }
- td.right,th.right { text-align:right }
+ td.left { text-align:left }
+ td.center { text-align:center }
+ td.right { text-align:right }
.zs-font-size-0 { font-size:75% }
.zs-font-size-1 { font-size:83% }
.zs-font-size-2 { font-size:100% }
.zs-font-size-3 { font-size:117% }
.zs-font-size-4 { font-size:150% }
DELETED box/constbox/base.mustache
Index: box/constbox/base.mustache
==================================================================
--- box/constbox/base.mustache
+++ box/constbox/base.mustache
@@ -1,66 +0,0 @@
-
-
-
-
-
-
-
-{{{MetaHeader}}}
-
-
-{{#CSSRoleURL}} {{/CSSRoleURL}}
-{{Title}}
-
-
-
-
-{{{Content}}}
-
-{{#FooterHTML}}{{/FooterHTML}}
-{{#DebugMode}}WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!
{{/DebugMode}}
-
-
ADDED box/constbox/base.sxn
Index: box/constbox/base.sxn
==================================================================
--- box/constbox/base.sxn
+++ box/constbox/base.sxn
@@ -0,0 +1,48 @@
+`(@@@@
+(html ,@(if lang `((@ (lang ,lang))))
+(head
+ (meta (@ (charset "utf-8")))
+ (meta (@ (name "viewport") (content "width=device-width, initial-scale=1.0")))
+ (meta (@ (name "generator") (content "Zettelstore")))
+ (meta (@ (name "format-detection") (content "telephone=no")))
+ ,@META-HEADER
+ (link (@ (rel "stylesheet") (href ,css-base-url)))
+ (link (@ (rel "stylesheet") (href ,css-user-url)))
+ ,@(ROLE-DEFAULT-meta (current-environment))
+ (title ,title))
+(body
+ (nav (@ (class "zs-menu"))
+ (a (@ (href ,home-url)) "Home")
+ ,@(if with-auth
+ `((div (@ (class "zs-dropdown"))
+ (button "User")
+ (nav (@ (class "zs-dropdown-content"))
+ ,@(if user-is-valid
+ `((a (@ (href ,user-zettel-url)) ,user-ident)
+ (a (@ (href ,logout-url)) "Logout"))
+ `((a (@ (href ,login-url)) "Login"))
+ )
+ )))
+ )
+ (div (@ (class "zs-dropdown"))
+ (button "Lists")
+ (nav (@ (class "zs-dropdown-content"))
+ (a (@ (href ,list-zettel-url)) "List Zettel")
+ (a (@ (href ,list-roles-url)) "List Roles")
+ (a (@ (href ,list-tags-url)) "List Tags")
+ ,@(if (bound? 'refresh-url) `((a (@ (href ,refresh-url)) "Refresh")))
+ ))
+ ,@(if new-zettel-links
+ `((div (@ (class "zs-dropdown"))
+ (button "New")
+ (nav (@ (class "zs-dropdown-content"))
+ ,@(map wui-link new-zettel-links)
+ )))
+ )
+ (form (@ (action ,search-url))
+ (input (@ (type "text") (placeholder "Search..") (name ,query-key-query) (dir "auto"))))
+ )
+ (main (@ (class "content")) ,DETAIL)
+ ,@(if FOOTER `((footer (hr) ,@FOOTER)))
+ ,@(if debug-mode '((div (b "WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!"))))
+)))
Index: box/constbox/constbox.go
==================================================================
--- box/constbox/constbox.go
+++ box/constbox/constbox.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -14,19 +14,19 @@
import (
"context"
_ "embed" // Allow to embed file content
"net/url"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"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"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func init() {
manager.Register(
" const",
@@ -43,11 +43,11 @@
type constHeader map[string]string
type constZettel struct {
header constHeader
- content domain.Content
+ content zettel.Content
}
type constBox struct {
log *logger.Logger
number int
@@ -57,44 +57,41 @@
func (*constBox) Location() string { return "const:" }
func (*constBox) CanCreateZettel(context.Context) bool { return false }
-func (cb *constBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) {
+func (cb *constBox) CreateZettel(context.Context, zettel.Zettel) (id.Zid, error) {
cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel")
return id.Invalid, box.ErrReadOnly
}
-func (cb *constBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
+func (cb *constBox) GetZettel(_ context.Context, zid id.Zid) (zettel.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 (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 (cb *constBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.RetrievePredicate) error {
+ return zettel.Zettel{Meta: meta.NewWithData(zid, z.header), Content: z.content}, nil
+ }
+ err := box.ErrZettelNotFound{Zid: zid}
+ cb.log.Trace().Err(err).Msg("GetZettel/Err")
+ return zettel.Zettel{}, err
+}
+
+func (cb *constBox) HasZettel(_ context.Context, zid id.Zid) bool {
+ _, found := cb.zettel[zid]
+ return found
+}
+
+func (cb *constBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.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 (cb *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error {
+func (cb *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.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)
@@ -102,37 +99,39 @@
}
}
return nil
}
-func (*constBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false }
+func (*constBox) CanUpdateZettel(context.Context, zettel.Zettel) bool { return false }
-func (cb *constBox) UpdateZettel(context.Context, domain.Zettel) error {
+func (cb *constBox) UpdateZettel(context.Context, zettel.Zettel) error {
cb.log.Trace().Err(box.ErrReadOnly).Msg("UpdateZettel")
return box.ErrReadOnly
}
func (cb *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool {
_, ok := cb.zettel[zid]
return !ok
}
-func (cb *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error {
- err := box.ErrNotFound
+func (cb *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) (err error) {
if _, ok := cb.zettel[curZid]; ok {
err = box.ErrReadOnly
+ } else {
+ err = box.ErrZettelNotFound{Zid: curZid}
}
cb.log.Trace().Err(err).Msg("RenameZettel")
return err
}
func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false }
-func (cb *constBox) DeleteZettel(_ context.Context, zid id.Zid) error {
- err := box.ErrNotFound
+func (cb *constBox) DeleteZettel(_ context.Context, zid id.Zid) (err error) {
if _, ok := cb.zettel[zid]; ok {
err = box.ErrReadOnly
+ } else {
+ err = box.ErrZettelNotFound{Zid: zid}
}
cb.log.Trace().Err(err).Msg("DeleteZettel")
return err
}
@@ -140,216 +139,234 @@
st.ReadOnly = true
st.Zettel = len(cb.zettel)
cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats")
}
-const syntaxTemplate = "mustache"
-
var constZettelMap = map[id.Zid]constZettel{
id.ConfigurationZid: {
constHeader{
api.KeyTitle: "Zettelstore Runtime Configuration",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxNone,
+ api.KeySyntax: meta.SyntaxNone,
+ api.KeyCreated: "20200804111624",
api.KeyVisibility: api.ValueVisibilityOwner,
},
- domain.NewContent(nil)},
+ zettel.NewContent(nil)},
id.MustParse(api.ZidLicense): {
constHeader{
api.KeyTitle: "Zettelstore License",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxText,
+ api.KeySyntax: meta.SyntaxText,
+ api.KeyCreated: "20210504135842",
api.KeyLang: api.ValueLangEN,
+ api.KeyModified: "20220131153422",
api.KeyReadOnly: api.ValueTrue,
api.KeyVisibility: api.ValueVisibilityPublic,
},
- domain.NewContent(contentLicense)},
+ zettel.NewContent(contentLicense)},
id.MustParse(api.ZidAuthors): {
constHeader{
api.KeyTitle: "Zettelstore Contributors",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxZmk,
+ api.KeySyntax: meta.SyntaxZmk,
+ api.KeyCreated: "20210504135842",
api.KeyLang: api.ValueLangEN,
api.KeyReadOnly: api.ValueTrue,
api.KeyVisibility: api.ValueVisibilityLogin,
},
- domain.NewContent(contentContributors)},
+ zettel.NewContent(contentContributors)},
id.MustParse(api.ZidDependencies): {
constHeader{
api.KeyTitle: "Zettelstore Dependencies",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxZmk,
+ api.KeySyntax: meta.SyntaxZmk,
api.KeyLang: api.ValueLangEN,
api.KeyReadOnly: api.ValueTrue,
api.KeyVisibility: api.ValueVisibilityLogin,
+ api.KeyCreated: "20210504135842",
+ api.KeyModified: "20230601163100",
},
- domain.NewContent(contentDependencies)},
+ zettel.NewContent(contentDependencies)},
id.BaseTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Base HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20230510155100",
+ api.KeyModified: "20230827212200",
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentBaseMustache)},
+ zettel.NewContent(contentBaseSxn)},
id.LoginTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Login Form HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20200804111624",
+ api.KeyModified: "20230527144100",
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentLoginMustache)},
+ zettel.NewContent(contentLoginSxn)},
id.ZettelTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Zettel HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20230510155300",
+ api.KeyModified: "20230907203300",
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentZettelMustache)},
+ zettel.NewContent(contentZettelSxn)},
id.InfoTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Info HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20200804111624",
+ api.KeyModified: "20230907203300",
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentInfoMustache)},
- id.ContextTemplateZid: {
- constHeader{
- api.KeyTitle: "Zettelstore Context HTML Template",
- api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
- api.KeyVisibility: api.ValueVisibilityExpert,
- },
- domain.NewContent(contentContextMustache)},
+ zettel.NewContent(contentInfoSxn)},
id.FormTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Form HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20200804111624",
+ api.KeyModified: "20230621132600",
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentFormMustache)},
+ zettel.NewContent(contentFormSxn)},
id.RenameTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Rename Form HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20200804111624",
+ api.KeyModified: "20230707190246",
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentRenameMustache)},
+ zettel.NewContent(contentRenameSxn)},
id.DeleteTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Delete HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20200804111624",
+ api.KeyModified: "20230621133100",
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentDeleteMustache)},
+ zettel.NewContent(contentDeleteSxn)},
id.ListTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore List Zettel HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
- api.KeyVisibility: api.ValueVisibilityExpert,
- },
- domain.NewContent(contentListZettelMustache)},
- id.RolesTemplateZid: {
- constHeader{
- api.KeyTitle: "Zettelstore List Roles HTML Template",
- api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
- api.KeyVisibility: api.ValueVisibilityExpert,
- },
- domain.NewContent(contentListRolesMustache)},
- id.TagsTemplateZid: {
- constHeader{
- api.KeyTitle: "Zettelstore List Tags HTML Template",
- api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
- api.KeyVisibility: api.ValueVisibilityExpert,
- },
- domain.NewContent(contentListTagsMustache)},
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20230704122100",
+ api.KeyModified: "20230829223600",
+ api.KeyVisibility: api.ValueVisibilityExpert,
+ },
+ zettel.NewContent(contentListZettelSxn)},
id.ErrorTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Error HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: syntaxTemplate,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20210305133215",
+ api.KeyModified: "20230527224800",
+ api.KeyVisibility: api.ValueVisibilityExpert,
+ },
+ zettel.NewContent(contentErrorSxn)},
+ id.StartSxnZid: {
+ constHeader{
+ api.KeyTitle: "Zettelstore Sxn Start Code",
+ api.KeyRole: api.ValueRoleConfiguration,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20230824160700",
+ api.KeyVisibility: api.ValueVisibilityExpert,
+ api.KeyPrecursor: id.BaseSxnZid.String(),
+ },
+ zettel.NewContent(contentStartCodeSxn)},
+ id.BaseSxnZid: {
+ constHeader{
+ api.KeyTitle: "Zettelstore Sxn Base Code",
+ api.KeyRole: api.ValueRoleConfiguration,
+ api.KeySyntax: meta.SyntaxSxn,
+ api.KeyCreated: "20230619132800",
+ api.KeyModified: "20230907203100",
+ api.KeyReadOnly: api.ValueTrue,
api.KeyVisibility: api.ValueVisibilityExpert,
},
- domain.NewContent(contentErrorMustache)},
+ zettel.NewContent(contentBaseCodeSxn)},
id.MustParse(api.ZidBaseCSS): {
constHeader{
api.KeyTitle: "Zettelstore Base CSS",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: "css",
+ api.KeySyntax: meta.SyntaxCSS,
+ api.KeyCreated: "20200804111624",
api.KeyVisibility: api.ValueVisibilityPublic,
},
- domain.NewContent(contentBaseCSS)},
+ zettel.NewContent(contentBaseCSS)},
id.MustParse(api.ZidUserCSS): {
constHeader{
api.KeyTitle: "Zettelstore User CSS",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: "css",
+ api.KeySyntax: meta.SyntaxCSS,
+ api.KeyCreated: "20210622110143",
api.KeyVisibility: api.ValueVisibilityPublic,
},
- domain.NewContent([]byte("/* User-defined CSS */"))},
- id.RoleCSSMapZid: {
- constHeader{
- api.KeyTitle: "Zettelstore Role to CSS Map",
- api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxNone,
- api.KeyVisibility: api.ValueVisibilityExpert,
- },
- domain.NewContent(nil)},
+ zettel.NewContent([]byte("/* User-defined CSS */"))},
id.EmojiZid: {
constHeader{
api.KeyTitle: "Zettelstore Generic Emoji",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxGif,
+ api.KeySyntax: meta.SyntaxGif,
api.KeyReadOnly: api.ValueTrue,
+ api.KeyCreated: "20210504175807",
api.KeyVisibility: api.ValueVisibilityPublic,
},
- domain.NewContent(contentEmoji)},
+ zettel.NewContent(contentEmoji)},
id.TOCNewTemplateZid: {
constHeader{
api.KeyTitle: "New Menu",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxZmk,
+ api.KeySyntax: meta.SyntaxZmk,
api.KeyLang: api.ValueLangEN,
+ api.KeyCreated: "20210217161829",
api.KeyVisibility: api.ValueVisibilityCreator,
},
- domain.NewContent(contentNewTOCZettel)},
+ zettel.NewContent(contentNewTOCZettel)},
id.MustParse(api.ZidTemplateNewZettel): {
constHeader{
api.KeyTitle: "New Zettel",
api.KeyRole: api.ValueRoleZettel,
- api.KeySyntax: api.ValueSyntaxZmk,
+ api.KeySyntax: meta.SyntaxZmk,
+ api.KeyCreated: "20201028185209",
api.KeyVisibility: api.ValueVisibilityCreator,
},
- domain.NewContent(nil)},
+ zettel.NewContent(nil)},
id.MustParse(api.ZidTemplateNewUser): {
constHeader{
api.KeyTitle: "New User",
api.KeyRole: api.ValueRoleConfiguration,
- api.KeySyntax: api.ValueSyntaxNone,
+ api.KeySyntax: meta.SyntaxNone,
+ api.KeyCreated: "20201028185209",
meta.NewPrefix + api.KeyCredential: "",
meta.NewPrefix + api.KeyUserID: "",
meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader,
api.KeyVisibility: api.ValueVisibilityOwner,
},
- domain.NewContent(nil)},
+ zettel.NewContent(nil)},
id.DefaultHomeZid: {
constHeader{
- api.KeyTitle: "Home",
- api.KeyRole: api.ValueRoleZettel,
- api.KeySyntax: api.ValueSyntaxZmk,
- api.KeyLang: api.ValueLangEN,
+ api.KeyTitle: "Home",
+ api.KeyRole: api.ValueRoleZettel,
+ api.KeySyntax: meta.SyntaxZmk,
+ api.KeyLang: api.ValueLangEN,
+ api.KeyCreated: "20210210190757",
},
- domain.NewContent(contentHomeZettel)},
+ zettel.NewContent(contentHomeZettel)},
}
//go:embed license.txt
var contentLicense []byte
@@ -357,45 +374,42 @@
var contentContributors []byte
//go:embed dependencies.zettel
var contentDependencies []byte
-//go:embed base.mustache
-var contentBaseMustache []byte
-
-//go:embed login.mustache
-var contentLoginMustache []byte
-
-//go:embed zettel.mustache
-var contentZettelMustache []byte
-
-//go:embed info.mustache
-var contentInfoMustache []byte
-
-//go:embed context.mustache
-var contentContextMustache []byte
-
-//go:embed form.mustache
-var contentFormMustache []byte
-
-//go:embed rename.mustache
-var contentRenameMustache []byte
-
-//go:embed delete.mustache
-var contentDeleteMustache []byte
-
-//go:embed listzettel.mustache
-var contentListZettelMustache []byte
-
-//go:embed listroles.mustache
-var contentListRolesMustache []byte
-
-//go:embed listtags.mustache
-var contentListTagsMustache []byte
-
-//go:embed error.mustache
-var contentErrorMustache []byte
+//go:embed base.sxn
+var contentBaseSxn []byte
+
+//go:embed login.sxn
+var contentLoginSxn []byte
+
+//go:embed zettel.sxn
+var contentZettelSxn []byte
+
+//go:embed info.sxn
+var contentInfoSxn []byte
+
+//go:embed form.sxn
+var contentFormSxn []byte
+
+//go:embed rename.sxn
+var contentRenameSxn []byte
+
+//go:embed delete.sxn
+var contentDeleteSxn []byte
+
+//go:embed listzettel.sxn
+var contentListZettelSxn []byte
+
+//go:embed error.sxn
+var contentErrorSxn []byte
+
+//go:embed start.sxn
+var contentStartCodeSxn []byte
+
+//go:embed wuicode.sxn
+var contentBaseCodeSxn []byte
//go:embed base.css
var contentBaseCSS []byte
//go:embed emoji_spin.gif
DELETED box/constbox/context.mustache
Index: box/constbox/context.mustache
==================================================================
--- box/constbox/context.mustache
+++ box/constbox/context.mustache
@@ -1,16 +0,0 @@
-
-
-{{{Start.Text}}}
-
-
DELETED box/constbox/delete.mustache
Index: box/constbox/delete.mustache
==================================================================
--- box/constbox/delete.mustache
+++ box/constbox/delete.mustache
@@ -1,43 +0,0 @@
-
-
-Do you really want to delete this zettel?
-{{#HasShadows}}
-
-
Infomation
-
If you delete this zettel, the previoulsy shadowed zettel from overlayed box {{ShadowedBox}} becomes available.
-
-{{/HasShadows}}
-{{#HasIncoming}}
-
-
Warning!
-
If you delete this zettel, incoming references from the following zettel will become invalid.
-
-{{#Incoming}}
-{{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.
-
-{{#UselessFiles}}
-{{{.}}}
-{{/UselessFiles}}
-
-
-{{/HasUselessFiles}}
-
-{{#MetaPairs}}
-{{Key}}: {{Value}}
-{{/MetaPairs}}
-
-
-
-{{end}}
ADDED box/constbox/delete.sxn
Index: box/constbox/delete.sxn
==================================================================
--- box/constbox/delete.sxn
+++ box/constbox/delete.sxn
@@ -0,0 +1,26 @@
+`(article
+ (header (h1 "Delete Zettel " ,zid))
+ (p "Do you really want to delete this zettel?")
+ ,@(if shadowed-box
+ `((div (@ (class "zs-info"))
+ (h2 "Information")
+ (p "If you delete this zettel, the previously shadowed zettel from overlayed box " ,shadowed-box " becomes available.")
+ ))
+ )
+ ,@(if incoming
+ `((div (@ (class "zs-warning"))
+ (h2 "Warning!")
+ (p "If you delete this zettel, incoming references from the following zettel will become invalid.")
+ (ul ,@(map wui-item-link incoming))
+ ))
+ )
+ ,@(if (and (bound? 'useless) useless)
+ `((div (@ (class "zs-warning"))
+ (h2 "Warning!")
+ (p "Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.")
+ (ul ,@(map wui-item useless))
+ ))
+ )
+ ,(wui-meta-desc metapairs)
+ (form (@ (method "POST")) (input (@ (class "zs-primary") (type "submit") (value "Delete"))))
+)
Index: box/constbox/dependencies.zettel
==================================================================
--- box/constbox/dependencies.zettel
+++ box/constbox/dependencies.zettel
@@ -72,83 +72,35 @@
; License
: BSD 3-Clause "New" or "Revised" License
; Source
: [[https://github.com/fsnotify/fsnotify]]
```
-Copyright (c) 2012 The Go Authors. All rights reserved.
-Copyright (c) 2012-2019 fsnotify Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-```
-
-=== hoisie/mustache / cbroglie/mustache
-; URL & Source
-: [[https://github.com/hoisie/mustache]] / [[https://github.com/cbroglie/mustache]]
-; License
-: MIT License
-; Remarks
-: cbroglie/mustache is a fork from hoisie/mustache (starting with commit [f9b4cbf]).
- cbroglie/mustache does not claim a copyright and includes just the license file from hoisie/mustache.
- cbroglie/mustache obviously continues with the original license.
-
-```
-Copyright (c) 2009 Michael Hoisie
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-```
-
-=== pascaldekloe/jwt
-; URL & Source
-: [[https://github.com/pascaldekloe/jwt]]
-; License
-: [[CC0 1.0 Universal|https://creativecommons.org/publicdomain/zero/1.0/legalcode]]
-```
-To the extent possible under law, Pascal S. de Kloe has waived all
-copyright and related or neighboring rights to JWT. This work is
-published from The Netherlands.
-
-https://creativecommons.org/publicdomain/zero/1.0/legalcode
+Copyright © 2012 The Go Authors. All rights reserved.
+Copyright © fsnotify Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice, this
+ list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+* Neither the name of Google Inc. nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
=== yuin/goldmark
; URL & Source
: [[https://github.com/yuin/goldmark]]
@@ -175,5 +127,16 @@
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
+
+=== sx, zettelstore-client
+These are companion projects, written by the current main developer of Zettelstore.
+They are published under the same license, [[EUPL v1.2, or later|00000000000004]].
+
+; URL & Source sx
+: [[https://zettelstore.de/sx]]
+; URL & Source zettelstore-client
+: [[https://zettelstore.de/client/]]
+; License:
+: European Union Public License, version 1.2 (EUPL v1.2), or later.
DELETED box/constbox/error.mustache
Index: box/constbox/error.mustache
==================================================================
--- box/constbox/error.mustache
+++ box/constbox/error.mustache
@@ -1,6 +0,0 @@
-
-
-{{ErrorText}}
-
ADDED box/constbox/error.sxn
Index: box/constbox/error.sxn
==================================================================
--- box/constbox/error.sxn
+++ box/constbox/error.sxn
@@ -0,0 +1,4 @@
+`(article
+ (header (h1 ,heading))
+ ,message
+)
DELETED box/constbox/form.mustache
Index: box/constbox/form.mustache
==================================================================
--- box/constbox/form.mustache
+++ box/constbox/form.mustache
@@ -1,54 +0,0 @@
-
-
-
-
ADDED box/constbox/form.sxn
Index: box/constbox/form.sxn
==================================================================
--- box/constbox/form.sxn
+++ box/constbox/form.sxn
@@ -0,0 +1,38 @@
+`(article
+ (header (h1 ,heading))
+ (form (@ (action ,form-action-url) (method "POST") (enctype "multipart/form-data"))
+ (div
+ (label (@ (for "zs-title")) "Title " (a (@ (title "Main heading of this zettel.")) (@H "ⓘ")))
+ (input (@ (class "zs-input") (type "text") (id "zs-title") (name "title") (placeholder "Title..") (value ,meta-title) (dir "auto") (autofocus))))
+ (div
+ (label (@ (for "zs-role")) "Role " (a (@ (title "One word, without spaces, to set the main role of this zettel.")) (@H "ⓘ")))
+ (input (@ (class "zs-input") (type "text") (id "zs-role") (name "role") (placeholder "role..") (value ,meta-role) (dir "auto")
+ ,@(if role-data '((list "zs-role-data")))
+ ))
+ ,@(wui-datalist "zs-role-data" role-data)
+ )
+ (div
+ (label (@ (for "zs-tags")) "Tags " (a (@ (title "Tags must begin with an '#' sign. They are separated by spaces.")) (@H "ⓘ")))
+ (input (@ (class "zs-input") (type "text") (id "zs-tags") (name "tags") (placeholder "#tag") (value ,meta-tags) (dir "auto"))))
+ (div
+ (label (@ (for "zs-meta")) "Metadata " (a (@ (title "Other metadata for this zettel. Each line contains a key/value pair, separated by a colon ':'.")) (@H "ⓘ")))
+ (textarea (@ (class "zs-input") (id "zs-meta") (name "meta") (rows "4") (placeholder "metakey: metavalue") (dir "auto")) ,meta))
+ (div
+ (label (@ (for "zs-syntax")) "Syntax " (a (@ (title "Syntax of zettel content below, one word. Typically 'zmk' (for zettelmarkup).")) (@H "ⓘ")))
+ (input (@ (class "zs-input") (type "text") (id "zs-syntax") (name "syntax") (placeholder "syntax..") (value ,meta-syntax) (dir "auto")
+ ,@(if syntax-data '((list "zs-syntax-data")))
+ ))
+ ,@(wui-datalist "zs-syntax-data" syntax-data)
+ )
+ ,@(if (bound? 'content)
+ `((div
+ (label (@ (for "zs-content")) "Content " (a (@ (title "Content for this zettel, according to above syntax.")) (@H "ⓘ")))
+ (textarea (@ (class "zs-input zs-content") (id "zs-content") (name "content") (rows "20") (placeholder "Zettel content..") (dir "auto")) ,content)
+ ))
+ )
+ (div
+ (input (@ (class "zs-primary") (type "submit") (value "Submit")))
+ (input (@ (class "zs-secondary") (type "submit") (value "Save") (formaction "?save")))
+ (input (@ (class "zs-upload") (type "file") (id "zs-file") (name "file")))
+ ))
+)
DELETED box/constbox/info.mustache
Index: box/constbox/info.mustache
==================================================================
--- box/constbox/info.mustache
+++ box/constbox/info.mustache
@@ -1,67 +0,0 @@
-
-
-Information for Zettel {{Zid}}
-Web
-· Context
-{{#CanWrite}} · Edit {{/CanWrite}}
-{{#CanFolge}} · Folge {{/CanFolge}}
-{{#CanCopy}} · Copy {{/CanCopy}}
-{{#CanRename}}· Rename {{/CanRename}}
-{{#CanDelete}}· Delete {{/CanDelete}}
-
-Interpreted Metadata
-{{#MetaData}}{{Key}} {{{Value}}} {{/MetaData}}
-References
-{{#HasLocLinks}}
-Local
-
-{{#LocLinks}}
-{{#Valid}}{{Zid}} {{/Valid}}
-{{^Valid}}{{Zid}} {{/Valid}}
-{{/LocLinks}}
-
-{{/HasLocLinks}}
-{{#HasExtLinks}}
-External
-
-{{#ExtLinks}}
-{{.}}
-{{/ExtLinks}}
-
-{{/HasExtLinks}}
-Unlinked
-
-
-Search Phrase
-
-
-Parts and encodings
-
-{{#EvalMatrix}}
-
-{{Header}}
-{{#Elements}}{{Text}}
-{{/Elements}}
-
-{{/EvalMatrix}}
-
-Parsed (not evaluated)
-
-{{#ParseMatrix}}
-
-{{Header}}
-{{#Elements}}{{Text}}
-{{/Elements}}
-
-{{/ParseMatrix}}
-
-{{#HasShadowLinks}}
-Shadowed Boxes
-{{#ShadowLinks}}{{.}} {{/ShadowLinks}}
-{{/HasShadowLinks}}
-{{#Endnotes}}{{{Endnotes}}}{{/Endnotes}}
-
ADDED box/constbox/info.sxn
Index: box/constbox/info.sxn
==================================================================
--- box/constbox/info.sxn
+++ box/constbox/info.sxn
@@ -0,0 +1,33 @@
+`(article
+ (header (h1 "Information for Zettel " ,zid)
+ (p
+ (a (@ (href ,web-url)) "Web")
+ (@H " · ") (a (@ (href ,context-url)) "Context")
+ ,@(if (bound? 'edit-url) `((@H " · ") (a (@ (href ,edit-url)) "Edit")))
+ ,@(ROLE-DEFAULT-actions (current-environment))
+ ,@(if (bound? 'rename-url) `((@H " · ") (a (@ (href ,rename-url)) "Rename")))
+ ,@(if (bound? 'delete-url) `((@H " · ") (a (@ (href ,delete-url)) "Delete")))
+ )
+ )
+ (h2 "Interpreted Metadata")
+ (table ,@(map wui-table-row metadata))
+ (h2 "References")
+ ,@(if local-links `((h3 "Local") (ul ,@(map wui-valid-link local-links))))
+ ,@(if query-links `((h3 "Queries") (ul ,@(map wui-item-link query-links))))
+ ,@(if ext-links `((h3 "External") (ul ,@(map wui-item-popup-link ext-links))))
+ (h3 "Unlinked")
+ ,@unlinked-content
+ (form
+ (label (@ (for "phrase")) "Search Phrase")
+ (input (@ (class "zs-input") (type "text") (id "phrase") (name ,query-key-phrase) (placeholder "Phrase..") (value ,phrase)))
+ )
+ (h2 "Parts and encodings")
+ ,(wui-enc-matrix enc-eval)
+ (h3 "Parsed (not evaluated)")
+ ,(wui-enc-matrix enc-parsed)
+ ,@(if shadow-links
+ `((h2 "Shadowed Boxes")
+ (ul ,@(map wui-item shadow-links))
+ )
+ )
+)
Index: box/constbox/license.txt
==================================================================
--- box/constbox/license.txt
+++ box/constbox/license.txt
@@ -1,6 +1,6 @@
-Copyright (c) 2020-2022 Detlef Stern
+Copyright (c) 2020-present 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
DELETED box/constbox/listroles.mustache
Index: box/constbox/listroles.mustache
==================================================================
--- box/constbox/listroles.mustache
+++ box/constbox/listroles.mustache
@@ -1,8 +0,0 @@
-
-
-
-
DELETED box/constbox/listtags.mustache
Index: box/constbox/listtags.mustache
==================================================================
--- box/constbox/listtags.mustache
+++ box/constbox/listtags.mustache
@@ -1,10 +0,0 @@
-
-
-{{#Tags}} {{Name}} {{Count}}
-{{/Tags}}
-
DELETED box/constbox/listzettel.mustache
Index: box/constbox/listzettel.mustache
==================================================================
--- box/constbox/listzettel.mustache
+++ box/constbox/listzettel.mustache
@@ -1,6 +0,0 @@
-
-
ADDED box/constbox/listzettel.sxn
Index: box/constbox/listzettel.sxn
==================================================================
--- box/constbox/listzettel.sxn
+++ box/constbox/listzettel.sxn
@@ -0,0 +1,22 @@
+`(article
+ (header (h1 ,heading))
+ (form (@ (action ,search-url))
+ (input (@ (class "zs-input") (type "text") (placeholder "Search..") (name ,query-key-query) (value ,query-value) (dir "auto"))))
+ ,@(if (bound? 'tag-zettel)
+ `((p (@ (class "zs-tag-zettel")) "Tag zettel: " ,@tag-zettel))
+ )
+ ,@content
+ ,@endnotes
+ (form (@ (action ,(if (bound? 'create-url) create-url)))
+ "Other encodings: "
+ (a (@ (href ,data-url)) "data")
+ ", "
+ (a (@ (href ,plain-url)) "plain")
+ ,@(if (bound? 'create-url)
+ `((input (@ (type "hidden") (name ,query-key-query) (value ,query-value)))
+ (input (@ (type "hidden") (name ,query-key-seed) (value ,seed)))
+ (input (@ (class "zs-primary") (type "submit") (value "Save As Zettel")))
+ )
+ )
+ )
+)
DELETED box/constbox/login.mustache
Index: box/constbox/login.mustache
==================================================================
--- box/constbox/login.mustache
+++ box/constbox/login.mustache
@@ -1,19 +0,0 @@
-
-
-{{#Retry}}
-Wrong user name / password. Try again.
-{{/Retry}}
-
-
-User name:
-
-
-
-Password:
-
-
-
-
-
ADDED box/constbox/login.sxn
Index: box/constbox/login.sxn
==================================================================
--- box/constbox/login.sxn
+++ box/constbox/login.sxn
@@ -0,0 +1,14 @@
+`(article
+ (header (h1 "Login"))
+ ,@(if retry '((div (@ (class "zs-indication zs-error")) "Wrong user name / password. Try again.")))
+ (form (@ (method "POST") (action ""))
+ (div
+ (label (@ (for "username")) "User name:")
+ (input (@ (class "zs-input") (type "text") (id "username") (name "username") (placeholder "Your user name..") (autofocus))))
+ (div
+ (label (@ (for "password")) "Password:")
+ (input (@ (class "zs-input") (type "password") (id "password") (name "password") (placeholder "Your password.."))))
+ (div
+ (input (@ (class "zs-primary") (type "submit") (value "Login"))))
+ )
+)
DELETED box/constbox/rename.mustache
Index: box/constbox/rename.mustache
==================================================================
--- box/constbox/rename.mustache
+++ box/constbox/rename.mustache
@@ -1,41 +0,0 @@
-
-
-Do you really want to rename this zettel?
-{{#HasIncoming}}
-
-
Warning!
-
If you rename this zettel, incoming references from the following zettel will become invalid.
-
-{{#Incoming}}
-{{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}}.
-
-{{#UselessFiles}}
-{{{.}}}
-{{/UselessFiles}}
-
-
-{{/HasUselessFiles}}
-
-
-New zettel id
-
-
-
-
-
-
-{{#MetaPairs}}
-{{Key}}: {{Value}}
-{{/MetaPairs}}
-
-
ADDED box/constbox/rename.sxn
Index: box/constbox/rename.sxn
==================================================================
--- box/constbox/rename.sxn
+++ box/constbox/rename.sxn
@@ -0,0 +1,26 @@
+`(article
+ (header (h1 "Rename Zettel " ,zid))
+ (p "Do you really want to rename this zettel?")
+ ,@(if incoming
+ `((div (@ (class "zs-warning"))
+ (h2 "Warning!")
+ (p "If you rename this zettel, incoming references from the following zettel will become invalid.")
+ (ul ,@(map wui-item-link incoming))
+ ))
+ )
+ ,@(if (and (bound? 'useless) useless)
+ `((div (@ (class "zs-warning"))
+ (h2 "Warning!")
+ (p "Renaming this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.")
+ (ul ,@(map wui-item useless))
+ ))
+ )
+ (form (@ (method "POST"))
+ (input (@ (type "hidden") (id "curzid") (name "curzid") (value ,zid)))
+ (div
+ (label (@ (for "newzid")) "New zettel id")
+ (input (@ (class "zs-input") (type "text") (id "newzid") (name "newzid") (placeholder "ZID..") (value ,zid) (autofocus))))
+ (div (input (@ (class "zs-primary") (type "submit") (value "Rename"))))
+ )
+ ,(wui-meta-desc metapairs)
+)
ADDED box/constbox/start.sxn
Index: box/constbox/start.sxn
==================================================================
--- box/constbox/start.sxn
+++ box/constbox/start.sxn
@@ -0,0 +1,14 @@
+;;;----------------------------------------------------------------------------
+;;; Copyright (c) 2023-present 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.
+;;;----------------------------------------------------------------------------
+
+;;; This zettel is the start of the loading sequence for Sx code used in the
+;;; Zettelstore. Via the precursor metadata, dependend zettel are evaluated
+;;; before this zettel. You must always depend, directly or indirectly on the
+;;; "Zettelstore Sxn Base Code" zettel. It provides the base definitions.
ADDED box/constbox/wuicode.sxn
Index: box/constbox/wuicode.sxn
==================================================================
--- box/constbox/wuicode.sxn
+++ box/constbox/wuicode.sxn
@@ -0,0 +1,123 @@
+;;;----------------------------------------------------------------------------
+;;; Copyright (c) 2023-present 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.
+;;;----------------------------------------------------------------------------
+
+;; wui-list-item returns the argument as a HTML list item.
+(define (wui-item s) `(li ,s))
+
+;; wui-table-row takes a pair and translates it into a HTML table row with
+;; two columns.
+(define (wui-table-row p)
+ `(tr (td ,(car p)) (td ,(cdr p))))
+
+;; wui-valid-link translates a local link into a HTML link. A link is a pair
+;; (valid . url). If valid is not truish, only the invalid url is returned.
+(define (wui-valid-link l)
+ (if (car l)
+ `(li (a (@ (href ,(cdr l))) ,(cdr l)))
+ `(li ,(cdr l))))
+
+;; wui-link takes a link (title . url) and returns a HTML reference.
+(define (wui-link q)
+ `(a (@ (href ,(cdr q))) ,(car q)))
+
+;; wui-item-link taks a pair (text . url) and returns a HTML link inside
+;; a list item.
+(define (wui-item-link q) `(li ,(wui-link q)))
+
+;; wui-tdata-link taks a pair (text . url) and returns a HTML link inside
+;; a table data item.
+(define (wui-tdata-link q) `(td ,(wui-link q)))
+
+;; wui-item-popup-link is like 'wui-item-link, but the HTML link will open
+;; a new tab / window.
+(define (wui-item-popup-link e)
+ `(li (a (@ (href ,e) (target "_blank") (rel "noopener noreferrer")) ,e)))
+
+;; wui-option-value returns a value for an HTML option element.
+(define (wui-option-value v) `(option (@ (value ,v))))
+
+;; wui-datalist returns a HTML datalist with the given HTML identifier and a
+;; list of values.
+(define (wui-datalist id lst)
+ (if lst
+ `((datalist (@ (id ,id)) ,@(map wui-option-value lst)))))
+
+;; wui-pair-desc-item takes a pair '(term . text) and returns a list with
+;; a HTML description term and a HTML description data.
+(define (wui-pair-desc-item p) `((dt ,(car p)) (dd ,(cdr p))))
+
+;; wui-meta-desc returns a HTML description list made from the list of pairs
+;; given.
+(define (wui-meta-desc l)
+ `(dl ,@(apply append (map wui-pair-desc-item l))))
+
+;; wui-enc-matrix returns the HTML table of all encodings and parts.
+(define (wui-enc-matrix matrix)
+ `(table
+ ,@(map
+ (lambda (row) `(tr (th ,(car row)) ,@(map wui-tdata-link (cdr row))))
+ matrix)))
+
+;; CSS-ROLE-map is a mapping (pair list, assoc list) of role names to zettel
+;; identifier. It is used in the base template to update the metadata of the
+;; HTML page to include some role specific CSS code.
+;; Referenced in function "ROLE-DEFAULT-meta".
+(define CSS-ROLE-map '())
+
+;; ROLE-DEFAULT-meta returns some metadata for the base template. Any role
+;; specific code should include the returned list of this function.
+(define (ROLE-DEFAULT-meta env)
+ `(,@(let (meta-role (environment-lookup 'meta-role env))
+ (let (entry (assoc CSS-ROLE-map meta-role))
+ (if (pair? entry)
+ `((link (@ (rel "stylesheet") (href ,(zid-content-path (cdr entry))))))
+ )
+ )
+ )
+ )
+)
+
+;;; ACTION-SEPARATOR defines a HTML value that separates actions links.
+(define ACTION-SEPARATOR '(@H " · "))
+
+;;; ROLE-DEFAULT-actions returns the default text for actions.
+(define (ROLE-DEFAULT-actions env)
+ `(,@(let (copy-url (environment-lookup 'copy-url env))
+ (if (defined? copy-url) `((@H " · ") (a (@ (href ,copy-url)) "Copy"))))
+ ,@(let (version-url (environment-lookup 'version-url env))
+ (if (defined? version-url) `((@H " · ") (a (@ (href ,version-url)) "Version"))))
+ ,@(let (child-url (environment-lookup 'child-url env))
+ (if (defined? child-url) `((@H " · ") (a (@ (href ,child-url)) "Child"))))
+ ,@(let (folge-url (environment-lookup 'folge-url env))
+ (if (defined? folge-url) `((@H " · ") (a (@ (href ,folge-url)) "Folge"))))
+ )
+)
+
+;;; ROLE-tag-actions returns an additional action "Zettel" for zettel with role "tag".
+(define (ROLE-tag-actions env)
+ `(,@(ROLE-DEFAULT-actions env)
+ ,@(let (title (environment-lookup 'title env))
+ (if (and (defined? title) title)
+ `(,ACTION-SEPARATOR (a (@ (href ,(query->url (string-append "tags:" title)))) "Zettel"))
+ )
+ )
+ )
+)
+
+;;; ROLE-DEFAULT-heading returns the default text for headings, below the
+;;; references of a zettel. In most cases it should be called from an
+;;; overwriting function.
+(define (ROLE-DEFAULT-heading env)
+ `(,@(let (meta-url (environment-lookup 'meta-url env))
+ (if (defined? meta-url) `((br) "URL: " ,(url-to-html meta-url))))
+ ,@(let (meta-author (environment-lookup 'meta-author env))
+ (if (and (defined? meta-author) meta-author) `((br) "By " ,meta-author)))
+ )
+)
DELETED box/constbox/zettel.mustache
Index: box/constbox/zettel.mustache
==================================================================
--- box/constbox/zettel.mustache
+++ box/constbox/zettel.mustache
@@ -1,41 +0,0 @@
-
-
-{{{Content}}}
-
-{{#HasFolgeLinks}}
-
-
-Folgezettel
-
-{{#FolgeLinks}}
-{{Text}}
-{{/FolgeLinks}}
-
-
-
-{{/HasFolgeLinks}}
-{{#HasBackLinks}}
-
-
-Incoming
-
-{{#BackLinks}}
-{{Text}}
-{{/BackLinks}}
-
-
-
-{{/HasBackLinks}}
ADDED box/constbox/zettel.sxn
Index: box/constbox/zettel.sxn
==================================================================
--- box/constbox/zettel.sxn
+++ box/constbox/zettel.sxn
@@ -0,0 +1,30 @@
+`(article
+ (header
+ (h1 ,heading)
+ (div (@ (class "zs-meta"))
+ ,@(if (bound? 'edit-url) `((a (@ (href ,edit-url)) "Edit") (@H " · ")))
+ ,zid (@H " · ")
+ (a (@ (href ,info-url)) "Info") (@H " · ")
+ "(" ,@(if (bound? 'role-url) `((a (@ (href ,role-url)) ,meta-role)))
+ ,@(if (and (bound? 'folge-role-url) (bound? 'meta-folge-role))
+ `((@H " → ") (a (@ (href ,folge-role-url)) ,meta-folge-role)))
+ ")"
+ ,@(if tag-refs `((@H " · ") ,@tag-refs))
+ ,@(ROLE-DEFAULT-actions (current-environment))
+ ,@(if predecessor-refs `((br) "Predecessor: " ,predecessor-refs))
+ ,@(if precursor-refs `((br) "Precursor: " ,precursor-refs))
+ ,@(if superior-refs `((br) "Superior: " ,superior-refs))
+ ,@(ROLE-DEFAULT-heading (current-environment))
+ )
+ )
+ ,@content
+ ,endnotes
+ ,@(if (or folge-links subordinate-links back-links successor-links)
+ `((nav
+ ,@(if folge-links `((details (@ (open)) (summary "Folgezettel") (ul ,@(map wui-item-link folge-links)))))
+ ,@(if subordinate-links `((details (@ (open)) (summary "Subordinates") (ul ,@(map wui-item-link subordinate-links)))))
+ ,@(if back-links `((details (@ (open)) (summary "Incoming") (ul ,@(map wui-item-link back-links)))))
+ ,@(if successor-links `((details (@ (open)) (summary "Successors") (ul ,@(map wui-item-link successor-links)))))
+ ))
+ )
+)
Index: box/dirbox/dirbox.go
==================================================================
--- box/dirbox/dirbox.go
+++ box/dirbox/dirbox.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -20,16 +20,16 @@
"sync"
"zettelstore.de/z/box"
"zettelstore.de/z/box/manager"
"zettelstore.de/z/box/notify"
- "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"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func init() {
manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) {
var log *logger.Logger
@@ -126,10 +126,28 @@
}
func (dp *dirBox) Location() string {
return dp.location
}
+
+func (dp *dirBox) State() box.StartState {
+ if ds := dp.dirSrv; ds != nil {
+ switch ds.State() {
+ case notify.DsCreated:
+ return box.StartStateStopped
+ case notify.DsStarting:
+ return box.StartStateStarting
+ case notify.DsWorking:
+ return box.StartStateStarted
+ case notify.DsMissing:
+ return box.StartStateStarted
+ case notify.DsStopping:
+ return box.StartStateStopping
+ }
+ }
+ return box.StartStateStopped
+}
func (dp *dirBox) Start(context.Context) error {
dp.mxCmds.Lock()
defer dp.mxCmds.Unlock()
dp.fCmds = make([]chan fileCmd, 0, dp.fSrvs)
@@ -151,10 +169,11 @@
dp.log.Fatal().Err(err).Msg("Unable to create directory supervisor")
dp.stopFileServices()
return err
}
dp.dirSrv = notify.NewDirService(
+ dp,
dp.log.Clone().Str("sub", "dirsrv").Child(),
notifier,
dp.cdata.Notify,
)
dp.dirSrv.Start()
@@ -179,14 +198,14 @@
for _, c := range dp.fCmds {
close(c)
}
}
-func (dp *dirBox) notifyChanged(reason box.UpdateReason, zid id.Zid) {
+func (dp *dirBox) notifyChanged(zid id.Zid) {
if chci := dp.cdata.Notify; chci != nil {
- dp.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChanged")
- chci <- box.UpdateInfo{Reason: reason, Zid: zid}
+ dp.log.Trace().Zid(zid).Msg("notifyChanged")
+ chci <- box.UpdateInfo{Reason: box.OnZettel, Zid: zid}
}
}
func (dp *dirBox) getFileChan(zid id.Zid) chan fileCmd {
// Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
@@ -202,11 +221,11 @@
func (dp *dirBox) CanCreateZettel(_ context.Context) bool {
return !dp.readonly
}
-func (dp *dirBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
+func (dp *dirBox) CreateZettel(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) {
if dp.readonly {
return id.Invalid, box.ErrReadOnly
}
newZid, err := dp.dirSrv.SetNewDirEntry()
@@ -220,56 +239,43 @@
err = dp.srvSetZettel(ctx, &entry, zettel)
if err == nil {
err = dp.dirSrv.UpdateDirEntry(&entry)
}
- dp.notifyChanged(box.OnUpdate, meta.Zid)
+ dp.notifyChanged(meta.Zid)
dp.log.Trace().Err(err).Zid(meta.Zid).Msg("CreateZettel")
return meta.Zid, err
}
-func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
+func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) {
entry := dp.dirSrv.GetDirEntry(zid)
if !entry.IsValid() {
- return domain.Zettel{}, box.ErrNotFound
+ return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid}
}
m, c, err := dp.srvGetMetaContent(ctx, entry, zid)
if err != nil {
- return domain.Zettel{}, err
+ return zettel.Zettel{}, err
}
- zettel := domain.Zettel{Meta: m, Content: domain.NewContent(c)}
+ zettel := zettel.Zettel{Meta: m, Content: zettel.NewContent(c)}
dp.log.Trace().Zid(zid).Msg("GetZettel")
return zettel, nil
}
-func (dp *dirBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
- m, err := dp.doGetMeta(ctx, zid)
- dp.log.Trace().Zid(zid).Err(err).Msg("GetMeta")
- return m, err
-}
-func (dp *dirBox) doGetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
- entry := dp.dirSrv.GetDirEntry(zid)
- if !entry.IsValid() {
- return nil, box.ErrNotFound
- }
- m, err := dp.srvGetMeta(ctx, entry, zid)
- if err != nil {
- return nil, err
- }
- return m, nil
-}
-
-func (dp *dirBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.RetrievePredicate) error {
+func (dp *dirBox) HasZettel(_ context.Context, zid id.Zid) bool {
+ return dp.dirSrv.GetDirEntry(zid).IsValid()
+}
+
+func (dp *dirBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error {
entries := dp.dirSrv.GetDirEntries(constraint)
dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid")
for _, entry := range entries {
handle(entry.Zid)
}
return nil
}
-func (dp *dirBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error {
+func (dp *dirBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error {
entries := dp.dirSrv.GetDirEntries(constraint)
dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyMeta")
// The following loop could be parallelized if needed for performance.
for _, entry := range entries {
@@ -282,23 +288,23 @@
handle(m)
}
return nil
}
-func (dp *dirBox) CanUpdateZettel(context.Context, domain.Zettel) bool {
+func (dp *dirBox) CanUpdateZettel(context.Context, zettel.Zettel) bool {
return !dp.readonly
}
-func (dp *dirBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
+func (dp *dirBox) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error {
if dp.readonly {
return box.ErrReadOnly
}
meta := zettel.Meta
zid := meta.Zid
if !zid.IsValid() {
- return &box.ErrInvalidID{Zid: zid}
+ return box.ErrInvalidZid{Zid: zid.String()}
}
entry := dp.dirSrv.GetDirEntry(zid)
if !entry.IsValid() {
// Existing zettel, but new in this box.
entry = ¬ify.DirEntry{Zid: zid}
@@ -305,17 +311,17 @@
}
dp.updateEntryFromMetaContent(entry, meta, zettel.Content)
dp.dirSrv.UpdateDirEntry(entry)
err := dp.srvSetZettel(ctx, entry, zettel)
if err == nil {
- dp.notifyChanged(box.OnUpdate, zid)
+ dp.notifyChanged(zid)
}
dp.log.Trace().Zid(zid).Err(err).Msg("UpdateZettel")
return err
}
-func (dp *dirBox) updateEntryFromMetaContent(entry *notify.DirEntry, m *meta.Meta, content domain.Content) {
+func (dp *dirBox) updateEntryFromMetaContent(entry *notify.DirEntry, m *meta.Meta, content zettel.Content) {
entry.SetupFromMetaContent(m, content, dp.cdata.Config.GetZettelFileSyntax)
}
func (dp *dirBox) AllowRenameZettel(context.Context, id.Zid) bool {
return !dp.readonly
@@ -325,19 +331,19 @@
if curZid == newZid {
return nil
}
curEntry := dp.dirSrv.GetDirEntry(curZid)
if !curEntry.IsValid() {
- return box.ErrNotFound
+ return box.ErrZettelNotFound{Zid: curZid}
}
if dp.readonly {
return box.ErrReadOnly
}
// Check whether zettel with new ID already exists in this box.
- if _, err := dp.doGetMeta(ctx, newZid); err == nil {
- return &box.ErrInvalidID{Zid: newZid}
+ if dp.HasZettel(ctx, newZid) {
+ return box.ErrInvalidZid{Zid: newZid.String()}
}
oldMeta, oldContent, err := dp.srvGetMetaContent(ctx, curEntry, curZid)
if err != nil {
return err
@@ -346,20 +352,20 @@
newEntry, err := dp.dirSrv.RenameDirEntry(curEntry, newZid)
if err != nil {
return err
}
oldMeta.Zid = newZid
- newZettel := domain.Zettel{Meta: oldMeta, Content: domain.NewContent(oldContent)}
+ newZettel := zettel.Zettel{Meta: oldMeta, Content: zettel.NewContent(oldContent)}
if err = dp.srvSetZettel(ctx, &newEntry, newZettel); err != nil {
// "Rollback" rename. No error checking...
dp.dirSrv.RenameDirEntry(&newEntry, curZid)
return err
}
err = dp.srvDeleteZettel(ctx, curEntry, curZid)
if err == nil {
- dp.notifyChanged(box.OnDelete, curZid)
- dp.notifyChanged(box.OnUpdate, newZid)
+ dp.notifyChanged(curZid)
+ dp.notifyChanged(newZid)
}
dp.log.Trace().Zid(curZid).Zid(newZid).Err(err).Msg("RenameZettel")
return err
}
@@ -376,19 +382,19 @@
return box.ErrReadOnly
}
entry := dp.dirSrv.GetDirEntry(zid)
if !entry.IsValid() {
- return box.ErrNotFound
+ return box.ErrZettelNotFound{Zid: zid}
}
err := dp.dirSrv.DeleteDirEntry(zid)
if err != nil {
return nil
}
err = dp.srvDeleteZettel(ctx, entry, zid)
if err == nil {
- dp.notifyChanged(box.OnDelete, zid)
+ dp.notifyChanged(zid)
}
dp.log.Trace().Zid(zid).Err(err).Msg("DeleteZettel")
return err
}
Index: box/dirbox/dirbox_test.go
==================================================================
--- box/dirbox/dirbox_test.go
+++ box/dirbox/dirbox_test.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
Index: box/dirbox/service.go
==================================================================
--- box/dirbox/service.go
+++ box/dirbox/service.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -17,23 +17,23 @@
"path/filepath"
"time"
"zettelstore.de/z/box/filebox"
"zettelstore.de/z/box/notify"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/input"
"zettelstore.de/z/kernel"
"zettelstore.de/z/logger"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func fileService(i uint32, log *logger.Logger, dirPath string, cmds <-chan fileCmd) {
// Something may panic. Ensure a running service.
defer func() {
- if r := recover(); r != nil {
- kernel.Main.LogRecover("FileService", r)
+ if ri := recover(); ri != nil {
+ kernel.Main.LogRecover("FileService", ri)
go fileService(i, log, dirPath, cmds)
}
}()
log.Trace().Uint("i", uint64(i)).Str("dirpath", dirPath).Msg("File service started")
@@ -166,11 +166,11 @@
// COMMAND: srvSetZettel ----------------------------------------
//
// Writes a new or exsting zettel.
-func (dp *dirBox) srvSetZettel(ctx context.Context, entry *notify.DirEntry, zettel domain.Zettel) error {
+func (dp *dirBox) srvSetZettel(ctx context.Context, entry *notify.DirEntry, zettel zettel.Zettel) error {
rc := make(chan resSetZettel, 1)
dp.getFileChan(zettel.Meta.Zid) <- &fileSetZettel{entry, zettel, rc}
ctx, cancel := context.WithTimeout(ctx, serviceTimeout)
defer cancel()
select {
@@ -181,11 +181,11 @@
}
}
type fileSetZettel struct {
entry *notify.DirEntry
- zettel domain.Zettel
+ zettel zettel.Zettel
rc chan<- resSetZettel
}
type resSetZettel = error
func (cmd *fileSetZettel) run(log *logger.Logger, dirPath string) {
Index: box/filebox/filebox.go
==================================================================
--- box/filebox/filebox.go
+++ box/filebox/filebox.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -15,16 +15,16 @@
"errors"
"net/url"
"path/filepath"
"strings"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/box"
"zettelstore.de/z/box/manager"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/kernel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func init() {
manager.Register("file", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) {
path := getFilepathFromURL(u)
Index: box/filebox/zipbox.go
==================================================================
--- box/filebox/zipbox.go
+++ box/filebox/zipbox.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -16,16 +16,16 @@
"io"
"strings"
"zettelstore.de/z/box"
"zettelstore.de/z/box/notify"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/input"
"zettelstore.de/z/logger"
- "zettelstore.de/z/search"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
type zipBox struct {
log *logger.Logger
number int
@@ -39,22 +39,37 @@
if strings.HasPrefix(zb.name, "/") {
return "file://" + zb.name
}
return "file:" + zb.name
}
+
+func (zb *zipBox) State() box.StartState {
+ if ds := zb.dirSrv; ds != nil {
+ switch ds.State() {
+ case notify.DsCreated:
+ return box.StartStateStopped
+ case notify.DsStarting:
+ return box.StartStateStarting
+ case notify.DsWorking:
+ return box.StartStateStarted
+ case notify.DsMissing:
+ return box.StartStateStarted
+ case notify.DsStopping:
+ return box.StartStateStopping
+ }
+ }
+ return box.StartStateStopped
+}
func (zb *zipBox) Start(context.Context) error {
reader, err := zip.OpenReader(zb.name)
if err != nil {
return err
}
reader.Close()
- zipNotifier, err := notify.NewSimpleZipNotifier(zb.log, zb.name)
- if err != nil {
- return err
- }
- zb.dirSrv = notify.NewDirService(zb.log, zipNotifier, zb.notify)
+ zipNotifier := notify.NewSimpleZipNotifier(zb.log, zb.name)
+ zb.dirSrv = notify.NewDirService(zb, zb.log, zipNotifier, zb.notify)
zb.dirSrv.Start()
return nil
}
func (zb *zipBox) Refresh(_ context.Context) {
@@ -62,28 +77,29 @@
zb.log.Trace().Msg("Refresh")
}
func (zb *zipBox) Stop(context.Context) {
zb.dirSrv.Stop()
+ zb.dirSrv = nil
}
func (*zipBox) CanCreateZettel(context.Context) bool { return false }
-func (zb *zipBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) {
+func (zb *zipBox) CreateZettel(context.Context, zettel.Zettel) (id.Zid, error) {
err := box.ErrReadOnly
zb.log.Trace().Err(err).Msg("CreateZettel")
return id.Invalid, err
}
-func (zb *zipBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
+func (zb *zipBox) GetZettel(_ context.Context, zid id.Zid) (zettel.Zettel, error) {
entry := zb.dirSrv.GetDirEntry(zid)
if !entry.IsValid() {
- return domain.Zettel{}, box.ErrNotFound
+ return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid}
}
reader, err := zip.OpenReader(zb.name)
if err != nil {
- return domain.Zettel{}, err
+ return zettel.Zettel{}, err
}
defer reader.Close()
var m *meta.Meta
var src []byte
@@ -94,11 +110,11 @@
if contentName == "" {
zb.log.Panic().Zid(zid).Msg("No meta, no content in zipBox.GetZettel")
}
src, err = readZipFileContent(reader, entry.ContentName)
if err != nil {
- return domain.Zettel{}, err
+ return zettel.Zettel{}, err
}
if entry.HasMetaInContent() {
inp := input.NewInput(src)
m = meta.NewFromInput(zid, inp)
src = src[inp.Pos:]
@@ -106,51 +122,40 @@
m = CalcDefaultMeta(zid, entry.ContentExt)
}
} else {
m, err = readZipMetaFile(reader, zid, metaName)
if err != nil {
- return domain.Zettel{}, err
+ return zettel.Zettel{}, err
}
inMeta = true
if contentName != "" {
src, err = readZipFileContent(reader, entry.ContentName)
if err != nil {
- return domain.Zettel{}, err
+ return zettel.Zettel{}, err
}
}
}
CleanupMeta(m, zid, entry.ContentExt, inMeta, entry.UselessFiles)
zb.log.Trace().Zid(zid).Msg("GetZettel")
- return domain.Zettel{Meta: m, Content: domain.NewContent(src)}, nil
-}
-
-func (zb *zipBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
- entry := zb.dirSrv.GetDirEntry(zid)
- if !entry.IsValid() {
- return nil, box.ErrNotFound
- }
- reader, err := zip.OpenReader(zb.name)
- if err != nil {
- return nil, err
- }
- defer reader.Close()
- m, err := zb.readZipMeta(reader, zid, entry)
- zb.log.Trace().Err(err).Zid(zid).Msg("GetMeta")
- return m, err
-}
-
-func (zb *zipBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.RetrievePredicate) error {
+ return zettel.Zettel{Meta: m, Content: zettel.NewContent(src)}, nil
+}
+
+func (zb *zipBox) HasZettel(_ context.Context, zid id.Zid) bool {
+ return zb.dirSrv.GetDirEntry(zid).IsValid()
+}
+
+func (zb *zipBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error {
entries := zb.dirSrv.GetDirEntries(constraint)
zb.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid")
for _, entry := range entries {
handle(entry.Zid)
}
return nil
}
-func (zb *zipBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error {
+func (zb *zipBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error {
reader, err := zip.OpenReader(zb.name)
if err != nil {
return err
}
defer reader.Close()
@@ -168,13 +173,13 @@
handle(m)
}
return nil
}
-func (*zipBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false }
+func (*zipBox) CanUpdateZettel(context.Context, zettel.Zettel) bool { return false }
-func (zb *zipBox) UpdateZettel(context.Context, domain.Zettel) error {
+func (zb *zipBox) UpdateZettel(context.Context, zettel.Zettel) error {
err := box.ErrReadOnly
zb.log.Trace().Err(err).Msg("UpdateZettel")
return err
}
@@ -188,11 +193,11 @@
if curZid == newZid {
err = nil
}
curEntry := zb.dirSrv.GetDirEntry(curZid)
if !curEntry.IsValid() {
- err = box.ErrNotFound
+ err = box.ErrZettelNotFound{Zid: curZid}
}
zb.log.Trace().Err(err).Msg("RenameZettel")
return err
}
@@ -200,11 +205,11 @@
func (zb *zipBox) DeleteZettel(_ context.Context, zid id.Zid) error {
err := box.ErrReadOnly
entry := zb.dirSrv.GetDirEntry(zid)
if !entry.IsValid() {
- err = box.ErrNotFound
+ err = box.ErrZettelNotFound{Zid: zid}
}
zb.log.Trace().Err(err).Msg("DeleteZettel")
return err
}
Index: box/helper.go
==================================================================
--- box/helper.go
+++ box/helper.go
@@ -1,21 +1,23 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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 box
import (
+ "net/url"
+ "strconv"
"time"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/zettel/id"
)
// GetNewZid calculates a new and unused zettel identifier, based on the current date and time.
func GetNewZid(testZid func(id.Zid) (bool, error)) (id.Zid, error) {
withSeconds := false
@@ -32,5 +34,30 @@
time.Sleep(100 * time.Millisecond)
withSeconds = true
}
return id.Invalid, ErrConflict
}
+
+// GetQueryBool is a helper function to extract bool values from a box URI.
+func GetQueryBool(u *url.URL, key string) bool {
+ _, ok := u.Query()[key]
+ return ok
+}
+
+// GetQueryInt is a helper function to extract int values of a specified range from a box URI.
+func GetQueryInt(u *url.URL, key string, min, def, max int) int {
+ sVal := u.Query().Get(key)
+ if sVal == "" {
+ return def
+ }
+ iVal, err := strconv.Atoi(sVal)
+ if err != nil {
+ return def
+ }
+ if iVal < min {
+ return min
+ }
+ if iVal > max {
+ return max
+ }
+ return iVal
+}
Index: box/manager/anteroom.go
==================================================================
--- box/manager/anteroom.go
+++ box/manager/anteroom.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -11,108 +11,97 @@
package manager
import (
"sync"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/zettel/id"
)
type arAction int
const (
arNothing arAction = iota
arReload
- arUpdate
- arDelete
+ arZettel
)
type anteroom struct {
num uint64
next *anteroom
- waiting map[id.Zid]arAction
+ waiting id.Set
curLoad int
reload bool
}
-type anterooms struct {
+type anteroomQueue struct {
mx sync.Mutex
nextNum uint64
first *anteroom
last *anteroom
maxLoad int
}
-func newAnterooms(maxLoad int) *anterooms {
- return &anterooms{maxLoad: maxLoad}
-}
+func newAnteroomQueue(maxLoad int) *anteroomQueue { return &anteroomQueue{maxLoad: maxLoad} }
-func (ar *anterooms) Enqueue(zid id.Zid, action arAction) {
- if !zid.IsValid() || action == arNothing || action == arReload {
+func (ar *anteroomQueue) EnqueueZettel(zid id.Zid) {
+ if !zid.IsValid() {
return
}
ar.mx.Lock()
defer ar.mx.Unlock()
if ar.first == nil {
- ar.first = ar.makeAnteroom(zid, action)
+ ar.first = ar.makeAnteroom(zid)
ar.last = ar.first
return
}
for room := ar.first; room != nil; room = room.next {
if room.reload {
continue // Do not put zettel in reload room
}
- a, ok := room.waiting[zid]
- if !ok {
- continue
- }
- switch action {
- case a:
- return
- case arUpdate:
- room.waiting[zid] = action
- case arDelete:
- room.waiting[zid] = action
- }
- return
+ if _, ok := room.waiting[zid]; ok {
+ // Zettel is already waiting. Nothing to do.
+ return
+ }
}
if room := ar.last; !room.reload && (ar.maxLoad == 0 || room.curLoad < ar.maxLoad) {
- room.waiting[zid] = action
+ room.waiting.Add(zid)
room.curLoad++
return
}
- room := ar.makeAnteroom(zid, action)
+ room := ar.makeAnteroom(zid)
ar.last.next = room
ar.last = room
}
-func (ar *anterooms) makeAnteroom(zid id.Zid, action arAction) *anteroom {
+func (ar *anteroomQueue) makeAnteroom(zid id.Zid) *anteroom {
+ ar.nextNum++
+ if zid == id.Invalid {
+ return &anteroom{num: ar.nextNum, next: nil, waiting: nil, curLoad: 0, reload: true}
+ }
c := ar.maxLoad
if c == 0 {
c = 100
}
- waiting := make(map[id.Zid]arAction, c)
- waiting[zid] = action
- ar.nextNum++
+ waiting := id.NewSetCap(ar.maxLoad, zid)
return &anteroom{num: ar.nextNum, next: nil, waiting: waiting, curLoad: 1, reload: false}
}
-func (ar *anterooms) Reset() {
+func (ar *anteroomQueue) Reset() {
ar.mx.Lock()
defer ar.mx.Unlock()
- ar.first = ar.makeAnteroom(id.Invalid, arReload)
+ ar.first = ar.makeAnteroom(id.Invalid)
ar.last = ar.first
}
-func (ar *anterooms) Reload(newZids id.Set) uint64 {
+func (ar *anteroomQueue) Reload(allZids id.Set) uint64 {
ar.mx.Lock()
defer ar.mx.Unlock()
- newWaiting := createWaitingSet(newZids, arUpdate)
ar.deleteReloadedRooms()
- if ns := len(newWaiting); ns > 0 {
+ if ns := len(allZids); ns > 0 {
ar.nextNum++
- ar.first = &anteroom{num: ar.nextNum, next: ar.first, waiting: newWaiting, curLoad: ns}
+ ar.first = &anteroom{num: ar.nextNum, next: ar.first, waiting: allZids, curLoad: ns, reload: true}
if ar.first.next == nil {
ar.last = ar.first
}
return ar.nextNum
}
@@ -120,21 +109,11 @@
ar.first = nil
ar.last = nil
return 0
}
-func createWaitingSet(zids id.Set, action arAction) map[id.Zid]arAction {
- waitingSet := make(map[id.Zid]arAction, len(zids))
- for zid := range zids {
- if zid.IsValid() {
- waitingSet[zid] = action
- }
- }
- return waitingSet
-}
-
-func (ar *anterooms) deleteReloadedRooms() {
+func (ar *anteroomQueue) deleteReloadedRooms() {
room := ar.first
for room != nil && room.reload {
room = room.next
}
ar.first = room
@@ -141,24 +120,34 @@
if room == nil {
ar.last = nil
}
}
-func (ar *anterooms) Dequeue() (arAction, id.Zid, uint64) {
- ar.mx.Lock()
- defer ar.mx.Unlock()
- if ar.first == nil {
- return arNothing, id.Invalid, 0
- }
- for zid, action := range ar.first.waiting {
- roomNo := ar.first.num
- delete(ar.first.waiting, zid)
- if len(ar.first.waiting) == 0 {
- ar.first = ar.first.next
- if ar.first == nil {
- ar.last = nil
- }
- }
- return action, zid, roomNo
- }
- return arNothing, id.Invalid, 0
+func (ar *anteroomQueue) Dequeue() (arAction, id.Zid, uint64) {
+ ar.mx.Lock()
+ defer ar.mx.Unlock()
+ first := ar.first
+ if first == nil {
+ return arNothing, id.Invalid, 0
+ }
+ roomNo := first.num
+ if first.waiting == nil && first.reload {
+ ar.removeFirst()
+ return arReload, id.Invalid, roomNo
+ }
+ for zid := range first.waiting {
+ delete(first.waiting, zid)
+ if len(first.waiting) == 0 {
+ ar.removeFirst()
+ }
+ return arZettel, zid, roomNo
+ }
+ ar.removeFirst()
+ return arNothing, id.Invalid, 0
+}
+
+func (ar *anteroomQueue) removeFirst() {
+ ar.first = ar.first.next
+ if ar.first == nil {
+ ar.last = nil
+ }
}
Index: box/manager/anteroom_test.go
==================================================================
--- box/manager/anteroom_test.go
+++ box/manager/anteroom_test.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -11,31 +11,31 @@
package manager
import (
"testing"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/zettel/id"
)
func TestSimple(t *testing.T) {
t.Parallel()
- ar := newAnterooms(2)
- ar.Enqueue(id.Zid(1), arUpdate)
+ ar := newAnteroomQueue(2)
+ ar.EnqueueZettel(id.Zid(1))
action, zid, rno := ar.Dequeue()
- if zid != id.Zid(1) || action != arUpdate || rno != 1 {
- t.Errorf("Expected arUpdate/1/1, but got %v/%v/%v", action, zid, rno)
+ if zid != id.Zid(1) || action != arZettel || rno != 1 {
+ t.Errorf("Expected arZettel/1/1, but got %v/%v/%v", action, zid, rno)
}
- action, zid, _ = ar.Dequeue()
- if zid != id.Invalid && action != arDelete {
+ _, zid, _ = ar.Dequeue()
+ if zid != id.Invalid {
t.Errorf("Expected invalid Zid, but got %v", zid)
}
- ar.Enqueue(id.Zid(1), arUpdate)
- ar.Enqueue(id.Zid(2), arUpdate)
+ ar.EnqueueZettel(id.Zid(1))
+ ar.EnqueueZettel(id.Zid(2))
if ar.first != ar.last {
t.Errorf("Expected one room, but got more")
}
- ar.Enqueue(id.Zid(3), arUpdate)
+ ar.EnqueueZettel(id.Zid(3))
if ar.first == ar.last {
t.Errorf("Expected more than one room, but got only one")
}
count := 0
@@ -50,59 +50,57 @@
}
}
func TestReset(t *testing.T) {
t.Parallel()
- ar := newAnterooms(1)
- ar.Enqueue(id.Zid(1), arUpdate)
+ ar := newAnteroomQueue(1)
+ ar.EnqueueZettel(id.Zid(1))
ar.Reset()
action, zid, _ := ar.Dequeue()
if action != arReload || zid != id.Invalid {
t.Errorf("Expected reload & invalid Zid, but got %v/%v", action, zid)
}
ar.Reload(id.NewSet(3, 4))
- ar.Enqueue(id.Zid(5), arUpdate)
- ar.Enqueue(id.Zid(5), arDelete)
- ar.Enqueue(id.Zid(5), arDelete)
- ar.Enqueue(id.Zid(5), arUpdate)
+ ar.EnqueueZettel(id.Zid(5))
+ ar.EnqueueZettel(id.Zid(5))
if ar.first == ar.last || ar.first.next != ar.last /*|| ar.first.next.next != ar.last*/ {
t.Errorf("Expected 2 rooms")
}
action, zid1, _ := ar.Dequeue()
- if action != arUpdate {
- t.Errorf("Expected arUpdate, but got %v", action)
+ if action != arZettel {
+ t.Errorf("Expected arZettel, but got %v", action)
}
action, zid2, _ := ar.Dequeue()
- if action != arUpdate {
- t.Errorf("Expected arUpdate, but got %v", action)
+ if action != arZettel {
+ t.Errorf("Expected arZettel, but got %v", action)
}
if !(zid1 == id.Zid(3) && zid2 == id.Zid(4) || zid1 == id.Zid(4) && zid2 == id.Zid(3)) {
t.Errorf("Zids must be 3 or 4, but got %v/%v", zid1, zid2)
}
action, zid, _ = ar.Dequeue()
- if zid != id.Zid(5) || action != arUpdate {
- t.Errorf("Expected 5/arUpdate, but got %v/%v", zid, action)
+ if zid != id.Zid(5) || action != arZettel {
+ t.Errorf("Expected 5/arZettel, but got %v/%v", zid, action)
+ }
+ action, zid, _ = ar.Dequeue()
+ if action != arNothing || zid != id.Invalid {
+ t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid)
+ }
+
+ ar = newAnteroomQueue(1)
+ ar.Reload(id.NewSet(id.Zid(6)))
+ action, zid, _ = ar.Dequeue()
+ if zid != id.Zid(6) || action != arZettel {
+ t.Errorf("Expected 6/arZettel, but got %v/%v", zid, action)
}
action, zid, _ = ar.Dequeue()
if action != arNothing || zid != id.Invalid {
t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid)
}
- ar = newAnterooms(1)
- ar.Reload(id.NewSet(id.Zid(6)))
- action, zid, _ = ar.Dequeue()
- if zid != id.Zid(6) || action != arUpdate {
- t.Errorf("Expected 6/arUpdate, but got %v/%v", zid, action)
- }
- action, zid, _ = ar.Dequeue()
- if action != arNothing || zid != id.Invalid {
- t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid)
- }
-
- ar = newAnterooms(1)
- ar.Enqueue(id.Zid(8), arUpdate)
+ ar = newAnteroomQueue(1)
+ ar.EnqueueZettel(id.Zid(8))
ar.Reload(nil)
action, zid, _ = ar.Dequeue()
if action != arNothing || zid != id.Invalid {
t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid)
}
}
Index: box/manager/box.go
==================================================================
--- box/manager/box.go
+++ box/manager/box.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -9,211 +9,205 @@
//-----------------------------------------------------------------------------
package manager
import (
- "bytes"
"context"
"errors"
+ "strings"
"zettelstore.de/z/box"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
- "zettelstore.de/z/search"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// Conatains all box.Box related functions
// Location returns some information where the box is located.
func (mgr *Manager) Location() string {
if len(mgr.boxes) <= 2 {
return "NONE"
}
- var buf bytes.Buffer
+ var sb strings.Builder
for i := 0; i < len(mgr.boxes)-2; i++ {
if i > 0 {
- buf.WriteString(", ")
+ sb.WriteString(", ")
}
- buf.WriteString(mgr.boxes[i].Location())
+ sb.WriteString(mgr.boxes[i].Location())
}
- return buf.String()
+ return sb.String()
}
// CanCreateZettel returns true, if box could possibly create a new zettel.
func (mgr *Manager) CanCreateZettel(ctx context.Context) bool {
mgr.mgrMx.RLock()
defer mgr.mgrMx.RUnlock()
- return mgr.started && mgr.boxes[0].CanCreateZettel(ctx)
+ return mgr.State() == box.StartStateStarted && mgr.boxes[0].CanCreateZettel(ctx)
}
// CreateZettel creates a new zettel.
-func (mgr *Manager) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
+func (mgr *Manager) CreateZettel(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) {
mgr.mgrLog.Debug().Msg("CreateZettel")
- mgr.mgrMx.RLock()
- defer mgr.mgrMx.RUnlock()
- if !mgr.started {
+ if mgr.State() != box.StartStateStarted {
return id.Invalid, box.ErrStopped
}
+ mgr.mgrMx.RLock()
+ defer mgr.mgrMx.RUnlock()
return mgr.boxes[0].CreateZettel(ctx, zettel)
}
// GetZettel retrieves a specific zettel.
-func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
+func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) {
mgr.mgrLog.Debug().Zid(zid).Msg("GetZettel")
+ if mgr.State() != box.StartStateStarted {
+ return zettel.Zettel{}, box.ErrStopped
+ }
mgr.mgrMx.RLock()
defer mgr.mgrMx.RUnlock()
- if !mgr.started {
- return domain.Zettel{}, box.ErrStopped
- }
for i, p := range mgr.boxes {
- if z, err := p.GetZettel(ctx, zid); err != box.ErrNotFound {
+ var errZNF box.ErrZettelNotFound
+ if z, err := p.GetZettel(ctx, zid); !errors.As(err, &errZNF) {
if err == nil {
mgr.Enrich(ctx, z.Meta, i+1)
}
return z, err
}
}
- return domain.Zettel{}, box.ErrNotFound
+ return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid}
}
// GetAllZettel retrieves a specific zettel from all managed boxes.
-func (mgr *Manager) GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) {
+func (mgr *Manager) GetAllZettel(ctx context.Context, zid id.Zid) ([]zettel.Zettel, error) {
mgr.mgrLog.Debug().Zid(zid).Msg("GetAllZettel")
- mgr.mgrMx.RLock()
- defer mgr.mgrMx.RUnlock()
- if !mgr.started {
+ if mgr.State() != box.StartStateStarted {
return nil, box.ErrStopped
}
- var result []domain.Zettel
+ mgr.mgrMx.RLock()
+ defer mgr.mgrMx.RUnlock()
+ var result []zettel.Zettel
for i, p := range mgr.boxes {
if z, err := p.GetZettel(ctx, zid); err == nil {
mgr.Enrich(ctx, z.Meta, i+1)
result = append(result, z)
}
}
return result, nil
}
-// GetMeta retrieves just the meta data of a specific zettel.
-func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
- mgr.mgrLog.Debug().Zid(zid).Msg("GetMeta")
- mgr.mgrMx.RLock()
- defer mgr.mgrMx.RUnlock()
- if !mgr.started {
- return nil, box.ErrStopped
- }
- return mgr.doGetMeta(ctx, zid)
-}
-
-func (mgr *Manager) doGetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
- for i, p := range mgr.boxes {
- if m, err := p.GetMeta(ctx, zid); err != box.ErrNotFound {
- if err == nil {
- mgr.Enrich(ctx, m, i+1)
- }
- return m, err
- }
- }
- return nil, box.ErrNotFound
-}
-
-// GetAllMeta retrieves the meta data of a specific zettel from all managed boxes.
-func (mgr *Manager) GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) {
- mgr.mgrLog.Debug().Zid(zid).Msg("GetAllMeta")
- mgr.mgrMx.RLock()
- defer mgr.mgrMx.RUnlock()
- if !mgr.started {
- return nil, box.ErrStopped
- }
- var result []*meta.Meta
- for i, p := range mgr.boxes {
- if m, err := p.GetMeta(ctx, zid); err == nil {
- mgr.Enrich(ctx, m, i+1)
- result = append(result, m)
- }
- }
- return result, nil
-}
-
// FetchZids returns the set of all zettel identifer managed by the box.
func (mgr *Manager) FetchZids(ctx context.Context) (id.Set, error) {
mgr.mgrLog.Debug().Msg("FetchZids")
- mgr.mgrMx.RLock()
- defer mgr.mgrMx.RUnlock()
- if !mgr.started {
+ if mgr.State() != box.StartStateStarted {
return nil, box.ErrStopped
}
result := id.Set{}
+ mgr.mgrMx.RLock()
+ defer mgr.mgrMx.RUnlock()
for _, p := range mgr.boxes {
- err := p.ApplyZid(ctx, func(zid id.Zid) { result.Zid(zid) }, func(id.Zid) bool { return true })
+ err := p.ApplyZid(ctx, func(zid id.Zid) { result.Add(zid) }, func(id.Zid) bool { return true })
if err != nil {
return nil, err
}
}
return result, nil
}
-type metaMap map[id.Zid]*meta.Meta
+func (mgr *Manager) HasZettel(ctx context.Context, zid id.Zid) bool {
+ mgr.mgrLog.Debug().Zid(zid).Msg("HasZettel")
+ if mgr.State() != box.StartStateStarted {
+ return false
+ }
+ mgr.mgrMx.RLock()
+ defer mgr.mgrMx.RUnlock()
+ for _, bx := range mgr.boxes {
+ if bx.HasZettel(ctx, zid) {
+ return true
+ }
+ }
+ return false
+}
+
+func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
+ mgr.mgrLog.Debug().Zid(zid).Msg("GetMeta")
+ if mgr.State() != box.StartStateStarted {
+ return nil, box.ErrStopped
+ }
+
+ m, err := mgr.idxStore.GetMeta(ctx, zid)
+ if err != nil {
+ return nil, err
+ }
+ mgr.Enrich(ctx, m, 0)
+ return m, nil
+}
// SelectMeta returns all zettel meta data that match the selection
// criteria. The result is ordered by descending zettel id.
-func (mgr *Manager) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) {
+func (mgr *Manager) SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *query.Query) ([]*meta.Meta, error) {
if msg := mgr.mgrLog.Debug(); msg.Enabled() {
- msg.Str("query", s.String()).Msg("SelectMeta")
+ msg.Str("query", q.String()).Msg("SelectMeta")
+ }
+ if mgr.State() != box.StartStateStarted {
+ return nil, box.ErrStopped
}
mgr.mgrMx.RLock()
defer mgr.mgrMx.RUnlock()
- if !mgr.started {
- return nil, box.ErrStopped
- }
-
- searchPred, match := s.RetrieveAndCompileMatch(mgr)
- selected, rejected := metaMap{}, id.Set{}
- handleMeta := func(m *meta.Meta) {
- zid := m.Zid
- if rejected.Contains(zid) {
- mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadyRejected")
- return
- }
- if _, ok := selected[zid]; ok {
- mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadySelected")
- return
- }
- if match(m) {
- selected[zid] = m
- mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/match")
- } else {
- rejected.Zid(zid)
- mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/reject")
- }
- }
- for _, p := range mgr.boxes {
- if err := p.ApplyMeta(ctx, handleMeta, searchPred); err != nil {
- return nil, err
+
+ compSearch := q.RetrieveAndCompile(ctx, mgr, metaSeq)
+ if result := compSearch.Result(); result != nil {
+ mgr.mgrLog.Trace().Int("count", int64(len(result))).Msg("found without ApplyMeta")
+ return result, nil
+ }
+ selected := map[id.Zid]*meta.Meta{}
+ for _, term := range compSearch.Terms {
+ rejected := id.Set{}
+ handleMeta := func(m *meta.Meta) {
+ zid := m.Zid
+ if rejected.ContainsOrNil(zid) {
+ mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadyRejected")
+ return
+ }
+ if _, ok := selected[zid]; ok {
+ mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadySelected")
+ return
+ }
+ if compSearch.PreMatch(m) && term.Match(m) {
+ selected[zid] = m
+ mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/match")
+ } else {
+ rejected.Add(zid)
+ mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/reject")
+ }
+ }
+ for _, p := range mgr.boxes {
+ if err2 := p.ApplyMeta(ctx, handleMeta, term.Retrieve); err2 != nil {
+ return nil, err2
+ }
}
}
result := make([]*meta.Meta, 0, len(selected))
for _, m := range selected {
result = append(result, m)
}
- return s.Sort(result), nil
+ result = compSearch.AfterSearch(result)
+ mgr.mgrLog.Trace().Int("count", int64(len(result))).Msg("found with ApplyMeta")
+ return result, nil
}
// CanUpdateZettel returns true, if box could possibly update the given zettel.
-func (mgr *Manager) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
+func (mgr *Manager) CanUpdateZettel(ctx context.Context, zettel zettel.Zettel) bool {
mgr.mgrMx.RLock()
defer mgr.mgrMx.RUnlock()
- return mgr.started && mgr.boxes[0].CanUpdateZettel(ctx, zettel)
+ return mgr.State() == box.StartStateStarted && mgr.boxes[0].CanUpdateZettel(ctx, zettel)
}
// UpdateZettel updates an existing zettel.
-func (mgr *Manager) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
+func (mgr *Manager) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error {
mgr.mgrLog.Debug().Zid(zettel.Meta.Zid).Msg("UpdateZettel")
- mgr.mgrMx.RLock()
- defer mgr.mgrMx.RUnlock()
- if !mgr.started {
+ if mgr.State() != box.StartStateStarted {
return box.ErrStopped
}
// Remove all (computed) properties from metadata before storing the zettel.
zettel.Meta = zettel.Meta.Clone()
for _, p := range zettel.Meta.ComputedPairsRest() {
@@ -224,15 +218,15 @@
return mgr.boxes[0].UpdateZettel(ctx, zettel)
}
// AllowRenameZettel returns true, if box will not disallow renaming the zettel.
func (mgr *Manager) AllowRenameZettel(ctx context.Context, zid id.Zid) bool {
+ if mgr.State() != box.StartStateStarted {
+ return false
+ }
mgr.mgrMx.RLock()
defer mgr.mgrMx.RUnlock()
- if !mgr.started {
- return false
- }
for _, p := range mgr.boxes {
if !p.AllowRenameZettel(ctx, zid) {
return false
}
}
@@ -240,18 +234,19 @@
}
// RenameZettel changes the current zid to a new zid.
func (mgr *Manager) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error {
mgr.mgrLog.Debug().Zid(curZid).Zid(newZid).Msg("RenameZettel")
- mgr.mgrMx.RLock()
- defer mgr.mgrMx.RUnlock()
- if !mgr.started {
+ if mgr.State() != box.StartStateStarted {
return box.ErrStopped
}
+ mgr.mgrMx.RLock()
+ defer mgr.mgrMx.RUnlock()
for i, p := range mgr.boxes {
err := p.RenameZettel(ctx, curZid, newZid)
- if err != nil && !errors.Is(err, box.ErrNotFound) {
+ var errZNF box.ErrZettelNotFound
+ if err != nil && !errors.As(err, &errZNF) {
for j := 0; j < i; j++ {
mgr.boxes[j].RenameZettel(ctx, newZid, curZid)
}
return err
}
@@ -259,15 +254,15 @@
return nil
}
// CanDeleteZettel returns true, if box could possibly delete the given zettel.
func (mgr *Manager) CanDeleteZettel(ctx context.Context, zid id.Zid) bool {
+ if mgr.State() != box.StartStateStarted {
+ return false
+ }
mgr.mgrMx.RLock()
defer mgr.mgrMx.RUnlock()
- if !mgr.started {
- return false
- }
for _, p := range mgr.boxes {
if p.CanDeleteZettel(ctx, zid) {
return true
}
}
@@ -275,21 +270,22 @@
}
// DeleteZettel removes the zettel from the box.
func (mgr *Manager) DeleteZettel(ctx context.Context, zid id.Zid) error {
mgr.mgrLog.Debug().Zid(zid).Msg("DeleteZettel")
+ if mgr.State() != box.StartStateStarted {
+ return box.ErrStopped
+ }
mgr.mgrMx.RLock()
defer mgr.mgrMx.RUnlock()
- if !mgr.started {
- return box.ErrStopped
- }
for _, p := range mgr.boxes {
err := p.DeleteZettel(ctx, zid)
if err == nil {
return nil
}
- if !errors.Is(err, box.ErrNotFound) && !errors.Is(err, box.ErrReadOnly) {
+ var errZNF box.ErrZettelNotFound
+ if !errors.As(err, &errZNF) && !errors.Is(err, box.ErrReadOnly) {
return err
}
}
- return box.ErrNotFound
+ return box.ErrZettelNotFound{Zid: zid}
}
Index: box/manager/collect.go
==================================================================
--- box/manager/collect.go
+++ box/manager/collect.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -13,26 +13,24 @@
import (
"strings"
"zettelstore.de/z/ast"
"zettelstore.de/z/box/manager/store"
- "zettelstore.de/z/domain/id"
"zettelstore.de/z/strfun"
+ "zettelstore.de/z/zettel/id"
)
type collectData struct {
refs id.Set
words store.WordSet
urls store.WordSet
- itags store.WordSet
}
func (data *collectData) initialize() {
data.refs = id.NewSet()
data.words = store.NewWordSet()
data.urls = store.NewWordSet()
- data.itags = store.NewWordSet()
}
func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) {
ast.Walk(data, &zn.Ast)
}
@@ -47,17 +45,16 @@
data.addText(string(n.Content))
case *ast.TranscludeNode:
data.addRef(n.Ref)
case *ast.TextNode:
data.addText(n.Text)
- case *ast.TagNode:
- data.addText(n.Tag)
- data.itags.Add("#" + strings.ToLower(n.Tag))
case *ast.LinkNode:
data.addRef(n.Ref)
case *ast.EmbedRefNode:
data.addRef(n.Ref)
+ case *ast.CiteNode:
+ data.addText(n.Key)
case *ast.LiteralNode:
data.addText(string(n.Content))
}
return data
}
@@ -77,8 +74,8 @@
}
if !ref.IsZettel() {
return
}
if zid, err := id.Parse(ref.URL.Path); err == nil {
- data.refs.Zid(zid)
+ data.refs.Add(zid)
}
}
Index: box/manager/enrich.go
==================================================================
--- box/manager/enrich.go
+++ box/manager/enrich.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -12,26 +12,100 @@
import (
"context"
"strconv"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/box"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// Enrich computes additional properties and updates the given metadata.
func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, boxNumber int) {
+
+ // Calculate computed, but stored values.
+ if _, ok := m.Get(api.KeyCreated); !ok {
+ m.Set(api.KeyCreated, computeCreated(m.Zid))
+ }
+
if box.DoNotEnrich(ctx) {
// Enrich is called indirectly via indexer or enrichment is not requested
- // because of other reasons -> ignore this call, do not update meta data
+ // because of other reasons -> ignore this call, do not update metadata
return
}
- m.Set(api.KeyBoxNumber, strconv.Itoa(boxNumber))
computePublished(m)
+ if boxNumber > 0 {
+ m.Set(api.KeyBoxNumber, strconv.Itoa(boxNumber))
+ }
mgr.idxStore.Enrich(ctx, m)
}
+
+func computeCreated(zid id.Zid) string {
+ if zid <= 10101000000 {
+ // A year 0000 is not allowed and therefore an artificaial Zid.
+ // In the year 0001, the month must be > 0.
+ // In the month 000101, the day must be > 0.
+ return "00010101000000"
+ }
+ seconds := zid % 100
+ if seconds > 59 {
+ seconds = 59
+ }
+ zid /= 100
+ minutes := zid % 100
+ if minutes > 59 {
+ minutes = 59
+ }
+ zid /= 100
+ hours := zid % 100
+ if hours > 23 {
+ hours = 23
+ }
+ zid /= 100
+ day := zid % 100
+ zid /= 100
+ month := zid % 100
+ year := zid / 100
+ month, day = sanitizeMonthDay(year, month, day)
+ created := ((((year*100+month)*100+day)*100+hours)*100+minutes)*100 + seconds
+ return created.String()
+}
+
+func sanitizeMonthDay(year, month, day id.Zid) (id.Zid, id.Zid) {
+ if day < 1 {
+ day = 1
+ }
+ if month < 1 {
+ month = 1
+ }
+ if month > 12 {
+ month = 12
+ }
+
+ switch month {
+ case 1, 3, 5, 7, 8, 10, 12:
+ if day > 31 {
+ day = 31
+ }
+ case 4, 6, 9, 11:
+ if day > 30 {
+ day = 30
+ }
+ case 2:
+ if year%4 != 0 || (year%100 == 0 && year%400 != 0) {
+ if day > 28 {
+ day = 28
+ }
+ } else {
+ if day > 29 {
+ day = 29
+ }
+ }
+ }
+ return month, day
+}
func computePublished(m *meta.Meta) {
if _, ok := m.Get(api.KeyPublished); ok {
return
}
@@ -38,10 +112,16 @@
if modified, ok := m.Get(api.KeyModified); ok {
if _, ok = meta.TimeValue(modified); ok {
m.Set(api.KeyPublished, modified)
return
}
+ }
+ if created, ok := m.Get(api.KeyCreated); ok {
+ if _, ok = meta.TimeValue(created); ok {
+ m.Set(api.KeyPublished, created)
+ return
+ }
}
zid := m.Zid.String()
if _, ok := meta.TimeValue(zid); ok {
m.Set(api.KeyPublished, zid)
return
Index: box/manager/indexer.go
==================================================================
--- box/manager/indexer.go
+++ box/manager/indexer.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -16,16 +16,16 @@
"net/url"
"time"
"zettelstore.de/z/box"
"zettelstore.de/z/box/manager/store"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/kernel"
"zettelstore.de/z/parser"
"zettelstore.de/z/strfun"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// SearchEqual returns all zettel that contains the given exact word.
// The word must be normalized through Unicode NKFD, trimmed and not empty.
func (mgr *Manager) SearchEqual(word string) id.Set {
@@ -73,12 +73,12 @@
// idxIndexer runs in the background and updates the index data structures.
// This is the main service of the idxIndexer.
func (mgr *Manager) idxIndexer() {
// Something may panic. Ensure a running indexer.
defer func() {
- if r := recover(); r != nil {
- kernel.Main.LogRecover("Indexer", r)
+ if ri := recover(); ri != nil {
+ kernel.Main.LogRecover("Indexer", ri)
go mgr.idxIndexer()
}
}()
timerDuration := 15 * time.Second
@@ -107,42 +107,34 @@
start = time.Now()
if rno := mgr.idxAr.Reload(zids); rno > 0 {
roomNum = rno
}
mgr.idxMx.Lock()
- mgr.idxLastReload = time.Now()
+ mgr.idxLastReload = time.Now().Local()
mgr.idxSinceReload = 0
mgr.idxMx.Unlock()
}
- case arUpdate:
- mgr.idxLog.Debug().Zid(zid).Msg("update")
+ case arZettel:
+ mgr.idxLog.Debug().Zid(zid).Msg("zettel")
zettel, err := mgr.GetZettel(ctx, zid)
if err != nil {
- // TODO: on some errors put the zid into a "try later" set
+ // Zettel was deleted or is not accessible b/c of other reasons
+ mgr.idxLog.Trace().Zid(zid).Msg("delete")
+ mgr.idxMx.Lock()
+ mgr.idxSinceReload++
+ mgr.idxMx.Unlock()
+ mgr.idxDeleteZettel(zid)
continue
}
+ mgr.idxLog.Trace().Zid(zid).Msg("update")
mgr.idxMx.Lock()
if arRoomNum == roomNum {
mgr.idxDurReload = time.Since(start)
}
mgr.idxSinceReload++
mgr.idxMx.Unlock()
mgr.idxUpdateZettel(ctx, zettel)
- case arDelete:
- mgr.idxLog.Debug().Zid(zid).Msg("delete")
- if _, err := mgr.GetMeta(ctx, zid); err == nil {
- // Zettel was not deleted. This might occur, if zettel was
- // deleted in secondary dirbox, but is still present in
- // first dirbox (or vice versa). Re-index zettel in case
- // a hidden zettel was recovered
- mgr.idxLog.Debug().Zid(zid).Msg("not deleted")
- mgr.idxAr.Enqueue(zid, arUpdate)
- }
- mgr.idxMx.Lock()
- mgr.idxSinceReload++
- mgr.idxMx.Unlock()
- mgr.idxDeleteZettel(zid)
}
}
}
func (mgr *Manager) idxSleepService(timer *time.Timer, timerDuration time.Duration) bool {
@@ -163,27 +155,27 @@
return false
}
return true
}
-func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel domain.Zettel) {
+func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) {
var cData collectData
cData.initialize()
- collectZettelIndexData(parser.ParseZettel(zettel, "", mgr.rtConfig), &cData)
+ collectZettelIndexData(parser.ParseZettel(ctx, zettel, "", mgr.rtConfig), &cData)
m := zettel.Meta
- zi := store.NewZettelIndex(m.Zid)
+ zi := store.NewZettelIndex(m)
mgr.idxCollectFromMeta(ctx, m, zi, &cData)
mgr.idxProcessData(ctx, zi, &cData)
toCheck := mgr.idxStore.UpdateReferences(ctx, zi)
mgr.idxCheckZettel(toCheck)
}
func (mgr *Manager) idxCollectFromMeta(ctx context.Context, m *meta.Meta, zi *store.ZettelIndex, cData *collectData) {
- for _, pair := range m.Pairs() {
+ for _, pair := range m.ComputedPairs() {
descr := meta.GetDescription(pair.Key)
- if descr.IsComputed() {
+ if descr.IsProperty() {
continue
}
switch descr.Type {
case meta.TypeID:
mgr.idxUpdateValue(ctx, descr.Inverse, pair.Value, zi)
@@ -206,42 +198,41 @@
}
}
func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) {
for ref := range cData.refs {
- if _, err := mgr.GetMeta(ctx, ref); err == nil {
+ if mgr.HasZettel(ctx, ref) {
zi.AddBackRef(ref)
} else {
zi.AddDeadRef(ref)
}
}
zi.SetWords(cData.words)
zi.SetUrls(cData.urls)
- zi.SetITags(cData.itags)
}
func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) {
zid, err := id.Parse(value)
if err != nil {
return
}
- if _, err = mgr.GetMeta(ctx, zid); err != nil {
+ if !mgr.HasZettel(ctx, zid) {
zi.AddDeadRef(zid)
return
}
if inverseKey == "" {
zi.AddBackRef(zid)
return
}
- zi.AddMetaRef(inverseKey, zid)
+ zi.AddInverseRef(inverseKey, zid)
}
func (mgr *Manager) idxDeleteZettel(zid id.Zid) {
toCheck := mgr.idxStore.DeleteZettel(context.Background(), zid)
mgr.idxCheckZettel(toCheck)
}
func (mgr *Manager) idxCheckZettel(s id.Set) {
for zid := range s {
- mgr.idxAr.Enqueue(zid, arUpdate)
+ mgr.idxAr.EnqueueZettel(zid)
}
}
Index: box/manager/manager.go
==================================================================
--- box/manager/manager.go
+++ box/manager/manager.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -16,21 +16,21 @@
"io"
"net/url"
"sync"
"time"
- "zettelstore.de/c/maps"
+ "zettelstore.de/client.fossil/maps"
"zettelstore.de/z/auth"
"zettelstore.de/z/box"
"zettelstore.de/z/box/manager/memstore"
"zettelstore.de/z/box/manager/store"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/kernel"
"zettelstore.de/z/logger"
"zettelstore.de/z/strfun"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// ConnectData contains all administration related values.
type ConnectData struct {
Number int // number of the box, starting with 1.
@@ -83,12 +83,13 @@
func GetSchemes() []string { return maps.Keys(registry) }
// Manager is a coordinating box.
type Manager struct {
mgrLog *logger.Logger
+ stateMx sync.RWMutex
+ state box.StartState
mgrMx sync.RWMutex
- started bool
rtConfig config.Config
boxes []box.ManagedBox
observers []box.UpdateFunc
mxObserver sync.RWMutex
done chan struct{}
@@ -96,19 +97,32 @@
propertyKeys strfun.Set // Set of property key names
// Indexer data
idxLog *logger.Logger
idxStore store.Store
- idxAr *anterooms
+ idxAr *anteroomQueue
idxReady chan struct{} // Signal a non-empty anteroom to background task
// Indexer stats data
idxMx sync.RWMutex
idxLastReload time.Time
idxDurReload time.Duration
idxSinceReload uint64
}
+
+func (mgr *Manager) setState(newState box.StartState) {
+ mgr.stateMx.Lock()
+ mgr.state = newState
+ mgr.stateMx.Unlock()
+}
+
+func (mgr *Manager) State() box.StartState {
+ mgr.stateMx.RLock()
+ state := mgr.state
+ mgr.stateMx.RUnlock()
+ return state
+}
// New creates a new managing box.
func New(boxURIs []*url.URL, authManager auth.BaseManager, rtConfig config.Config) (*Manager, error) {
descrs := meta.GetSortedKeyDescriptions()
propertyKeys := make(strfun.Set, len(descrs))
@@ -124,11 +138,11 @@
infos: make(chan box.UpdateInfo, len(boxURIs)*10),
propertyKeys: propertyKeys,
idxLog: boxLog.Clone().Str("box", "index").Child(),
idxStore: memstore.New(),
- idxAr: newAnterooms(10),
+ idxAr: newAnteroomQueue(1000),
idxReady: make(chan struct{}, 1),
}
cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.infos}
boxes := make([]box.ManagedBox, 0, len(boxURIs)+2)
for _, uri := range boxURIs {
@@ -167,12 +181,12 @@
}
func (mgr *Manager) notifier() {
// The call to notify may panic. Ensure a running notifier.
defer func() {
- if r := recover(); r != nil {
- kernel.Main.LogRecover("Notifier", r)
+ if ri := recover(); ri != nil {
+ kernel.Main.LogRecover("Notifier", ri)
go mgr.notifier()
}
}()
tsLastEvent := time.Now()
@@ -193,15 +207,18 @@
mgr.mgrLog.Debug().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier")
if ignoreUpdate(cache, now, reason, zid) {
mgr.mgrLog.Trace().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier ignored")
continue
}
+
mgr.idxEnqueue(reason, zid)
if ci.Box == nil {
ci.Box = mgr
}
- mgr.notifyObserver(&ci)
+ if mgr.State() == box.StartStateStarted {
+ mgr.notifyObserver(&ci)
+ }
}
case <-mgr.done:
return
}
}
@@ -226,17 +243,18 @@
return false
}
func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zid id.Zid) {
switch reason {
+ case box.OnReady:
+ return
case box.OnReload:
mgr.idxAr.Reset()
- case box.OnUpdate:
- mgr.idxAr.Enqueue(zid, arUpdate)
- case box.OnDelete:
- mgr.idxAr.Enqueue(zid, arDelete)
+ case box.OnZettel:
+ mgr.idxAr.EnqueueZettel(zid)
default:
+ mgr.mgrLog.Warn().Uint("reason", uint64(reason)).Zid(zid).Msg("Unknown notification reason")
return
}
select {
case mgr.idxReady <- struct{}{}:
default:
@@ -254,65 +272,94 @@
// Start the box. Now all other functions of the box are allowed.
// Starting an already started box is not allowed.
func (mgr *Manager) Start(ctx context.Context) error {
mgr.mgrMx.Lock()
- if mgr.started {
- mgr.mgrMx.Unlock()
+ defer mgr.mgrMx.Unlock()
+ if mgr.State() != box.StartStateStopped {
return box.ErrStarted
}
+ mgr.setState(box.StartStateStarting)
for i := len(mgr.boxes) - 1; i >= 0; i-- {
ssi, ok := mgr.boxes[i].(box.StartStopper)
if !ok {
continue
}
err := ssi.Start(ctx)
if err == nil {
continue
}
+ mgr.setState(box.StartStateStopping)
for j := i + 1; j < len(mgr.boxes); j++ {
if ssj, ok2 := mgr.boxes[j].(box.StartStopper); ok2 {
ssj.Stop(ctx)
}
}
- mgr.mgrMx.Unlock()
+ mgr.setState(box.StartStateStopped)
return err
}
mgr.idxAr.Reset() // Ensure an initial index run
mgr.done = make(chan struct{})
go mgr.notifier()
- go mgr.idxIndexer()
+
+ mgr.waitBoxesAreStarted()
+ mgr.setState(box.StartStateStarted)
+ mgr.notifyObserver(&box.UpdateInfo{Box: mgr, Reason: box.OnReady})
- mgr.started = true
- mgr.mgrMx.Unlock()
+ go mgr.idxIndexer()
return nil
}
+
+func (mgr *Manager) waitBoxesAreStarted() {
+ const waitTime = 10 * time.Millisecond
+ const waitLoop = int(1 * time.Second / waitTime)
+ for i := 1; !mgr.allBoxesStarted(); i++ {
+ if i%waitLoop == 0 {
+ if time.Duration(i)*waitTime > time.Minute {
+ mgr.mgrLog.Warn().Msg("Waiting for more than one minute to start")
+ } else {
+ mgr.mgrLog.Trace().Msg("Wait for boxes to start")
+ }
+ }
+ time.Sleep(waitTime)
+ }
+}
+
+func (mgr *Manager) allBoxesStarted() bool {
+ for _, bx := range mgr.boxes {
+ if b, ok := bx.(box.StartStopper); ok && b.State() != box.StartStateStarted {
+ return false
+ }
+ }
+ return true
+}
// Stop the started box. Now only the Start() function is allowed.
func (mgr *Manager) Stop(ctx context.Context) {
mgr.mgrMx.Lock()
defer mgr.mgrMx.Unlock()
- if !mgr.started {
+ if mgr.State() != box.StartStateStarted {
return
}
+ mgr.setState(box.StartStateStopping)
close(mgr.done)
for _, p := range mgr.boxes {
if ss, ok := p.(box.StartStopper); ok {
ss.Stop(ctx)
}
}
- mgr.started = false
+ mgr.setState(box.StartStateStopped)
}
// Refresh internal box data.
func (mgr *Manager) Refresh(ctx context.Context) error {
mgr.mgrLog.Debug().Msg("Refresh")
+ if mgr.State() != box.StartStateStarted {
+ return box.ErrStopped
+ }
mgr.mgrMx.Lock()
defer mgr.mgrMx.Unlock()
- if !mgr.started {
- return box.ErrStopped
- }
mgr.infos <- box.UpdateInfo{Reason: box.OnReload, Zid: id.Invalid}
for _, bx := range mgr.boxes {
if rb, ok := bx.(box.Refresher); ok {
rb.Refresh(ctx)
}
Index: box/manager/memstore/memstore.go
==================================================================
--- box/manager/memstore/memstore.go
+++ box/manager/memstore/memstore.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -17,67 +17,74 @@
"io"
"sort"
"strings"
"sync"
- "zettelstore.de/c/api"
- "zettelstore.de/c/maps"
+ "zettelstore.de/client.fossil/api"
+ "zettelstore.de/client.fossil/maps"
+ "zettelstore.de/z/box"
"zettelstore.de/z/box/manager/store"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
-type metaRefs struct {
+type bidiRefs struct {
forward id.Slice
backward id.Slice
}
-type zettelIndex struct {
- dead id.Slice
- forward id.Slice
- backward id.Slice
- meta map[string]metaRefs
- words []string
- urls []string
- itags string // Inline tags
-}
-
-func (zi *zettelIndex) isEmpty() bool {
- if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 || len(zi.words) > 0 {
- return false
- }
- return len(zi.meta) == 0
+type zettelData struct {
+ meta *meta.Meta
+ dead id.Slice
+ forward id.Slice
+ backward id.Slice
+ otherRefs map[string]bidiRefs
+ words []string
+ urls []string
}
type stringRefs map[string]id.Slice
type memStore struct {
- mx sync.RWMutex
- idx map[id.Zid]*zettelIndex
- dead map[id.Zid]id.Slice // map dead refs where they occur
- words stringRefs
- urls stringRefs
+ mx sync.RWMutex
+ intern map[string]string // map to intern strings
+ idx map[id.Zid]*zettelData
+ dead map[id.Zid]id.Slice // map dead refs where they occur
+ words stringRefs
+ urls stringRefs
// Stats
+ mxStats sync.Mutex
updates uint64
}
// New returns a new memory-based index store.
func New() store.Store {
return &memStore{
- idx: make(map[id.Zid]*zettelIndex),
- dead: make(map[id.Zid]id.Slice),
- words: make(stringRefs),
- urls: make(stringRefs),
+ intern: make(map[string]string, 1024),
+ idx: make(map[id.Zid]*zettelData),
+ dead: make(map[id.Zid]id.Slice),
+ words: make(stringRefs),
+ urls: make(stringRefs),
+ }
+}
+
+func (ms *memStore) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
+ ms.mx.RLock()
+ defer ms.mx.RUnlock()
+ if zi, found := ms.idx[zid]; found && zi.meta != nil {
+ // zi.meta is nil, if zettel was referenced, but is not indexed yet.
+ return zi.meta.Clone(), nil
}
+ return nil, box.ErrZettelNotFound{Zid: zid}
}
func (ms *memStore) Enrich(_ context.Context, m *meta.Meta) {
if ms.doEnrich(m) {
- ms.mx.Lock()
+ ms.mxStats.Lock()
ms.updates++
- ms.mx.Unlock()
+ ms.mxStats.Unlock()
}
}
func (ms *memStore) doEnrich(m *meta.Meta) bool {
ms.mx.RLock()
@@ -89,21 +96,21 @@
var updated bool
if len(zi.dead) > 0 {
m.Set(api.KeyDead, zi.dead.String())
updated = true
}
- back := removeOtherMetaRefs(m, zi.backward.Copy())
+ back := removeOtherMetaRefs(m, zi.backward.Clone())
if len(zi.backward) > 0 {
m.Set(api.KeyBackward, zi.backward.String())
updated = true
}
if len(zi.forward) > 0 {
m.Set(api.KeyForward, zi.forward.String())
back = remRefs(back, zi.forward)
updated = true
}
- for k, refs := range zi.meta {
+ for k, refs := range zi.otherRefs {
if len(refs.backward) > 0 {
m.Set(k, refs.backward.String())
back = remRefs(back, refs.backward)
updated = true
}
@@ -110,22 +117,10 @@
}
if len(back) > 0 {
m.Set(api.KeyBack, back.String())
updated = true
}
- if itags := zi.itags; itags != "" {
- m.Set(api.KeyContentTags, itags)
- if tags, ok2 := m.Get(api.KeyTags); ok2 {
- m.Set(api.KeyAllTags, tags+" "+itags)
- } else {
- m.Set(api.KeyAllTags, itags)
- }
- updated = true
- } else if tags, ok2 := m.Get(api.KeyTags); ok2 {
- m.Set(api.KeyAllTags, tags)
- updated = true
- }
return updated
}
// SearchEqual returns all zettel that contains the given exact word.
// The word must be normalized through Unicode NKFD, trimmed and not empty.
@@ -132,14 +127,14 @@
func (ms *memStore) SearchEqual(word string) id.Set {
ms.mx.RLock()
defer ms.mx.RUnlock()
result := id.NewSet()
if refs, ok := ms.words[word]; ok {
- result.AddSlice(refs)
+ result.CopySlice(refs)
}
if refs, ok := ms.urls[word]; ok {
- result.AddSlice(refs)
+ result.CopySlice(refs)
}
zid, err := id.Parse(word)
if err != nil {
return result
}
@@ -160,17 +155,22 @@
result := ms.selectWithPred(prefix, strings.HasPrefix)
l := len(prefix)
if l > 14 {
return result
}
- minZid, err := id.Parse(prefix + "00000000000000"[:14-l])
+ maxZid, err := id.Parse(prefix + "99999999999999"[:14-l])
if err != nil {
return result
}
- maxZid, err := id.Parse(prefix + "99999999999999"[:14-l])
- if err != nil {
- return result
+ var minZid id.Zid
+ if l < 14 && prefix == "0000000000000"[:l] {
+ minZid = id.Zid(1)
+ } else {
+ minZid, err = id.Parse(prefix + "00000000000000"[:14-l])
+ if err != nil {
+ return result
+ }
}
for zid, zi := range ms.idx {
if minZid <= zid && zid <= maxZid {
addBackwardZids(result, zid, zi)
}
@@ -229,27 +229,27 @@
result := id.NewSet()
for word, refs := range ms.words {
if !pred(word, s) {
continue
}
- result.AddSlice(refs)
+ result.CopySlice(refs)
}
for u, refs := range ms.urls {
if !pred(u, s) {
continue
}
- result.AddSlice(refs)
+ result.CopySlice(refs)
}
return result
}
-func addBackwardZids(result id.Set, zid id.Zid, zi *zettelIndex) {
+func addBackwardZids(result id.Set, zid id.Zid, zi *zettelData) {
// Must only be called if ms.mx is read-locked!
- result.Zid(zid)
- result.AddSlice(zi.backward)
- for _, mref := range zi.meta {
- result.AddSlice(mref.backward)
+ result.Add(zid)
+ result.CopySlice(zi.backward)
+ for _, mref := range zi.otherRefs {
+ result.CopySlice(mref.backward)
}
}
func removeOtherMetaRefs(m *meta.Meta, back id.Slice) id.Slice {
for _, p := range m.PairsRest() {
@@ -268,15 +268,16 @@
}
return back
}
func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set {
+ m := ms.makeMeta(zidx)
ms.mx.Lock()
defer ms.mx.Unlock()
zi, ziExist := ms.idx[zidx.Zid]
if !ziExist || zi == nil {
- zi = &zettelIndex{}
+ zi = &zettelData{}
ziExist = false
}
// Is this zettel an old dead reference mentioned in other zettel?
var toCheck id.Set
@@ -284,26 +285,65 @@
// These must be checked later again
toCheck = id.NewSet(refs...)
delete(ms.dead, zidx.Zid)
}
+ zi.meta = m
ms.updateDeadReferences(zidx, zi)
- ms.updateForwardBackwardReferences(zidx, zi)
- ms.updateMetadataReferences(zidx, zi)
+ ids := ms.updateForwardBackwardReferences(zidx, zi)
+ toCheck = toCheck.Copy(ids)
+ ids = ms.updateMetadataReferences(zidx, zi)
+ toCheck = toCheck.Copy(ids)
zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords())
zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls())
- zi.itags = setITags(zidx.GetITags())
// Check if zi must be inserted into ms.idx
- if !ziExist && !zi.isEmpty() {
+ if !ziExist {
ms.idx[zidx.Zid] = zi
}
return toCheck
}
-func (ms *memStore) updateDeadReferences(zidx *store.ZettelIndex, zi *zettelIndex) {
+var internableKeys = map[string]bool{
+ api.KeyRole: true,
+ api.KeySyntax: true,
+ api.KeyFolgeRole: true,
+ api.KeyLang: true,
+ api.KeyReadOnly: true,
+}
+
+func isInternableValue(key string) bool {
+ if internableKeys[key] {
+ return true
+ }
+ return strings.HasSuffix(key, "-role")
+}
+
+func (ms *memStore) internString(s string) string {
+ if is, found := ms.intern[s]; found {
+ return is
+ }
+ ms.intern[s] = s
+ return s
+}
+
+func (ms *memStore) makeMeta(zidx *store.ZettelIndex) *meta.Meta {
+ origM := zidx.GetMeta()
+ copyM := meta.New(origM.Zid)
+ for _, p := range origM.Pairs() {
+ key := ms.internString(p.Key)
+ if isInternableValue(key) {
+ copyM.Set(key, ms.internString(p.Value))
+ } else if key == api.KeyBoxNumber || !meta.IsComputed(key) {
+ copyM.Set(key, p.Value)
+ }
+ }
+ return copyM
+}
+
+func (ms *memStore) updateDeadReferences(zidx *store.ZettelIndex, zi *zettelData) {
// Must only be called if ms.mx is write-locked!
drefs := zidx.GetDeadRefs()
newRefs, remRefs := refsDiff(drefs, zi.dead)
zi.dead = drefs
for _, ref := range remRefs {
@@ -312,58 +352,71 @@
for _, ref := range newRefs {
ms.dead[ref] = addRef(ms.dead[ref], zidx.Zid)
}
}
-func (ms *memStore) updateForwardBackwardReferences(zidx *store.ZettelIndex, zi *zettelIndex) {
+func (ms *memStore) updateForwardBackwardReferences(zidx *store.ZettelIndex, zi *zettelData) id.Set {
// Must only be called if ms.mx is write-locked!
brefs := zidx.GetBackRefs()
newRefs, remRefs := refsDiff(brefs, zi.forward)
zi.forward = brefs
+
+ var toCheck id.Set
for _, ref := range remRefs {
- bzi := ms.getEntry(ref)
+ bzi := ms.getOrCreateEntry(ref)
bzi.backward = remRef(bzi.backward, zidx.Zid)
+ if bzi.meta == nil {
+ toCheck = toCheck.Add(ref)
+ }
}
for _, ref := range newRefs {
- bzi := ms.getEntry(ref)
+ bzi := ms.getOrCreateEntry(ref)
bzi.backward = addRef(bzi.backward, zidx.Zid)
+ if bzi.meta == nil {
+ toCheck = toCheck.Add(ref)
+ }
}
+ return toCheck
}
-func (ms *memStore) updateMetadataReferences(zidx *store.ZettelIndex, zi *zettelIndex) {
+func (ms *memStore) updateMetadataReferences(zidx *store.ZettelIndex, zi *zettelData) id.Set {
// Must only be called if ms.mx is write-locked!
- metarefs := zidx.GetMetaRefs()
- for key, mr := range zi.meta {
- if _, ok := metarefs[key]; ok {
+ inverseRefs := zidx.GetInverseRefs()
+ for key, mr := range zi.otherRefs {
+ if _, ok := inverseRefs[key]; ok {
continue
}
ms.removeInverseMeta(zidx.Zid, key, mr.forward)
}
- if zi.meta == nil {
- zi.meta = make(map[string]metaRefs)
+ if zi.otherRefs == nil {
+ zi.otherRefs = make(map[string]bidiRefs)
}
- for key, mrefs := range metarefs {
- mr := zi.meta[key]
+ var toCheck id.Set
+ for key, mrefs := range inverseRefs {
+ mr := zi.otherRefs[key]
newRefs, remRefs := refsDiff(mrefs, mr.forward)
mr.forward = mrefs
- zi.meta[key] = mr
+ zi.otherRefs[key] = mr
for _, ref := range newRefs {
- bzi := ms.getEntry(ref)
- if bzi.meta == nil {
- bzi.meta = make(map[string]metaRefs)
+ bzi := ms.getOrCreateEntry(ref)
+ if bzi.otherRefs == nil {
+ bzi.otherRefs = make(map[string]bidiRefs)
}
- bmr := bzi.meta[key]
+ bmr := bzi.otherRefs[key]
bmr.backward = addRef(bmr.backward, zidx.Zid)
- bzi.meta[key] = bmr
+ bzi.otherRefs[key] = bmr
+ if bzi.meta == nil {
+ toCheck = toCheck.Add(ref)
+ }
}
ms.removeInverseMeta(zidx.Zid, key, remRefs)
}
+ return toCheck
}
func updateWordSet(zid id.Zid, srefs stringRefs, prev []string, next store.WordSet) []string {
- // Must only be called if ms.mx is write-locked!
newWords, removeWords := next.Diff(prev)
for _, word := range newWords {
if refs, ok := srefs[word]; ok {
srefs[word] = addRef(refs, zid)
continue
@@ -383,25 +436,16 @@
srefs[word] = refs2
}
return next.Words()
}
-func setITags(next store.WordSet) string {
- itags := next.Words()
- if len(itags) == 0 {
- return ""
- }
- sort.Strings(itags)
- return strings.Join(itags, " ")
-}
-
-func (ms *memStore) getEntry(zid id.Zid) *zettelIndex {
+func (ms *memStore) getOrCreateEntry(zid id.Zid) *zettelData {
// Must only be called if ms.mx is write-locked!
if zi, ok := ms.idx[zid]; ok {
return zi
}
- zi := &zettelIndex{}
+ zi := &zettelData{}
ms.idx[zid] = zi
return zi
}
func (ms *memStore) DeleteZettel(_ context.Context, zid id.Zid) id.Set {
@@ -413,21 +457,21 @@
return nil
}
ms.deleteDeadSources(zid, zi)
toCheck := ms.deleteForwardBackward(zid, zi)
- if len(zi.meta) > 0 {
- for key, mrefs := range zi.meta {
+ if len(zi.otherRefs) > 0 {
+ for key, mrefs := range zi.otherRefs {
ms.removeInverseMeta(zid, key, mrefs.forward)
}
}
ms.deleteWords(zid, zi.words)
delete(ms.idx, zid)
return toCheck
}
-func (ms *memStore) deleteDeadSources(zid id.Zid, zi *zettelIndex) {
+func (ms *memStore) deleteDeadSources(zid id.Zid, zi *zettelData) {
// Must only be called if ms.mx is write-locked!
for _, ref := range zi.dead {
if drefs, ok := ms.dead[ref]; ok {
drefs = remRef(drefs, zid)
if len(drefs) > 0 {
@@ -437,11 +481,11 @@
}
}
}
}
-func (ms *memStore) deleteForwardBackward(zid id.Zid, zi *zettelIndex) id.Set {
+func (ms *memStore) deleteForwardBackward(zid id.Zid, zi *zettelData) id.Set {
// Must only be called if ms.mx is write-locked!
var toCheck id.Set
for _, ref := range zi.forward {
if fzi, ok := ms.idx[ref]; ok {
fzi.backward = remRef(fzi.backward, zid)
@@ -451,34 +495,34 @@
if bzi, ok := ms.idx[ref]; ok {
bzi.forward = remRef(bzi.forward, zid)
if toCheck == nil {
toCheck = id.NewSet()
}
- toCheck.Zid(ref)
+ toCheck.Add(ref)
}
}
return toCheck
}
func (ms *memStore) removeInverseMeta(zid id.Zid, key string, forward id.Slice) {
// Must only be called if ms.mx is write-locked!
for _, ref := range forward {
bzi, ok := ms.idx[ref]
- if !ok || bzi.meta == nil {
+ if !ok || bzi.otherRefs == nil {
continue
}
- bmr, ok := bzi.meta[key]
+ bmr, ok := bzi.otherRefs[key]
if !ok {
continue
}
bmr.backward = remRef(bmr.backward, zid)
if len(bmr.backward) > 0 || len(bmr.forward) > 0 {
- bzi.meta[key] = bmr
+ bzi.otherRefs[key] = bmr
} else {
- delete(bzi.meta, key)
- if len(bzi.meta) == 0 {
- bzi.meta = nil
+ delete(bzi.otherRefs, key)
+ if len(bzi.otherRefs) == 0 {
+ bzi.otherRefs = nil
}
}
}
}
@@ -499,14 +543,16 @@
}
func (ms *memStore) ReadStats(st *store.Stats) {
ms.mx.RLock()
st.Zettel = len(ms.idx)
- st.Updates = ms.updates
st.Words = uint64(len(ms.words))
st.Urls = uint64(len(ms.urls))
ms.mx.RUnlock()
+ ms.mxStats.Lock()
+ st.Updates = ms.updates
+ ms.mxStats.Unlock()
}
func (ms *memStore) Dump(w io.Writer) {
ms.mx.RLock()
defer ms.mx.RUnlock()
@@ -534,11 +580,11 @@
if len(zi.dead) > 0 {
fmt.Fprintln(w, "* Dead:", zi.dead)
}
dumpZids(w, "* Forward:", zi.forward)
dumpZids(w, "* Backward:", zi.backward)
- for k, fb := range zi.meta {
+ for k, fb := range zi.otherRefs {
fmt.Fprintln(w, "* Meta", k)
dumpZids(w, "** Forward:", fb.forward)
dumpZids(w, "** Backward:", fb.backward)
}
dumpStrings(w, "* Words", "", "", zi.words)
Index: box/manager/memstore/refs.go
==================================================================
--- box/manager/memstore/refs.go
+++ box/manager/memstore/refs.go
@@ -1,18 +1,22 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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 memstore
-import "zettelstore.de/z/domain/id"
+import (
+ "slices"
+
+ "zettelstore.de/z/zettel/id"
+)
func refsDiff(refsN, refsO id.Slice) (newRefs, remRefs id.Slice) {
npos, opos := 0, 0
for npos < len(refsN) && opos < len(refsO) {
rn, ro := refsN[npos], refsO[opos]
@@ -48,13 +52,11 @@
lo = m + 1
} else {
hi = m
}
}
- refs = append(refs, id.Invalid)
- copy(refs[hi+1:], refs[hi:])
- refs[hi] = ref
+ refs = slices.Insert(refs, hi, ref)
return refs
}
func remRefs(refs, rem id.Slice) id.Slice {
if len(refs) == 0 || len(rem) == 0 {
Index: box/manager/memstore/refs_test.go
==================================================================
--- box/manager/memstore/refs_test.go
+++ box/manager/memstore/refs_test.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -11,11 +11,11 @@
package memstore
import (
"testing"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/zettel/id"
)
func assertRefs(t *testing.T, i int, got, exp id.Slice) {
t.Helper()
if got == nil && exp != nil {
Index: box/manager/store/store.go
==================================================================
--- box/manager/store/store.go
+++ box/manager/store/store.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -13,13 +13,13 @@
import (
"context"
"io"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
- "zettelstore.de/z/search"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// Stats records statistics about the store.
type Stats struct {
// Zettel is the number of zettel managed by the indexer.
@@ -36,11 +36,14 @@
}
// Store all relevant zettel data. There may be multiple implementations, i.e.
// memory-based, file-based, based on SQLite, ...
type Store interface {
- search.Searcher
+ query.Searcher
+
+ // GetMeta returns the metadata of the zettel with the given identifier.
+ GetMeta(context.Context, id.Zid) (*meta.Meta, error)
// Entrich metadata with data from store.
Enrich(ctx context.Context, m *meta.Meta)
// UpdateReferences for a specific zettel.
Index: box/manager/store/wordset.go
==================================================================
--- box/manager/store/wordset.go
+++ box/manager/store/wordset.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
Index: box/manager/store/wordset_test.go
==================================================================
--- box/manager/store/wordset_test.go
+++ box/manager/store/wordset_test.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
Index: box/manager/store/zettel.go
==================================================================
--- box/manager/store/zettel.go
+++ box/manager/store/zettel.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
@@ -8,80 +8,80 @@
// under this license.
//-----------------------------------------------------------------------------
package store
-import "zettelstore.de/z/domain/id"
+import (
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
+)
// ZettelIndex contains all index data of a zettel.
type ZettelIndex struct {
- Zid id.Zid // zid of the indexed zettel
- backrefs id.Set // set of back references
- metarefs map[string]id.Set // references to inverse keys
- deadrefs id.Set // set of dead references
- words WordSet
- urls WordSet
- itags WordSet
+ Zid id.Zid // zid of the indexed zettel
+ meta *meta.Meta // full metadata
+ backrefs id.Set // set of back references
+ inverseRefs map[string]id.Set // references of inverse keys
+ deadrefs id.Set // set of dead references
+ words WordSet
+ urls WordSet
}
// NewZettelIndex creates a new zettel index.
-func NewZettelIndex(zid id.Zid) *ZettelIndex {
+func NewZettelIndex(m *meta.Meta) *ZettelIndex {
return &ZettelIndex{
- Zid: zid,
- backrefs: id.NewSet(),
- metarefs: make(map[string]id.Set),
- deadrefs: id.NewSet(),
+ Zid: m.Zid,
+ meta: m,
+ backrefs: id.NewSet(),
+ inverseRefs: make(map[string]id.Set),
+ deadrefs: id.NewSet(),
}
}
// AddBackRef adds a reference to a zettel where the current zettel links to
// without any more information.
func (zi *ZettelIndex) AddBackRef(zid id.Zid) {
- zi.backrefs.Zid(zid)
+ zi.backrefs.Add(zid)
}
-// AddMetaRef adds a named reference to a zettel. On that zettel, the given
+// AddInverseRef adds a named reference to a zettel. On that zettel, the given
// metadata key should point back to the current zettel.
-func (zi *ZettelIndex) AddMetaRef(key string, zid id.Zid) {
- if zids, ok := zi.metarefs[key]; ok {
- zids.Zid(zid)
+func (zi *ZettelIndex) AddInverseRef(key string, zid id.Zid) {
+ if zids, ok := zi.inverseRefs[key]; ok {
+ zids.Add(zid)
return
}
- zi.metarefs[key] = id.NewSet(zid)
+ zi.inverseRefs[key] = id.NewSet(zid)
}
// AddDeadRef adds a dead reference to a zettel.
func (zi *ZettelIndex) AddDeadRef(zid id.Zid) {
- zi.deadrefs.Zid(zid)
+ zi.deadrefs.Add(zid)
}
// SetWords sets the words to the given value.
func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words }
// SetUrls sets the words to the given value.
func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls }
-// SetITags sets the words to the given value.
-func (zi *ZettelIndex) SetITags(itags WordSet) { zi.itags = itags }
+// GetDeadRefs returns all dead references as a sorted list.
+func (zi *ZettelIndex) GetDeadRefs() id.Slice { return zi.deadrefs.Sorted() }
-// GetDeadRefs returns all dead references as a sorted list.
-func (zi *ZettelIndex) GetDeadRefs() id.Slice {
- return zi.deadrefs.Sorted()
-}
+// GetMeta return just the raw metadata.
+func (zi *ZettelIndex) GetMeta() *meta.Meta { return zi.meta }
// GetBackRefs returns all back references as a sorted list.
-func (zi *ZettelIndex) GetBackRefs() id.Slice {
- return zi.backrefs.Sorted()
-}
+func (zi *ZettelIndex) GetBackRefs() id.Slice { return zi.backrefs.Sorted() }
-// GetMetaRefs returns all meta references as a map of strings to a sorted list of references
-func (zi *ZettelIndex) GetMetaRefs() map[string]id.Slice {
- if len(zi.metarefs) == 0 {
+// GetInverseRefs returns all inverse meta references as a map of strings to a sorted list of references
+func (zi *ZettelIndex) GetInverseRefs() map[string]id.Slice {
+ if len(zi.inverseRefs) == 0 {
return nil
}
- result := make(map[string]id.Slice, len(zi.metarefs))
- for key, refs := range zi.metarefs {
+ result := make(map[string]id.Slice, len(zi.inverseRefs))
+ for key, refs := range zi.inverseRefs {
result[key] = refs.Sorted()
}
return result
}
@@ -88,8 +88,5 @@
// GetWords returns a reference to the set of words. It must not be modified.
func (zi *ZettelIndex) GetWords() WordSet { return zi.words }
// GetUrls returns a reference to the set of URLs. It must not be modified.
func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls }
-
-// GetITags returns a reference to the set of internal tags. It must not be modified.
-func (zi *ZettelIndex) GetITags() WordSet { return zi.itags }
Index: box/membox/membox.go
==================================================================
--- box/membox/membox.go
+++ box/membox/membox.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -16,16 +16,15 @@
"net/url"
"sync"
"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"
+ "zettelstore.de/z/query"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
)
func init() {
manager.Register(
"mem",
@@ -46,27 +45,36 @@
u *url.URL
cdata manager.ConnectData
maxZettel int
maxBytes int
mx sync.RWMutex // Protects the following fields
- zettel map[id.Zid]domain.Zettel
+ zettel map[id.Zid]zettel.Zettel
curBytes int
}
-func (mb *memBox) notifyChanged(reason box.UpdateReason, zid id.Zid) {
+func (mb *memBox) notifyChanged(zid id.Zid) {
if chci := mb.cdata.Notify; chci != nil {
- chci <- box.UpdateInfo{Reason: reason, Zid: zid}
+ chci <- box.UpdateInfo{Box: mb, Reason: box.OnZettel, Zid: zid}
}
}
func (mb *memBox) Location() string {
return mb.u.String()
}
+
+func (mb *memBox) State() box.StartState {
+ mb.mx.RLock()
+ defer mb.mx.RUnlock()
+ if mb.zettel == nil {
+ return box.StartStateStopped
+ }
+ return box.StartStateStarted
+}
func (mb *memBox) Start(context.Context) error {
mb.mx.Lock()
- mb.zettel = make(map[id.Zid]domain.Zettel)
+ mb.zettel = make(map[id.Zid]zettel.Zettel)
mb.curBytes = 0
mb.mx.Unlock()
mb.log.Trace().Int("max-zettel", int64(mb.maxZettel)).Int("max-bytes", int64(mb.maxBytes)).Msg("Start Box")
return nil
}
@@ -81,11 +89,11 @@
mb.mx.RLock()
defer mb.mx.RUnlock()
return len(mb.zettel) < mb.maxZettel
}
-func (mb *memBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) {
+func (mb *memBox) CreateZettel(_ context.Context, zettel zettel.Zettel) (id.Zid, error) {
mb.mx.Lock()
newBytes := mb.curBytes + zettel.Length()
if mb.maxZettel < len(mb.zettel) || mb.maxBytes < newBytes {
mb.mx.Unlock()
return id.Invalid, box.ErrCapacity
@@ -102,39 +110,35 @@
meta.Zid = zid
zettel.Meta = meta
mb.zettel[zid] = zettel
mb.curBytes = newBytes
mb.mx.Unlock()
- mb.notifyChanged(box.OnUpdate, zid)
+ mb.notifyChanged(zid)
mb.log.Trace().Zid(zid).Msg("CreateZettel")
return zid, nil
}
-func (mb *memBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
- mb.mx.RLock()
- zettel, ok := mb.zettel[zid]
- mb.mx.RUnlock()
- if !ok {
- return domain.Zettel{}, box.ErrNotFound
- }
- zettel.Meta = zettel.Meta.Clone()
- mb.log.Trace().Msg("GetZettel")
- return zettel, nil
-}
-
-func (mb *memBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
- mb.mx.RLock()
- zettel, ok := mb.zettel[zid]
- mb.mx.RUnlock()
- if !ok {
- return nil, box.ErrNotFound
- }
- mb.log.Trace().Msg("GetMeta")
- return zettel.Meta.Clone(), nil
-}
-
-func (mb *memBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.RetrievePredicate) error {
+func (mb *memBox) GetZettel(_ context.Context, zid id.Zid) (zettel.Zettel, error) {
+ mb.mx.RLock()
+ z, ok := mb.zettel[zid]
+ mb.mx.RUnlock()
+ if !ok {
+ return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid}
+ }
+ z.Meta = z.Meta.Clone()
+ mb.log.Trace().Msg("GetZettel")
+ return z, nil
+}
+
+func (mb *memBox) HasZettel(_ context.Context, zid id.Zid) bool {
+ mb.mx.RLock()
+ _, found := mb.zettel[zid]
+ mb.mx.RUnlock()
+ return found
+}
+
+func (mb *memBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error {
mb.mx.RLock()
defer mb.mx.RUnlock()
mb.log.Trace().Int("entries", int64(len(mb.zettel))).Msg("ApplyZid")
for zid := range mb.zettel {
if constraint(zid) {
@@ -142,11 +146,11 @@
}
}
return nil
}
-func (mb *memBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error {
+func (mb *memBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error {
mb.mx.RLock()
defer mb.mx.RUnlock()
mb.log.Trace().Int("entries", int64(len(mb.zettel))).Msg("ApplyMeta")
for zid, zettel := range mb.zettel {
if constraint(zid) {
@@ -156,11 +160,11 @@
}
}
return nil
}
-func (mb *memBox) CanUpdateZettel(_ context.Context, zettel domain.Zettel) bool {
+func (mb *memBox) CanUpdateZettel(_ context.Context, zettel zettel.Zettel) bool {
mb.mx.RLock()
defer mb.mx.RUnlock()
zid := zettel.Meta.Zid
if !zid.IsValid() {
return false
@@ -171,14 +175,14 @@
newBytes -= prevZettel.Length()
}
return newBytes < mb.maxBytes
}
-func (mb *memBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error {
+func (mb *memBox) UpdateZettel(_ context.Context, zettel zettel.Zettel) error {
m := zettel.Meta.Clone()
if !m.Zid.IsValid() {
- return &box.ErrInvalidID{Zid: m.Zid}
+ return box.ErrInvalidZid{Zid: m.Zid.String()}
}
mb.mx.Lock()
newBytes := mb.curBytes + zettel.Length()
if prevZettel, found := mb.zettel[m.Zid]; found {
@@ -191,11 +195,11 @@
zettel.Meta = m
mb.zettel[m.Zid] = zettel
mb.curBytes = newBytes
mb.mx.Unlock()
- mb.notifyChanged(box.OnUpdate, m.Zid)
+ mb.notifyChanged(m.Zid)
mb.log.Trace().Msg("UpdateZettel")
return nil
}
func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true }
@@ -203,27 +207,27 @@
func (mb *memBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error {
mb.mx.Lock()
zettel, ok := mb.zettel[curZid]
if !ok {
mb.mx.Unlock()
- return box.ErrNotFound
+ return box.ErrZettelNotFound{Zid: curZid}
}
// Check that there is no zettel with newZid
if _, ok = mb.zettel[newZid]; ok {
mb.mx.Unlock()
- return &box.ErrInvalidID{Zid: newZid}
+ return box.ErrInvalidZid{Zid: newZid.String()}
}
meta := zettel.Meta.Clone()
meta.Zid = newZid
zettel.Meta = meta
mb.zettel[newZid] = zettel
delete(mb.zettel, curZid)
mb.mx.Unlock()
- mb.notifyChanged(box.OnDelete, curZid)
- mb.notifyChanged(box.OnUpdate, newZid)
+ mb.notifyChanged(curZid)
+ mb.notifyChanged(newZid)
mb.log.Trace().Msg("RenameZettel")
return nil
}
func (mb *memBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool {
@@ -236,16 +240,16 @@
func (mb *memBox) DeleteZettel(_ context.Context, zid id.Zid) error {
mb.mx.Lock()
oldZettel, found := mb.zettel[zid]
if !found {
mb.mx.Unlock()
- return box.ErrNotFound
+ return box.ErrZettelNotFound{Zid: zid}
}
delete(mb.zettel, zid)
mb.curBytes -= oldZettel.Length()
mb.mx.Unlock()
- mb.notifyChanged(box.OnDelete, zid)
+ mb.notifyChanged(zid)
mb.log.Trace().Msg("DeleteZettel")
return nil
}
func (mb *memBox) ReadStats(st *box.ManagedBoxStats) {
Index: box/notify/directory.go
==================================================================
--- box/notify/directory.go
+++ box/notify/directory.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -17,68 +17,80 @@
"regexp"
"strings"
"sync"
"zettelstore.de/z/box"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/kernel"
"zettelstore.de/z/logger"
"zettelstore.de/z/parser"
- "zettelstore.de/z/search"
+ "zettelstore.de/z/query"
"zettelstore.de/z/strfun"
+ "zettelstore.de/z/zettel/id"
)
type entrySet map[id.Zid]*DirEntry
-// directoryState signal the internal state of the service.
+// DirServiceState signal the internal state of the service.
//
// The following state transitions are possible:
// --newDirService--> dsCreated
// dsCreated --Start--> dsStarting
// dsStarting --last list notification--> dsWorking
// dsWorking --directory missing--> dsMissing
// dsMissing --last list notification--> dsWorking
// --Stop--> dsStopping
-type directoryState uint8
+type DirServiceState uint8
const (
- dsCreated directoryState = iota
- dsStarting // Reading inital scan
- dsWorking // Initial scan complete, fully operational
- dsMissing // Directory is missing
- dsStopping // Service is shut down
+ DsCreated DirServiceState = iota
+ DsStarting // Reading inital scan
+ DsWorking // Initial scan complete, fully operational
+ DsMissing // Directory is missing
+ DsStopping // Service is shut down
)
// DirService specifies a directory service for file based zettel.
type DirService struct {
+ box box.ManagedBox
log *logger.Logger
dirPath string
notifier Notifier
infos chan<- box.UpdateInfo
mx sync.RWMutex // protects status, entries
- state directoryState
+ state DirServiceState
entries entrySet
}
// ErrNoDirectory signals missing directory data.
var ErrNoDirectory = errors.New("unable to retrieve zettel directory information")
// NewDirService creates a new directory service.
-func NewDirService(log *logger.Logger, notifier Notifier, chci chan<- box.UpdateInfo) *DirService {
+func NewDirService(box box.ManagedBox, log *logger.Logger, notifier Notifier, chci chan<- box.UpdateInfo) *DirService {
return &DirService{
+ box: box,
log: log,
notifier: notifier,
infos: chci,
- state: dsCreated,
+ state: DsCreated,
}
}
+
+// State the current service state.
+func (ds *DirService) State() DirServiceState {
+ ds.mx.RLock()
+ state := ds.state
+ ds.mx.RUnlock()
+ return state
+}
// Start the directory service.
func (ds *DirService) Start() {
ds.mx.Lock()
- ds.state = dsStarting
+ ds.state = DsStarting
ds.mx.Unlock()
- go ds.updateEvents()
+ var newEntries entrySet
+ go ds.updateEvents(newEntries)
}
// Refresh the directory entries.
func (ds *DirService) Refresh() {
ds.notifier.Refresh()
@@ -85,11 +97,11 @@
}
// Stop the directory service.
func (ds *DirService) Stop() {
ds.mx.Lock()
- ds.state = dsStopping
+ ds.state = DsStopping
ds.mx.Unlock()
ds.notifier.Close()
}
func (ds *DirService) logMissingEntry(action string) error {
@@ -107,11 +119,11 @@
}
return len(ds.entries)
}
// GetDirEntries returns a list of directory entries, which satisfy the given constraint.
-func (ds *DirService) GetDirEntries(constraint search.RetrievePredicate) []*DirEntry {
+func (ds *DirService) GetDirEntries(constraint query.RetrievePredicate) []*DirEntry {
ds.mx.RLock()
defer ds.mx.RUnlock()
if ds.entries == nil {
return nil
}
@@ -177,11 +189,11 @@
defer ds.mx.Unlock()
if ds.entries == nil {
return DirEntry{}, ds.logMissingEntry("rename")
}
if _, found := ds.entries[newZid]; found {
- return DirEntry{}, &box.ErrInvalidID{Zid: newZid}
+ return DirEntry{}, box.ErrInvalidZid{Zid: newZid.String()}
}
oldZid := oldEntry.Zid
newEntry := DirEntry{
Zid: newZid,
MetaName: renameFilename(oldEntry.MetaName, oldZid, newZid),
@@ -210,68 +222,87 @@
}
delete(ds.entries, zid)
return nil
}
-func (ds *DirService) updateEvents() {
- var newEntries entrySet
+func (ds *DirService) updateEvents(newEntries entrySet) {
+ // Something may panic. Ensure a running service.
+ defer func() {
+ if ri := recover(); ri != nil {
+ kernel.Main.LogRecover("DirectoryService", ri)
+ go ds.updateEvents(newEntries)
+ }
+ }()
+
for ev := range ds.notifier.Events() {
- ds.mx.RLock()
- state := ds.state
- ds.mx.RUnlock()
-
- if msg := ds.log.Trace(); msg.Enabled() {
- msg.Uint("state", uint64(state)).Str("op", ev.Op.String()).Str("name", ev.Name).Msg("notifyEvent")
- }
- if state == dsStopping {
+ e, ok := ds.handleEvent(ev, newEntries)
+ if !ok {
break
}
-
- switch ev.Op {
- case Error:
- newEntries = nil
- if state != dsMissing {
- ds.log.Warn().Err(ev.Err).Msg("Notifier confused")
- }
- case Make:
- newEntries = make(entrySet)
- case List:
- if ev.Name == "" {
- zids := getNewZids(newEntries)
- ds.mx.Lock()
- fromMissing := ds.state == dsMissing
- prevEntries := ds.entries
- ds.entries = newEntries
- ds.state = dsWorking
- ds.mx.Unlock()
- newEntries = nil
- ds.onCreateDirectory(zids, prevEntries)
- if fromMissing {
- ds.log.Info().Str("path", ds.dirPath).Msg("Zettel directory found")
- }
- } else if newEntries != nil {
- ds.onUpdateFileEvent(newEntries, ev.Name)
- }
- case Destroy:
- newEntries = nil
- ds.onDestroyDirectory()
- ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing")
- case Update:
- ds.mx.Lock()
- zid := ds.onUpdateFileEvent(ds.entries, ev.Name)
- ds.mx.Unlock()
- if zid != id.Invalid {
- ds.notifyChange(box.OnUpdate, zid)
- }
- case Delete:
- ds.mx.Lock()
- ds.onDeleteFileEvent(ds.entries, ev.Name)
- ds.mx.Unlock()
- default:
- ds.log.Warn().Str("event", fmt.Sprintf("%v", ev)).Msg("Unknown zettel notification event")
- }
- }
+ newEntries = e
+ }
+}
+func (ds *DirService) handleEvent(ev Event, newEntries entrySet) (entrySet, bool) {
+ ds.mx.RLock()
+ state := ds.state
+ ds.mx.RUnlock()
+
+ if msg := ds.log.Trace(); msg.Enabled() {
+ msg.Uint("state", uint64(state)).Str("op", ev.Op.String()).Str("name", ev.Name).Msg("notifyEvent")
+ }
+ if state == DsStopping {
+ return nil, false
+ }
+
+ switch ev.Op {
+ case Error:
+ newEntries = nil
+ if state != DsMissing {
+ ds.log.Warn().Err(ev.Err).Msg("Notifier confused")
+ }
+ case Make:
+ newEntries = make(entrySet)
+ case List:
+ if ev.Name == "" {
+ zids := getNewZids(newEntries)
+ ds.mx.Lock()
+ fromMissing := ds.state == DsMissing
+ prevEntries := ds.entries
+ ds.entries = newEntries
+ ds.state = DsWorking
+ ds.mx.Unlock()
+ ds.onCreateDirectory(zids, prevEntries)
+ if fromMissing {
+ ds.log.Info().Str("path", ds.dirPath).Msg("Zettel directory found")
+ }
+ return nil, true
+ }
+ if newEntries != nil {
+ ds.onUpdateFileEvent(newEntries, ev.Name)
+ }
+ case Destroy:
+ ds.onDestroyDirectory()
+ ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing")
+ return nil, true
+ case Update:
+ ds.mx.Lock()
+ zid := ds.onUpdateFileEvent(ds.entries, ev.Name)
+ ds.mx.Unlock()
+ if zid != id.Invalid {
+ ds.notifyChange(zid)
+ }
+ case Delete:
+ ds.mx.Lock()
+ zid := ds.onDeleteFileEvent(ds.entries, ev.Name)
+ ds.mx.Unlock()
+ if zid != id.Invalid {
+ ds.notifyChange(zid)
+ }
+ default:
+ ds.log.Warn().Str("event", fmt.Sprintf("%v", ev)).Msg("Unknown zettel notification event")
+ }
+ return newEntries, true
}
func getNewZids(entries entrySet) id.Slice {
zids := make(id.Slice, 0, len(entries))
for zid := range entries {
@@ -280,29 +311,29 @@
return zids
}
func (ds *DirService) onCreateDirectory(zids id.Slice, prevEntries entrySet) {
for _, zid := range zids {
- ds.notifyChange(box.OnUpdate, zid)
+ ds.notifyChange(zid)
delete(prevEntries, zid)
}
// These were previously stored, by are not found now.
// Notify system that these were deleted, e.g. for updating the index.
for zid := range prevEntries {
- ds.notifyChange(box.OnDelete, zid)
+ ds.notifyChange(zid)
}
}
func (ds *DirService) onDestroyDirectory() {
ds.mx.Lock()
entries := ds.entries
ds.entries = nil
- ds.state = dsMissing
+ ds.state = DsMissing
ds.mx.Unlock()
for zid := range entries {
- ds.notifyChange(box.OnDelete, zid)
+ ds.notifyChange(zid)
}
}
var validFileName = regexp.MustCompile(`^(\d{14})`)
@@ -344,30 +375,31 @@
if dupName1 != "" {
ds.log.Warn().Str("name", dupName1).Msg("Duplicate content (is ignored)")
if dupName2 != "" {
ds.log.Warn().Str("name", dupName2).Msg("Duplicate content (is ignored)")
}
+ return id.Invalid
}
return zid
}
-func (ds *DirService) onDeleteFileEvent(entries entrySet, name string) {
+func (ds *DirService) onDeleteFileEvent(entries entrySet, name string) id.Zid {
if entries == nil {
- return
+ return id.Invalid
}
zid := seekZid(name)
if zid == id.Invalid {
- return
+ return id.Invalid
}
entry, found := entries[zid]
if !found {
- return
+ return zid
}
for i, dupName := range entry.UselessFiles {
if dupName == name {
removeDuplicate(entry, i)
- return
+ return zid
}
}
if name == entry.ContentName {
entry.ContentName = ""
entry.ContentExt = ""
@@ -376,12 +408,12 @@
entry.MetaName = ""
ds.replayUpdateUselessFiles(entry)
}
if entry.ContentName == "" && entry.MetaName == "" {
delete(entries, zid)
- ds.notifyChange(box.OnDelete, zid)
}
+ return zid
}
func removeDuplicate(entry *DirEntry, i int) {
if len(entry.UselessFiles) == 1 {
entry.UselessFiles = nil
@@ -546,12 +578,15 @@
if newExt == "zmk" {
return true
}
oldInfo := parser.Get(oldExt)
newInfo := parser.Get(newExt)
- if oldTextParser := oldInfo.IsTextParser; oldTextParser != newInfo.IsTextParser {
- return !oldTextParser
+ if oldASTParser := oldInfo.IsASTParser; oldASTParser != newInfo.IsASTParser {
+ return !oldASTParser
+ }
+ if oldTextFormat := oldInfo.IsTextFormat; oldTextFormat != newInfo.IsTextFormat {
+ return !oldTextFormat
}
if oldImageFormat := oldInfo.IsImageFormat; oldImageFormat != newInfo.IsImageFormat {
return oldImageFormat
}
if oldPrimary := primarySyntax.Has(oldExt); oldPrimary != primarySyntax.Has(newExt) {
@@ -565,11 +600,11 @@
return newLen < oldLen
}
return newExt < oldExt
}
-func (ds *DirService) notifyChange(reason box.UpdateReason, zid id.Zid) {
+func (ds *DirService) notifyChange(zid id.Zid) {
if chci := ds.infos; chci != nil {
- ds.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChange")
- chci <- box.UpdateInfo{Reason: reason, Zid: zid}
+ ds.log.Trace().Zid(zid).Msg("notifyChange")
+ chci <- box.UpdateInfo{Box: ds.box, Reason: box.OnZettel, Zid: zid}
}
}
Index: box/notify/directory_test.go
==================================================================
--- box/notify/directory_test.go
+++ box/notify/directory_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2022 Detlef Stern
+// Copyright (c) 2022-present 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
@@ -11,17 +11,18 @@
package notify
import (
"testing"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
_ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser.
+ _ "zettelstore.de/z/parser/draw" // Allow to use draw parser.
_ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser.
_ "zettelstore.de/z/parser/none" // Allow to use none parser.
_ "zettelstore.de/z/parser/plain" // Allow to use plain parser.
_ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser.
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
func TestSeekZid(t *testing.T) {
testcases := []struct {
name string
@@ -46,15 +47,20 @@
}
func TestNewExtIsBetter(t *testing.T) {
extVals := []string{
// Main Formats
- api.ValueSyntaxZmk, "markdown", "md",
+ meta.SyntaxZmk, meta.SyntaxDraw, meta.SyntaxMarkdown, meta.SyntaxMD,
// Other supported text formats
- "css", "txt", api.ValueSyntaxHTML, api.ValueSyntaxNone, "mustache", api.ValueSyntaxText, "plain",
- // Supported graphics formats
- api.ValueSyntaxGif, "png", api.ValueSyntaxSVG, "jpeg", "jpg",
+ meta.SyntaxCSS, meta.SyntaxSxn, meta.SyntaxTxt, meta.SyntaxHTML,
+ meta.SyntaxText, meta.SyntaxPlain,
+ // Supported text graphics formats
+ meta.SyntaxSVG,
+ meta.SyntaxNone,
+ // Supported binary graphic formats
+ meta.SyntaxGif, meta.SyntaxPNG, meta.SyntaxJPEG, meta.SyntaxWebp, meta.SyntaxJPG,
+
// Unsupported syntax values
"gz", "cpp", "tar", "cppc",
}
for oldI, oldExt := range extVals {
for newI, newExt := range extVals {
Index: box/notify/entry.go
==================================================================
--- box/notify/entry.go
+++ box/notify/entry.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -11,15 +11,15 @@
package notify
import (
"path/filepath"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/parser"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
const (
extZettel = "zettel" // file contains metadata and content
extBin = "bin" // file contains binary content
@@ -46,11 +46,11 @@
func (e *DirEntry) HasMetaInContent() bool {
return e.IsValid() && extIsMetaAndContent(e.ContentExt)
}
// SetupFromMetaContent fills entry data based on metadata and zettel content.
-func (e *DirEntry) SetupFromMetaContent(m *meta.Meta, content domain.Content, getZettelFileSyntax func() []string) {
+func (e *DirEntry) SetupFromMetaContent(m *meta.Meta, content zettel.Content, getZettelFileSyntax func() []string) {
if e.Zid != m.Zid {
panic("Zid differ")
}
if contentName := e.ContentName; contentName != "" {
if !extIsMetaAndContent(e.ContentExt) && e.MetaName == "" {
@@ -78,11 +78,11 @@
e.MetaName = e.calcBaseName(e.ContentName)
}
}
}
-func contentExtWithMeta(syntax string, content domain.Content) string {
+func contentExtWithMeta(syntax string, content zettel.Content) string {
p := parser.Get(syntax)
if content.IsBinary() {
if p.IsImageFormat {
return syntax
}
@@ -97,11 +97,11 @@
func calcContentExt(syntax string, yamlSep bool, getZettelFileSyntax func() []string) string {
if yamlSep {
return extZettel
}
switch syntax {
- case api.ValueSyntaxNone, api.ValueSyntaxZmk:
+ case meta.SyntaxNone, meta.SyntaxZmk:
return extZettel
}
for _, s := range getZettelFileSyntax() {
if s == syntax {
return extZettel
Index: box/notify/fsdir.go
==================================================================
--- box/notify/fsdir.go
+++ box/notify/fsdir.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -92,107 +92,141 @@
defer close(fsdn.events)
defer close(fsdn.refresh)
if !listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) {
return
}
+
for fsdn.readAndProcessEvent() {
}
}
func (fsdn *fsdirNotifier) readAndProcessEvent() bool {
select {
case <-fsdn.done:
+ fsdn.traceDone(1)
return false
default:
}
select {
case <-fsdn.done:
+ fsdn.traceDone(2)
return false
case <-fsdn.refresh:
+ fsdn.log.Trace().Msg("refresh")
listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done)
case err, ok := <-fsdn.base.Errors:
+ fsdn.log.Trace().Err(err).Bool("ok", ok).Msg("got errors")
if !ok {
return false
}
select {
case fsdn.events <- Event{Op: Error, Err: err}:
case <-fsdn.done:
+ fsdn.traceDone(3)
return false
}
case ev, ok := <-fsdn.base.Events:
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Bool("ok", ok).Msg("file event")
if !ok {
return false
}
if !fsdn.processEvent(&ev) {
return false
}
}
return true
}
+
+func (fsdn *fsdirNotifier) traceDone(pos int64) {
+ fsdn.log.Trace().Int("i", pos).Msg("done with read and process events")
+}
func (fsdn *fsdirNotifier) processEvent(ev *fsnotify.Event) bool {
if strings.HasPrefix(ev.Name, fsdn.path) {
if len(ev.Name) == len(fsdn.path) {
return fsdn.processDirEvent(ev)
}
return fsdn.processFileEvent(ev)
}
+ fsdn.log.Trace().Str("path", fsdn.path).Str("name", ev.Name).Str("op", ev.Op.String()).Msg("event does not match")
return true
}
-const deleteFsOps = fsnotify.Remove | fsnotify.Rename
-const updateFsOps = fsnotify.Create | fsnotify.Write
-
func (fsdn *fsdirNotifier) processDirEvent(ev *fsnotify.Event) bool {
- if ev.Op&deleteFsOps != 0 {
+ if ev.Has(fsnotify.Remove) || ev.Has(fsnotify.Rename) {
fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory removed")
fsdn.base.Remove(fsdn.path)
select {
case fsdn.events <- Event{Op: Destroy}:
case <-fsdn.done:
+ fsdn.log.Trace().Int("i", 1).Msg("done dir event processing")
return false
}
return true
}
- if ev.Op&fsnotify.Create != 0 {
+
+ if ev.Has(fsnotify.Create) {
err := fsdn.base.Add(fsdn.path)
if err != nil {
fsdn.log.IfErr(err).Str("name", fsdn.path).Msg("Unable to add directory")
select {
case fsdn.events <- Event{Op: Error, Err: err}:
case <-fsdn.done:
+ fsdn.log.Trace().Int("i", 2).Msg("done dir event processing")
return false
}
}
fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory added")
return listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done)
}
+
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("Directory processed")
return true
}
func (fsdn *fsdirNotifier) processFileEvent(ev *fsnotify.Event) bool {
- if ev.Op&deleteFsOps != 0 {
- fsdn.log.Trace().Str("name", ev.Name).Uint("op", uint64(ev.Op)).Msg("File deleted")
- select {
- case fsdn.events <- Event{Op: Delete, Name: filepath.Base(ev.Name)}:
- case <-fsdn.done:
- return false
- }
- return true
- }
- if ev.Op&updateFsOps != 0 {
- if fi, err := os.Lstat(ev.Name); err != nil || !fi.Mode().IsRegular() {
- return true
- }
- fsdn.log.Trace().Str("name", ev.Name).Uint("op", uint64(ev.Op)).Msg("File updated")
- select {
- case fsdn.events <- Event{Op: Update, Name: filepath.Base(ev.Name)}:
- case <-fsdn.done:
- return false
- }
+ if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
+ if fi, err := os.Lstat(ev.Name); err != nil || !fi.Mode().IsRegular() {
+ regular := err == nil && fi.Mode().IsRegular()
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Err(err).Bool("regular", regular).Msg("error with file")
+ return true
+ }
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File updated")
+ return fsdn.sendEvent(Update, filepath.Base(ev.Name))
+ }
+
+ if ev.Has(fsnotify.Rename) {
+ fi, err := os.Lstat(ev.Name)
+ if err != nil {
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File deleted")
+ return fsdn.sendEvent(Delete, filepath.Base(ev.Name))
+ }
+ if fi.Mode().IsRegular() {
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File updated")
+ return fsdn.sendEvent(Update, filepath.Base(ev.Name))
+ }
+ fsdn.log.Trace().Str("name", ev.Name).Msg("File not regular")
+ return true
+ }
+
+ if ev.Has(fsnotify.Remove) {
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File deleted")
+ return fsdn.sendEvent(Delete, filepath.Base(ev.Name))
+ }
+
+ fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File processed")
+ return true
+}
+
+func (fsdn *fsdirNotifier) sendEvent(op EventOp, filename string) bool {
+ select {
+ case fsdn.events <- Event{Op: op, Name: filename}:
+ case <-fsdn.done:
+ fsdn.log.Trace().Msg("done file event processing")
+ return false
}
return true
}
func (fsdn *fsdirNotifier) Close() {
close(fsdn.done)
}
Index: box/notify/helper.go
==================================================================
--- box/notify/helper.go
+++ box/notify/helper.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
Index: box/notify/notify.go
==================================================================
--- box/notify/notify.go
+++ box/notify/notify.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -33,19 +33,19 @@
// Error signals a detected error. Details are in Event.Err.
//
// Make signals that the container is detected. List events will follow.
//
// List signals a found file, if Event.Name is not empty. Otherwise it signals
-// the end of files within the container.
+// the end of files within the container.
//
// Destroy signals that the container is not there any more. It might me Make later again.
//
-// Update signals that file Event.Name was created/updated. File name is relative
-// to the container.
+// Update signals that file Event.Name was created/updated.
+// File name is relative to the container.
//
-// Delete signals that file Event.Name was removed. File name is relative to
-// the container's name.
+// Delete signals that file Event.Name was removed.
+// File name is relative to the container's name.
const (
_ EventOp = iota
Error // Error while operating
Make // Make container
List // List container
Index: box/notify/simpledir.go
==================================================================
--- box/notify/simpledir.go
+++ box/notify/simpledir.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021 Detlef Stern
+// Copyright (c) 2021-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -42,20 +42,20 @@
return sdn, nil
}
// NewSimpleZipNotifier creates a zip-file based notifier that will not receive
// any notifications from the operating system.
-func NewSimpleZipNotifier(log *logger.Logger, zipPath string) (Notifier, error) {
+func NewSimpleZipNotifier(log *logger.Logger, zipPath string) Notifier {
sdn := &simpleDirNotifier{
log: log,
events: make(chan Event),
done: make(chan struct{}),
refresh: make(chan struct{}),
fetcher: newZipPathFetcher(zipPath),
}
go sdn.eventLoop()
- return sdn, nil
+ return sdn
}
func (sdn *simpleDirNotifier) Events() <-chan Event {
return sdn.events
}
Index: cmd/cmd_file.go
==================================================================
--- cmd/cmd_file.go
+++ cmd/cmd_file.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -9,22 +9,23 @@
//-----------------------------------------------------------------------------
package cmd
import (
+ "context"
"flag"
"fmt"
"io"
"os"
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/encoder"
"zettelstore.de/z/input"
"zettelstore.de/z/parser"
+ "zettelstore.de/z/zettel"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
// ---------- Subcommand: file -----------------------------------------------
func cmdFile(fs *flag.FlagSet) (int, error) {
@@ -32,15 +33,16 @@
m, inp, err := getInput(fs.Args())
if m == nil {
return 2, err
}
z := parser.ParseZettel(
- domain.Zettel{
+ context.Background(),
+ zettel.Zettel{
Meta: m,
- Content: domain.NewContent(inp.Src[inp.Pos:]),
+ Content: zettel.NewContent(inp.Src[inp.Pos:]),
},
- m.GetDefault(api.KeySyntax, api.ValueSyntaxZmk),
+ m.GetDefault(api.KeySyntax, meta.SyntaxZmk),
nil,
)
encdr := encoder.Create(api.Encoder(enc))
if encdr == nil {
fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc)
Index: cmd/cmd_password.go
==================================================================
--- cmd/cmd_password.go
+++ cmd/cmd_password.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
@@ -15,13 +15,13 @@
"fmt"
"os"
"golang.org/x/term"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/auth/cred"
- "zettelstore.de/z/domain/id"
+ "zettelstore.de/z/zettel/id"
)
// ---------- Subcommand: password -------------------------------------------
func cmdPassword(fs *flag.FlagSet) (int, error) {
Index: cmd/cmd_run.go
==================================================================
--- cmd/cmd_run.go
+++ cmd/cmd_run.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -9,20 +9,23 @@
//-----------------------------------------------------------------------------
package cmd
import (
+ "context"
"flag"
+ "net/http"
"zettelstore.de/z/auth"
"zettelstore.de/z/box"
"zettelstore.de/z/config"
"zettelstore.de/z/kernel"
"zettelstore.de/z/usecase"
"zettelstore.de/z/web/adapter/api"
"zettelstore.de/z/web/adapter/webui"
"zettelstore.de/z/web/server"
+ "zettelstore.de/z/zettel/meta"
)
// ---------- Subcommand: run ------------------------------------------------
func flgRun(fs *flag.FlagSet) {
@@ -44,98 +47,88 @@
kernel.Main.WaitForShutdown()
return exitCode, err
}
func setupRouting(webSrv server.Server, boxManager box.Manager, authManager auth.Manager, rtConfig config.Config) {
- protectedBoxManager, authPolicy := authManager.BoxWithPolicy(webSrv, boxManager, rtConfig)
+ protectedBoxManager, authPolicy := authManager.BoxWithPolicy(boxManager, rtConfig)
kern := kernel.Main
webLog := kern.GetLogger(kernel.WebService)
- a := api.New(
- webLog.Clone().Str("adapter", "api").Child(),
- webSrv, authManager, authManager, webSrv, rtConfig, authPolicy)
- wui := webui.New(
- webLog.Clone().Str("adapter", "wui").Child(),
- webSrv, authManager, rtConfig, authManager, boxManager, authPolicy)
-
- authLog := kern.GetLogger(kernel.AuthService)
- ucLog := kern.GetLogger(kernel.CoreService).WithUser(webSrv)
- ucAuthenticate := usecase.NewAuthenticate(authLog, authManager, authManager, boxManager)
- ucIsAuth := usecase.NewIsAuthenticated(ucLog, webSrv, authManager)
- ucCreateZettel := usecase.NewCreateZettel(ucLog, rtConfig, protectedBoxManager)
- ucGetMeta := usecase.NewGetMeta(protectedBoxManager)
- ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager)
+
+ var getUser getUserImpl
+ logAuth := kern.GetLogger(kernel.AuthService)
+ logUc := kern.GetLogger(kernel.CoreService).WithUser(&getUser)
+ ucGetUser := usecase.NewGetUser(authManager, boxManager)
+ ucAuthenticate := usecase.NewAuthenticate(logAuth, authManager, &ucGetUser)
+ ucIsAuth := usecase.NewIsAuthenticated(logUc, &getUser, authManager)
+ ucCreateZettel := usecase.NewCreateZettel(logUc, rtConfig, protectedBoxManager)
+ ucGetAllZettel := usecase.NewGetAllZettel(protectedBoxManager)
ucGetZettel := usecase.NewGetZettel(protectedBoxManager)
ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel)
- ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta)
- ucListMeta := usecase.NewListMeta(protectedBoxManager)
+ ucQuery := usecase.NewQuery(protectedBoxManager)
+ ucEvaluate := usecase.NewEvaluate(rtConfig, &ucGetZettel, &ucQuery)
+ ucQuery.SetEvaluate(&ucEvaluate)
ucListSyntax := usecase.NewListSyntax(protectedBoxManager)
ucListRoles := usecase.NewListRoles(protectedBoxManager)
- ucListTags := usecase.NewListTags(protectedBoxManager)
- ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig)
- ucDelete := usecase.NewDeleteZettel(ucLog, protectedBoxManager)
- ucUpdate := usecase.NewUpdateZettel(ucLog, protectedBoxManager)
- ucRename := usecase.NewRenameZettel(ucLog, protectedBoxManager)
- ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig)
- ucRefresh := usecase.NewRefresh(ucLog, protectedBoxManager)
+ ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager)
+ ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager)
+ ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager)
+ ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager)
ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string))
+ a := api.New(
+ webLog.Clone().Str("adapter", "api").Child(),
+ webSrv, authManager, authManager, rtConfig, authPolicy)
+ wui := webui.New(
+ webLog.Clone().Str("adapter", "wui").Child(),
+ webSrv, authManager, rtConfig, authManager, boxManager, authPolicy, &ucEvaluate)
+
webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager))
+ if assetDir := kern.GetConfig(kernel.WebService, kernel.WebAssetDir).(string); assetDir != "" {
+ const assetPrefix = "/assets/"
+ webSrv.Handle(assetPrefix, http.StripPrefix(assetPrefix, http.FileServer(http.Dir(assetDir))))
+ webSrv.Handle("/favicon.ico", wui.MakeFaviconHandler(assetDir))
+ }
// Web user interface
if !authManager.IsReadonly() {
- webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler(
- ucGetMeta, &ucEvaluate))
+ webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler(ucGetZettel))
webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(&ucRename))
+ webSrv.AddListRoute('c', server.MethodGet, wui.MakeGetZettelFromListHandler(&ucQuery, &ucEvaluate, ucListRoles, ucListSyntax))
+ webSrv.AddListRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCreateZettelHandler(
ucGetZettel, &ucCreateZettel, ucListRoles, ucListSyntax))
webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
- webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler(
- ucGetMeta, ucGetAllMeta, &ucEvaluate))
+ webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel, ucGetAllZettel))
webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete))
webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax))
webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate))
}
webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh))
- webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler(
- ucListMeta, ucListRoles, ucListTags, &ucEvaluate))
- webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler(
- &ucEvaluate, ucGetMeta))
+ webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler(&ucQuery))
+ webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler(&ucEvaluate, ucGetZettel))
webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler())
webSrv.AddListRoute('i', server.MethodPost, wui.MakePostLoginHandler(&ucAuthenticate))
webSrv.AddZettelRoute('i', server.MethodGet, wui.MakeGetInfoHandler(
- ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta, ucUnlinkedRefs))
- webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler(
- ucZettelContext, &ucEvaluate))
+ ucParseZettel, &ucEvaluate, ucGetZettel, ucGetAllZettel, &ucQuery))
// API
webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate))
webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler())
- webSrv.AddListRoute('j', server.MethodGet, a.MakeListMetaHandler(ucListMeta))
- webSrv.AddZettelRoute('j', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel))
- webSrv.AddListRoute('m', server.MethodGet, a.MakeListMapMetaHandler(ucListRoles, ucListTags))
- webSrv.AddZettelRoute('m', server.MethodGet, a.MakeGetMetaHandler(ucGetMeta))
- webSrv.AddZettelRoute('o', server.MethodGet, a.MakeGetOrderHandler(
- usecase.NewZettelOrder(protectedBoxManager, ucEvaluate)))
- webSrv.AddZettelRoute('p', server.MethodGet, a.MakeGetParsedZettelHandler(ucParseZettel))
- webSrv.AddZettelRoute('u', server.MethodGet, a.MakeListUnlinkedMetaHandler(
- ucGetMeta, ucUnlinkedRefs, &ucEvaluate))
- webSrv.AddZettelRoute('v', server.MethodGet, a.MakeGetEvalZettelHandler(ucEvaluate))
webSrv.AddListRoute('x', server.MethodGet, a.MakeGetDataHandler(ucVersion))
webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh))
- webSrv.AddZettelRoute('x', server.MethodGet, a.MakeZettelContextHandler(ucZettelContext))
- webSrv.AddListRoute('z', server.MethodGet, a.MakeListPlainHandler(ucListMeta))
- webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetPlainZettelHandler(ucGetZettel))
+ webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(&ucQuery))
+ webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel, ucParseZettel, ucEvaluate))
if !authManager.IsReadonly() {
- webSrv.AddListRoute('j', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel))
- webSrv.AddZettelRoute('j', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate))
- webSrv.AddZettelRoute('j', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete))
- webSrv.AddZettelRoute('j', server.MethodMove, a.MakeRenameZettelHandler(&ucRename))
- webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreatePlainZettelHandler(&ucCreateZettel))
- webSrv.AddZettelRoute('z', server.MethodPut, a.MakeUpdatePlainZettelHandler(&ucUpdate))
+ webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel))
+ webSrv.AddZettelRoute('z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate))
webSrv.AddZettelRoute('z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete))
webSrv.AddZettelRoute('z', server.MethodMove, a.MakeRenameZettelHandler(&ucRename))
}
if authManager.WithAuth() {
webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager))
}
}
+
+type getUserImpl struct{}
+
+func (*getUserImpl) GetUser(ctx context.Context) *meta.Meta { return server.GetUser(ctx) }
Index: cmd/command.go
==================================================================
--- cmd/command.go
+++ cmd/command.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -11,11 +11,11 @@
package cmd
import (
"flag"
- "zettelstore.de/c/maps"
+ "zettelstore.de/client.fossil/maps"
"zettelstore.de/z/logger"
)
// Command stores information about commands / sub-commands.
type Command struct {
@@ -46,11 +46,11 @@
}
if _, ok := commands[cmd.Name]; ok {
panic("Command already registered: " + cmd.Name)
}
cmd.flags = flag.NewFlagSet(cmd.Name, flag.ExitOnError)
- cmd.flags.String("l", logger.InfoLevel.String(), "global log level")
+ cmd.flags.String("l", logger.InfoLevel.String(), "log level specification")
if cmd.SetFlags != nil {
cmd.SetFlags(cmd.flags)
}
commands[cmd.Name] = cmd
DELETED cmd/fd_limit.go
Index: cmd/fd_limit.go
==================================================================
--- cmd/fd_limit.go
+++ cmd/fd_limit.go
@@ -1,16 +0,0 @@
-//-----------------------------------------------------------------------------
-// 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.
-//-----------------------------------------------------------------------------
-
-//go:build !darwin
-// +build !darwin
-
-package cmd
-
-func raiseFdLimit() error { return nil }
DELETED cmd/fd_limit_raise.go
Index: cmd/fd_limit_raise.go
==================================================================
--- cmd/fd_limit_raise.go
+++ cmd/fd_limit_raise.go
@@ -1,51 +0,0 @@
-//-----------------------------------------------------------------------------
-// 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.
-//-----------------------------------------------------------------------------
-
-//go:build darwin
-// +build darwin
-
-package cmd
-
-import (
- "fmt"
- "syscall"
-
- "zettelstore.de/z/kernel"
-)
-
-const minFiles = 1048576
-
-func raiseFdLimit() error {
- var rLimit syscall.Rlimit
- err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- return err
- }
- if rLimit.Cur >= minFiles {
- return nil
- }
- rLimit.Cur = minFiles
- if rLimit.Cur > rLimit.Max {
- rLimit.Cur = rLimit.Max
- }
- err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- return err
- }
- err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
- if err != nil {
- return err
- }
- if rLimit.Cur < minFiles {
- msg := fmt.Sprintf("Make sure you have no more than %d files in all your boxes if you enabled notification\n", rLimit.Cur)
- kernel.Main.GetKernelLogger().Mandatory().Msg(msg)
- }
- return nil
-}
Index: cmd/main.go
==================================================================
--- cmd/main.go
+++ cmd/main.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -9,33 +9,34 @@
//-----------------------------------------------------------------------------
package cmd
import (
- "errors"
+ "crypto/sha256"
"flag"
"fmt"
"net"
"net/url"
"os"
"runtime/debug"
"strconv"
"strings"
+ "time"
- "zettelstore.de/c/api"
+ "zettelstore.de/client.fossil/api"
"zettelstore.de/z/auth"
"zettelstore.de/z/auth/impl"
"zettelstore.de/z/box"
"zettelstore.de/z/box/compbox"
"zettelstore.de/z/box/manager"
"zettelstore.de/z/config"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
"zettelstore.de/z/input"
"zettelstore.de/z/kernel"
"zettelstore.de/z/logger"
"zettelstore.de/z/web/server"
+ "zettelstore.de/z/zettel/id"
+ "zettelstore.de/z/zettel/meta"
)
const strRunSimple = "run-simple"
func init() {
@@ -85,19 +86,19 @@
Name: "password",
Func: cmdPassword,
})
}
-func fetchStartupConfiguration(fs *flag.FlagSet) (cfg *meta.Meta) {
+func fetchStartupConfiguration(fs *flag.FlagSet) (string, *meta.Meta) {
if configFlag := fs.Lookup("c"); configFlag != nil {
if filename := configFlag.Value.String(); filename != "" {
content, err := readConfiguration(filename)
- return createConfiguration(content, err)
+ return filename, createConfiguration(content, err)
}
}
- content, err := searchAndReadConfiguration()
- return createConfiguration(content, err)
+ filename, content, err := searchAndReadConfiguration()
+ return filename, createConfiguration(content, err)
}
func createConfiguration(content []byte, err error) *meta.Meta {
if err != nil {
return meta.New(id.Invalid)
@@ -105,31 +106,27 @@
return meta.NewFromInput(id.Invalid, input.NewInput(content))
}
func readConfiguration(filename string) ([]byte, error) { return os.ReadFile(filename) }
-func searchAndReadConfiguration() ([]byte, error) {
- for _, filename := range []string{"zettelstore.cfg", "zsconfig.txt", "zscfg.txt", "_zscfg"} {
+func searchAndReadConfiguration() (string, []byte, error) {
+ for _, filename := range []string{"zettelstore.cfg", "zsconfig.txt", "zscfg.txt", "_zscfg", ".zscfg"} {
if content, err := readConfiguration(filename); err == nil {
- return content, nil
+ return filename, content, nil
}
}
- return readConfiguration(".zscfg")
+ return "", nil, os.ErrNotExist
}
-func getConfig(fs *flag.FlagSet) *meta.Meta {
- cfg := fetchStartupConfiguration(fs)
+func getConfig(fs *flag.FlagSet) (string, *meta.Meta) {
+ filename, cfg := fetchStartupConfiguration(fs)
fs.Visit(func(flg *flag.Flag) {
switch flg.Name {
case "p":
- if portStr, err := parsePort(flg.Value.String()); err == nil {
- cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", portStr))
- }
+ cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", flg.Value.String()))
case "a":
- if portStr, err := parsePort(flg.Value.String()); err == nil {
- cfg.Set(keyAdminPort, portStr)
- }
+ cfg.Set(keyAdminPort, flg.Value.String())
case "d":
val := flg.Value.String()
if strings.HasPrefix(val, "/") {
val = "dir://" + val
} else {
@@ -145,20 +142,11 @@
cfg.Set(keyReadOnly, flg.Value.String())
case "v":
cfg.Set(keyVerbose, flg.Value.String())
}
})
- return cfg
-}
-
-func parsePort(s string) (string, error) {
- port, err := net.LookupPort("tcp", s)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Wrong port specification: %q", s)
- return "", err
- }
- return strconv.Itoa(port), nil
+ return filename, cfg
}
func deleteConfiguredBoxes(cfg *meta.Meta) {
for _, p := range cfg.PairsRest() {
if key := p.Key; strings.HasPrefix(key, kernel.BoxURIs) {
@@ -167,13 +155,16 @@
}
}
const (
keyAdminPort = "admin-port"
+ keyAssetDir = "asset-dir"
+ keyBaseURL = "base-url"
keyDebug = "debug-mode"
keyDefaultDirBoxType = "default-dir-box-type"
keyInsecureCookie = "insecure-cookie"
+ keyInsecureHTML = "insecure-html"
keyListenAddr = "listen-addr"
keyLogLevel = "log-level"
keyMaxRequestSize = "max-request-size"
keyOwner = "owner"
keyPersistentCookie = "persistent-cookie"
@@ -183,69 +174,72 @@
keyTokenLifetimeAPI = "token-lifetime-api"
keyURLPrefix = "url-prefix"
keyVerbose = "verbose-mode"
)
-func setServiceConfig(cfg *meta.Meta) error {
+func setServiceConfig(cfg *meta.Meta) bool {
debugMode := cfg.GetBool(keyDebug)
if debugMode && kernel.Main.GetKernelLogger().Level() > logger.DebugLevel {
- kernel.Main.SetGlobalLogLevel(logger.DebugLevel)
- }
- if strLevel, found := cfg.Get(keyLogLevel); found {
- if level := logger.ParseLevel(strLevel); level.IsValid() {
- kernel.Main.SetGlobalLogLevel(level)
- }
- }
- ok := setConfigValue(true, kernel.CoreService, kernel.CoreDebug, debugMode)
- ok = setConfigValue(ok, kernel.CoreService, kernel.CoreVerbose, cfg.GetBool(keyVerbose))
+ kernel.Main.SetLogLevel(logger.DebugLevel.String())
+ }
+ if logLevel, found := cfg.Get(keyLogLevel); found {
+ kernel.Main.SetLogLevel(logLevel)
+ }
+ err := setConfigValue(nil, kernel.CoreService, kernel.CoreDebug, debugMode)
+ err = setConfigValue(err, kernel.CoreService, kernel.CoreVerbose, cfg.GetBool(keyVerbose))
if val, found := cfg.Get(keyAdminPort); found {
- ok = setConfigValue(ok, kernel.CoreService, kernel.CorePort, val)
- }
-
- ok = setConfigValue(ok, kernel.AuthService, kernel.AuthOwner, cfg.GetDefault(keyOwner, ""))
- ok = setConfigValue(ok, kernel.AuthService, kernel.AuthReadonly, cfg.GetBool(keyReadOnly))
-
- ok = setConfigValue(
- ok, kernel.BoxService, kernel.BoxDefaultDirType,
+ err = setConfigValue(err, kernel.CoreService, kernel.CorePort, val)
+ }
+
+ err = setConfigValue(err, kernel.AuthService, kernel.AuthOwner, cfg.GetDefault(keyOwner, ""))
+ err = setConfigValue(err, kernel.AuthService, kernel.AuthReadonly, cfg.GetBool(keyReadOnly))
+
+ err = setConfigValue(
+ err, kernel.BoxService, kernel.BoxDefaultDirType,
cfg.GetDefault(keyDefaultDirBoxType, kernel.BoxDirTypeNotify))
- ok = setConfigValue(ok, kernel.BoxService, kernel.BoxURIs+"1", "dir:./zettel")
- format := kernel.BoxURIs + "%v"
+ err = setConfigValue(err, kernel.BoxService, kernel.BoxURIs+"1", "dir:./zettel")
for i := 1; ; i++ {
- key := fmt.Sprintf(format, i)
+ key := kernel.BoxURIs + strconv.Itoa(i)
val, found := cfg.Get(key)
if !found {
break
}
- ok = setConfigValue(ok, kernel.BoxService, key, val)
- }
-
- ok = setConfigValue(
- ok, kernel.WebService, kernel.WebListenAddress,
- cfg.GetDefault(keyListenAddr, "127.0.0.1:23123"))
- ok = setConfigValue(ok, kernel.WebService, kernel.WebURLPrefix, cfg.GetDefault(keyURLPrefix, "/"))
- ok = setConfigValue(ok, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie))
- ok = setConfigValue(ok, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie))
+ err = setConfigValue(err, kernel.BoxService, key, val)
+ }
+
+ err = setConfigValue(err, kernel.ConfigService, kernel.ConfigInsecureHTML, cfg.GetDefault(keyInsecureHTML, kernel.ConfigSecureHTML))
+
+ err = setConfigValue(err, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123"))
+ if val, found := cfg.Get(keyBaseURL); found {
+ err = setConfigValue(err, kernel.WebService, kernel.WebBaseURL, val)
+ }
+ if val, found := cfg.Get(keyURLPrefix); found {
+ err = setConfigValue(err, kernel.WebService, kernel.WebURLPrefix, val)
+ }
+ err = setConfigValue(err, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie))
+ err = setConfigValue(err, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie))
if val, found := cfg.Get(keyMaxRequestSize); found {
- ok = setConfigValue(ok, kernel.WebService, kernel.WebMaxRequestSize, val)
- }
- ok = setConfigValue(
- ok, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, ""))
- ok = setConfigValue(
- ok, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, ""))
-
- if !ok {
- return errors.New("unable to set configuration")
- }
- return nil
-}
-
-func setConfigValue(ok bool, subsys kernel.Service, key string, val interface{}) bool {
- done := kernel.Main.SetConfig(subsys, key, fmt.Sprintf("%v", val))
- if !done {
- kernel.Main.GetKernelLogger().Error().Str(key, fmt.Sprint(val)).Msg("Unable to set configuration")
- }
- return ok && done
+ err = setConfigValue(err, kernel.WebService, kernel.WebMaxRequestSize, val)
+ }
+ err = setConfigValue(
+ err, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, ""))
+ err = setConfigValue(
+ err, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, ""))
+ if val, found := cfg.Get(keyAssetDir); found {
+ err = setConfigValue(err, kernel.WebService, kernel.WebAssetDir, val)
+ }
+ return err == nil
+}
+
+func setConfigValue(err error, subsys kernel.Service, key string, val any) error {
+ if err == nil {
+ err = kernel.Main.SetConfig(subsys, key, fmt.Sprint(val))
+ if err != nil {
+ kernel.Main.GetKernelLogger().Fatal().Str("key", key).Str("value", fmt.Sprint(val)).Err(err).Msg("Unable to set configuration")
+ }
+ }
+ return err
}
func executeCommand(name string, args ...string) int {
command, ok := Get(name)
if !ok {
@@ -255,26 +249,19 @@
fs := command.GetFlags()
if err := fs.Parse(args); err != nil {
fmt.Fprintf(os.Stderr, "%s: unable to parse flags: %v %v\n", name, args, err)
return 1
}
- cfg := getConfig(fs)
- if err := setServiceConfig(cfg); err != nil {
- fmt.Fprintf(os.Stderr, "%s: %v\n", name, err)
+ filename, cfg := getConfig(fs)
+ if !setServiceConfig(cfg) {
+ fs.Usage()
return 2
}
kern := kernel.Main
var createManager kernel.CreateBoxManagerFunc
if command.Boxes {
- err := raiseFdLimit()
- if err != nil {
- logger := kern.GetKernelLogger()
- logger.IfErr(err).Msg("Raising some limitions did not work")
- logger.Error().Msg("Prepare to encounter errors. Most of them can be mitigated. See the manual for details")
- kern.SetConfig(kernel.BoxService, kernel.BoxDefaultDirType, kernel.BoxDirTypeSimple)
- }
createManager = func(boxURIs []*url.URL, authManager auth.Manager, rtConfig config.Config) (box.Manager, error) {
compbox.Setup(cfg)
return manager.New(boxURIs, authManager, rtConfig)
}
} else {
@@ -284,10 +271,12 @@
secret := cfg.GetDefault("secret", "")
if len(secret) < 16 && cfg.GetDefault(keyOwner, "") != "" {
fmt.Fprintf(os.Stderr, "secret must have at least length 16 when authentication is enabled, but is %q\n", secret)
return 2
}
+ cfg.Delete("secret")
+ secret = fmt.Sprintf("%x", sha256.Sum256([]byte(secret)))
kern.SetCreators(
func(readonly bool, owner id.Zid) (auth.Manager, error) {
return impl.New(readonly, owner, secret), nil
},
@@ -299,23 +288,23 @@
)
if command.Simple {
kern.SetConfig(kernel.ConfigService, kernel.ConfigSimpleMode, "true")
}
- kern.Start(command.Header, command.LineServer)
+ kern.Start(command.Header, command.LineServer, filename)
exitCode, err := command.Func(fs)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", name, err)
}
kern.Shutdown(true)
return exitCode
}
// runSimple is called, when the user just starts the software via a double click
-// or via a simple call ``./zettelstore`` on the command line.
+// or via a simple call “./zettelstore“ on the command line.
func runSimple() int {
- if _, err := searchAndReadConfiguration(); err == nil {
+ if _, _, err := searchAndReadConfiguration(); err == nil {
return executeCommand(strRunSimple)
}
dir := "./zettel"
if err := os.MkdirAll(dir, 0750); err != nil {
fmt.Fprintf(os.Stderr, "Unable to create zettel directory %q (%s)\n", dir, err)
@@ -327,13 +316,16 @@
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
var memprofile = flag.String("memprofile", "", "write memory profile to `file`")
// Main is the real entrypoint of the zettelstore.
func Main(progName, buildVersion string) int {
- fullVersion := retrieveFullVersion(buildVersion)
- kernel.Main.SetConfig(kernel.CoreService, kernel.CoreProgname, progName)
- kernel.Main.SetConfig(kernel.CoreService, kernel.CoreVersion, fullVersion)
+ info := retrieveVCSInfo(buildVersion)
+ fullVersion := info.revision
+ if info.dirty {
+ fullVersion += "-dirty"
+ }
+ kernel.Main.Setup(progName, fullVersion, info.time)
flag.Parse()
if *cpuprofile != "" || *memprofile != "" {
if *cpuprofile != "" {
kernel.Main.StartProfiling(kernel.ProfileCPU, *cpuprofile)
} else {
@@ -346,26 +338,38 @@
return runSimple()
}
return executeCommand(args[0], args[1:]...)
}
-func retrieveFullVersion(version string) string {
+type vcsInfo struct {
+ revision string
+ dirty bool
+ time time.Time
+}
+
+func retrieveVCSInfo(version string) vcsInfo {
+ buildTime := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
info, ok := debug.ReadBuildInfo()
if !ok {
- return version
+ return vcsInfo{revision: version, dirty: false, time: buildTime}
}
- var revision, dirty string
+ result := vcsInfo{time: buildTime}
for _, kv := range info.Settings {
switch kv.Key {
case "vcs.revision":
- revision = "+" + kv.Value
+ revision := "+" + kv.Value
if len(revision) > 11 {
revision = revision[:11]
}
+ result.revision = version + revision
case "vcs.modified":
if kv.Value == "true" {
- dirty = "-dirty"
+ result.dirty = true
+ }
+ case "vcs.time":
+ if t, err := time.Parse(time.RFC3339, kv.Value); err == nil {
+ result.time = t
}
}
}
- return version + revision + dirty
+ return result
}
Index: cmd/register.go
==================================================================
--- cmd/register.go
+++ cmd/register.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -17,16 +17,18 @@
_ "zettelstore.de/z/box/constbox" // Allow to use global internal box.
_ "zettelstore.de/z/box/dirbox" // Allow to use directory box.
_ "zettelstore.de/z/box/filebox" // Allow to use file box.
_ "zettelstore.de/z/box/membox" // Allow to use in-memory box.
_ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder.
- _ "zettelstore.de/z/encoder/sexprenc" // Allow to use sexpr encoder.
+ _ "zettelstore.de/z/encoder/mdenc" // Allow to use markdown encoder.
+ _ "zettelstore.de/z/encoder/shtmlenc" // Allow to use SHTML encoder.
+ _ "zettelstore.de/z/encoder/szenc" // Allow to use Sz encoder.
_ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder.
- _ "zettelstore.de/z/encoder/zjsonenc" // Allow to use ZJSON encoder.
_ "zettelstore.de/z/encoder/zmkenc" // Allow to use zmk encoder.
_ "zettelstore.de/z/kernel/impl" // Allow kernel implementation to create itself
_ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser.
+ _ "zettelstore.de/z/parser/draw" // Allow to use draw parser.
_ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser.
_ "zettelstore.de/z/parser/none" // Allow to use none parser.
_ "zettelstore.de/z/parser/plain" // Allow to use plain parser.
_ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser.
)
Index: cmd/zettelstore/main.go
==================================================================
--- cmd/zettelstore/main.go
+++ cmd/zettelstore/main.go
@@ -1,9 +1,9 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2021 Detlef Stern
+// Copyright (c) 2020-present Detlef Stern
//
-// This file is part of zettelstore.
+// 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.
//-----------------------------------------------------------------------------
Index: collect/collect.go
==================================================================
--- collect/collect.go
+++ collect/collect.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
Index: collect/collect_test.go
==================================================================
--- collect/collect_test.go
+++ collect/collect_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
Index: collect/order.go
==================================================================
--- collect/order.go
+++ collect/order.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2021-2022 Detlef Stern
+// Copyright (c) 2021-present 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
Index: collect/split.go
==================================================================
--- collect/split.go
+++ collect/split.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
Index: config/config.go
==================================================================
--- config/config.go
+++ config/config.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020-2022 Detlef Stern
+// Copyright (c) 2020-present 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
@@ -10,46 +10,47 @@
// Package config provides functions to retrieve runtime configuration data.
package config
import (
- "zettelstore.de/c/api"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "context"
+
+ "zettelstore.de/z/zettel/meta"
+)
+
+// Key values that are supported by Config.Get
+const (
+ KeyFooterZettel = "footer-zettel"
+ KeyHomeZettel = "home-zettel"
+ // api.KeyLang
)
// Config allows to retrieve all defined configuration values that can be changed during runtime.
type Config interface {
AuthConfig
+
+ // Get returns the value of the given key. It searches first in the given metadata,
+ // then in the data of the current user, and at last in the system-wide data.
+ Get(ctx context.Context, m *meta.Meta, key string) string
// AddDefaultValues enriches the given meta data with its default values.
- AddDefaultValues(m *meta.Meta) *meta.Meta
-
- // GetDefaultLang returns the current value of the "default-lang" key.
- GetDefaultLang() string
+ AddDefaultValues(context.Context, *meta.Meta) *meta.Meta
// GetSiteName returns the current value of the "site-name" key.
GetSiteName() string
- // GetHomeZettel returns the value of the "home-zettel" key.
- GetHomeZettel() id.Zid
+ // GetHTMLInsecurity returns the current
+ GetHTMLInsecurity() HTMLInsecurity
- // GetMaxTransclusions return the maximum number of indirect transclusions.
+ // GetMaxTransclusions returns the maximum number of indirect transclusions.
GetMaxTransclusions() int
// GetYAMLHeader returns the current value of the "yaml-header" key.
GetYAMLHeader() bool
// GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key.
GetZettelFileSyntax() []string
-
- // GetMarkerExternal returns the current value of the "marker-external" key.
- GetMarkerExternal() string
-
- // GetFooterHTML returns HTML code that should be embedded into the footer
- // of each WebUI page.
- GetFooterHTML() string
}
// AuthConfig are relevant configuration values for authentication.
type AuthConfig interface {
// GetSimpleMode returns true if system tuns in simple-mode.
@@ -60,13 +61,42 @@
// GetVisibility returns the visibility value of the metadata.
GetVisibility(m *meta.Meta) meta.Visibility
}
-// GetLang returns the value of the "lang" key of the given meta. If there is
-// no such value, GetDefaultLang is returned.
-func GetLang(m *meta.Meta, cfg Config) string {
- if val, ok := m.Get(api.KeyLang); ok {
- return val
+// HTMLInsecurity states what kind of insecure HTML is allowed.
+// The lowest value is the most secure one (disallowing any HTML)
+type HTMLInsecurity uint8
+
+// Constant values for HTMLInsecurity:
+const (
+ NoHTML HTMLInsecurity = iota
+ SyntaxHTML
+ MarkdownHTML
+ ZettelmarkupHTML
+)
+
+func (hi HTMLInsecurity) String() string {
+ switch hi {
+ case SyntaxHTML:
+ return "html"
+ case MarkdownHTML:
+ return "markdown"
+ case ZettelmarkupHTML:
+ return "zettelmarkup"
+ }
+ return "secure"
+}
+
+// AllowHTML returns true, if the given HTML insecurity level matches the given syntax value.
+func (hi HTMLInsecurity) AllowHTML(syntax string) bool {
+ switch hi {
+ case SyntaxHTML:
+ return syntax == meta.SyntaxHTML
+ case MarkdownHTML:
+ return syntax == meta.SyntaxHTML || syntax == meta.SyntaxMarkdown || syntax == meta.SyntaxMD
+ case ZettelmarkupHTML:
+ return syntax == meta.SyntaxZmk || syntax == meta.SyntaxHTML ||
+ syntax == meta.SyntaxMarkdown || syntax == meta.SyntaxMD
}
- return cfg.GetDefaultLang()
+ return false
}
Index: docs/development/00010000000000.zettel
==================================================================
--- docs/development/00010000000000.zettel
+++ docs/development/00010000000000.zettel
@@ -1,8 +1,10 @@
id: 00010000000000
title: Developments Notes
role: zettel
syntax: zmk
-modified: 20210916194954
+created: 00010101000000
+modified: 20221026184905
* [[Required Software|20210916193200]]
+* [[Fuzzing tests|20221026184300]]
* [[Checklist for Release|20210916194900]]
Index: docs/development/20210916193200.zettel
==================================================================
--- docs/development/20210916193200.zettel
+++ docs/development/20210916193200.zettel
@@ -1,19 +1,28 @@
id: 20210916193200
title: Required Software
role: zettel
syntax: zmk
-modified: 20211213190428
+created: 20210916193200
+modified: 20230405150541
The following software must be installed:
-* A current, supported [[release of Go|https://golang.org/doc/devel/release.html]],
-* [[shadow|https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow]] via ``go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest``,
-* [[staticcheck|https://staticcheck.io/]] via ``go install honnef.co/go/tools/cmd/staticcheck@latest``,
-* [[unparam|https://mvdan.cc/unparam]][^[[GitHub|https://github.com/mvdan/unparam]]] via ``go install mvdan.cc/unparam@latest``
+* A current, supported [[release of Go|https://go.dev/doc/devel/release]],
+* [[Fossil|https://fossil-scm.org/]],
+* [[Git|https://git-scm.org/]] (most dependencies are accessible via Git only).
Make sure that the software is in your path, e.g. via:
-
```sh
export PATH=$PATH:/usr/local/go/bin
export PATH=$PATH:$(go env GOPATH)/bin
```
+
+The internal build tool need the following software.
+It can be installed / updated via the build tool itself: ``go run tools/build.go tools``.
+
+Otherwise you can install the software by hand:
+
+* [[shadow|https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow]] via ``go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest``,
+* [[staticcheck|https://staticcheck.io/]] via ``go install honnef.co/go/tools/cmd/staticcheck@latest``,
+* [[unparam|https://mvdan.cc/unparam]][^[[GitHub|https://github.com/mvdan/unparam]]] via ``go install mvdan.cc/unparam@latest``,
+* [[govulncheck|https://golang.org/x/vuln/cmd/govulncheck]] via ``go install golang.org/x/vuln/cmd/govulncheck@latest``,
Index: docs/development/20210916194900.zettel
==================================================================
--- docs/development/20210916194900.zettel
+++ docs/development/20210916194900.zettel
@@ -1,13 +1,16 @@
id: 20210916194900
title: Checklist for Release
role: zettel
syntax: zmk
-modified: 20220309105459
+created: 20210916194900
+modified: 20230402181229
# Sync with the official repository
#* ``fossil sync -u``
+# Make sure that there is no workspace defined.
+#* ``ls ..`` must not have a file ''go.work'', in no parent folder.
# Make sure that all dependencies are up-to-date.
#* ``cat go.mod``
# Clean up your Go workspace:
#* ``go run tools/build.go clean`` (alternatively: ``make clean``).
# All internal tests must succeed:
@@ -30,11 +33,12 @@
# Update files in directory ''www''
#* index.wiki
#* download.wiki
#* changes.wiki
#* plan.wiki
-# Set file ''VERSION'' to the new release version
+# Set file ''VERSION'' to the new release version.
+ It _must_ consist of three digits: MAJOR.MINOR.PATCH, even if PATCH is zero
# Disable Fossil autosync mode:
#* ``fossil setting autosync off``
# Commit the new release version:
#* ``fossil commit --tag release --tag vVERSION -m "Version VERSION"``
#* **Important:** the tag must follow the given pattern, e.g. ''v0.0.15''.
@@ -44,12 +48,12 @@
# Create the release:
#* ``go run tools/build.go release`` (alternatively: ``make release``).
# Remove previous executables:
#* ``fossil uv remove --glob '*-PREVVERSION*'``
# Add executables for release:
-#* ``cd release``
+#* ``cd releases``
#* ``fossil uv add *.zip``
#* ``cd ..``
#* Synchronize with main repository:
#* ``fossil sync -u``
# Enable autosync:
#* ``fossil setting autosync on``
ADDED docs/development/20221026184300.zettel
Index: docs/development/20221026184300.zettel
==================================================================
--- docs/development/20221026184300.zettel
+++ docs/development/20221026184300.zettel
@@ -0,0 +1,14 @@
+id: 20221026184300
+title: Fuzzing Tests
+role: zettel
+syntax: zmk
+created: 20221026184320
+modified: 20221102140156
+
+The source code contains some simple [[fuzzing tests|https://go.dev/security/fuzz/]].
+You should call them regularly to make sure that the software will cope with unusual input.
+
+```sh
+go test -fuzz=FuzzParseBlocks zettelstore.de/z/parser/draw
+go test -fuzz=FuzzParseBlocks zettelstore.de/z/parser/zettelmark
+```
Index: docs/manual/00000000000100.zettel
==================================================================
--- docs/manual/00000000000100.zettel
+++ docs/manual/00000000000100.zettel
@@ -1,13 +1,14 @@
id: 00000000000100
title: Zettelstore Runtime Configuration
role: configuration
syntax: none
-default-copyright: (c) 2020-2022 by Detlef Stern
+created: 00010101000000
+default-copyright: (c) 2020-present by Detlef Stern
default-license: EUPL-1.2-or-later
default-visibility: public
-footer-html: Imprint / Privacy
+footer-zettel: 00001000000100
home-zettel: 00001000000000
-modified: 20220215171041
+modified: 20221205173642
site-name: Zettelstore Manual
visibility: owner
ADDED docs/manual/00000000025001
Index: docs/manual/00000000025001
==================================================================
--- docs/manual/00000000025001
+++ docs/manual/00000000025001
@@ -0,0 +1,7 @@
+id: 00000000025001
+title: Zettelstore User CSS
+role: configuration
+syntax: css
+created: 20210622110143
+modified: 20220926183101
+visibility: public
ADDED docs/manual/00000000025001.css
Index: docs/manual/00000000025001.css
==================================================================
--- docs/manual/00000000025001.css
+++ docs/manual/00000000025001.css
@@ -0,0 +1,2 @@
+/* User-defined CSS */
+.example { border-style: dotted !important }
Index: docs/manual/00001000000000.zettel
==================================================================
--- docs/manual/00001000000000.zettel
+++ docs/manual/00001000000000.zettel
@@ -1,11 +1,11 @@
id: 00001000000000
title: Zettelstore Manual
role: manual
tags: #manual #zettelstore
syntax: zmk
-modified: 20211027121716
+modified: 20220803183647
* [[Introduction|00001001000000]]
* [[Design goals|00001002000000]]
* [[Installation|00001003000000]]
* [[Configuration|00001004000000]]
@@ -14,9 +14,10 @@
* [[Zettelmarkup|00001007000000]]
* [[Other markup languages|00001008000000]]
* [[Security|00001010000000]]
* [[API|00001012000000]]
* [[Web user interface|00001014000000]]
+* [[Tips and Tricks|00001017000000]]
* [[Troubleshooting|00001018000000]]
* Frequently asked questions
Licensed under the EUPL-1.2-or-later.
ADDED docs/manual/00001000000100.zettel
Index: docs/manual/00001000000100.zettel
==================================================================
--- docs/manual/00001000000100.zettel
+++ docs/manual/00001000000100.zettel
@@ -0,0 +1,8 @@
+id: 00001000000100
+title: Footer Zettel
+role: configuration
+syntax: zmk
+created: 20221205173520
+modified: 20221207175927
+
+[[Imprint / Privacy|/home/doc/trunk/www/impri.wiki]]
Index: docs/manual/00001002000000.zettel
==================================================================
--- docs/manual/00001002000000.zettel
+++ docs/manual/00001002000000.zettel
@@ -1,21 +1,26 @@
id: 00001002000000
title: Design goals for the Zettelstore
role: manual
tags: #design #goal #manual #zettelstore
syntax: zmk
-modified: 20211124131628
+created: 20210126175322
+modified: 20230624171152
Zettelstore supports the following design goals:
; Longevity of stored notes / zettel
: Every zettel you create should be readable without the help of any tool, even without Zettelstore.
: It should be not hard to write other software that works with your zettel.
+: Normal zettel should be stored in a single file.
+ If this is not possible: at most in two files: one for the metadata, one for the content.
+ The only exception are [[predefined zettel|00001005090000]] stored in the Zettelstore executable.
+: There is no additional database.
; Single user
: All zettel belong to you, only to you.
Zettelstore provides its services only to one person: you.
- If your device is securely configured, there should be no risk that others are able to read or update your zettel.
+ If the computer running Zettelstore is securely configured, there should be no risk that others are able to read or update your zettel.
: If you want, you can customize Zettelstore in a way that some specific or all persons are able to read some of your zettel.
; Ease of installation
: If you want to use the Zettelstore software, all you need is to copy the executable to an appropriate file directory and start working.
: Upgrading the software is done just by replacing the executable with a newer one.
; Ease of operation
@@ -28,5 +33,11 @@
: Zettelstore provides a default [[web-based user interface|00001014000000]].
Anybody can provide alternative user interfaces, e.g. for special purposes.
; Simple service
: The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them.
: External software can be written to deeply analyze your zettel and the structures they form.
+; Security by default
+: Without any customization, Zettelstore provides its services in a safe and secure manner and does not expose you (or other users) to security risks.
+: If you know what use are doing, Zettelstore allows you to relax some security-related preferences.
+ However, even in this case, the more secure way is chosen.
+: The Zettelstore software uses a minimal design and uses other software dependencies only is essential needed.
+: There will be no plugin mechanism, which allows external software to control the inner workings of the Zettelstore software.
Index: docs/manual/00001004010000.zettel
==================================================================
--- docs/manual/00001004010000.zettel
+++ docs/manual/00001004010000.zettel
@@ -1,11 +1,12 @@
id: 00001004010000
title: Zettelstore startup configuration
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
-modified: 20220419193611
+created: 20210126175322
+modified: 20221128155143
The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options.
These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons.
For example, Zettelstore need to know in advance, on which network address is must listen or where zettel are stored.
An attacker that is able to change the owner can do anything.
@@ -22,10 +23,28 @@
The administrator console will only be enabled if Zettelstore is started with the [[''run'' sub-command|00001004051000]].
On most operating systems, the value must be greater than ""1024"" unless you start Zettelstore with the full privileges of a system administrator (which is not recommended).
Default: ""0""
+; [!asset-dir|''asset-dir'']
+: Allows to specify a directory whose files are allowed be transferred directly with the help of the web server.
+ The URL prefix for these files is ''/assets/''.
+ You can use this if you want to transfer files that are too large for a note to users.
+ Examples would be presentation files, PDF files, music files or video files.
+
+ Files within the given directory will not be managed by Zettelstore.[^They will be managed by Zettelstore just in the case that the directory is one of the configured [[boxes|#box-uri-x]].]
+
+ If you specify only the URL prefix, then the contents of the directory are listed to the user.
+ To avoid this, create an empty file in the directory named ""index.html"".
+
+ Default: """", no asset directory is set, the URL prefix ''/assets/'' is invalid.
+; [!base-url|''base-url'']
+: Sets the absolute base URL for the service.
+
+ Note: [[''url-prefix''|#url-prefix]] must be the suffix of ''base-url'', otherwise the web service will not start.
+
+ Default: ""http://127.0.0.1:23123/"".
; [!box-uri-x|''box-uri-X''], where __X__ is a number greater or equal to one
: Specifies a [[box|00001004011200]] where zettel are stored.
During startup __X__ is counted up, starting with one, until no key is found.
This allows to configure more than one box.
@@ -47,20 +66,35 @@
; [!insecure-cookie|''insecure-cookie'']
: Must be set to [[true|00001006030500]], if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP).
Otherwise web browser are free to ignore the authentication cookie.
Default: ""false""
+; [!insecure-html|''insecure-html'']
+: Allows to use HTML, e.g. within supported markup languages, even if this might introduce security-related problems.
+ However, HTML containing the ``