mirror of https://github.com/mickael-menu/zk
Index notes content
parent
b4f2c841eb
commit
0573852529
@ -0,0 +1,123 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mickael-menu/zk/core/zk"
|
||||||
|
"github.com/mickael-menu/zk/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Indexer struct {
|
||||||
|
tx *sql.Tx
|
||||||
|
root string
|
||||||
|
logger util.Logger
|
||||||
|
|
||||||
|
// Prepared SQL statements
|
||||||
|
indexedStmt *sql.Stmt
|
||||||
|
addStmt *sql.Stmt
|
||||||
|
updateStmt *sql.Stmt
|
||||||
|
removeStmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIndexer(tx *sql.Tx, root string, logger util.Logger) (*Indexer, error) {
|
||||||
|
indexedStmt, err := tx.Prepare("SELECT filename, dir, modified from notes")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addStmt, err := tx.Prepare(`
|
||||||
|
INSERT INTO notes (filename, dir, title, body, word_count, checksum, created, modified)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStmt, err := tx.Prepare(`
|
||||||
|
UPDATE notes
|
||||||
|
SET title = ?, body = ?, word_count = ?, checksum = ?, modified = ?
|
||||||
|
WHERE filename = ? AND dir = ?
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
removeStmt, err := tx.Prepare(`
|
||||||
|
DELETE FROM notes
|
||||||
|
WHERE filename = ? AND dir = ?
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Indexer{
|
||||||
|
tx: tx,
|
||||||
|
root: root,
|
||||||
|
logger: logger,
|
||||||
|
indexedStmt: indexedStmt,
|
||||||
|
addStmt: addStmt,
|
||||||
|
updateStmt: updateStmt,
|
||||||
|
removeStmt: removeStmt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Indexer) Indexed() (<-chan zk.FileMetadata, error) {
|
||||||
|
rows, err := i.indexedStmt.Query()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan zk.FileMetadata)
|
||||||
|
go func() {
|
||||||
|
defer close(c)
|
||||||
|
defer rows.Close()
|
||||||
|
var (
|
||||||
|
filename, dir string
|
||||||
|
modified time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
err := rows.Scan(&filename, &dir, &modified)
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c <- zk.FileMetadata{
|
||||||
|
Path: zk.Path{Dir: dir, Filename: filename, Abs: filepath.Join(i.root, dir, filename)},
|
||||||
|
Modified: modified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Err(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Indexer) Add(metadata zk.NoteMetadata) error {
|
||||||
|
_, err := i.addStmt.Exec(
|
||||||
|
metadata.Path.Filename, metadata.Path.Dir, metadata.Title,
|
||||||
|
metadata.Body, metadata.WordCount, metadata.Checksum,
|
||||||
|
metadata.Created, metadata.Modified,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Indexer) Update(metadata zk.NoteMetadata) error {
|
||||||
|
_, err := i.updateStmt.Exec(
|
||||||
|
metadata.Title, metadata.Body, metadata.WordCount,
|
||||||
|
metadata.Checksum, metadata.Modified,
|
||||||
|
metadata.Path.Filename, metadata.Path.Dir,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Indexer) Remove(path zk.Path) error {
|
||||||
|
_, err := i.updateStmt.Exec(path.Filename, path.Dir)
|
||||||
|
return err
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mickael-menu/zk/adapter/sqlite"
|
||||||
|
"github.com/mickael-menu/zk/core/zk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Index indexes the content of all the notes in the slip box.
|
||||||
|
type Index struct {
|
||||||
|
Directory string `arg optional type:"path" default:"." help:"Directory containing the notes to index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *Index) Run(container *Container) error {
|
||||||
|
z, err := zk.Open(".")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := z.RequireDirAt(cmd.Directory)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := container.Database(z)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx, err := db.Begin()
|
||||||
|
defer tx.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
indexer, err := sqlite.NewIndexer(tx, z.Path, container.Logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = zk.Index(*dir, indexer, container.Logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package note
|
|
||||||
|
|
||||||
// Metadata holds information about a particular note.
|
|
||||||
type Metadata struct {
|
|
||||||
Title string
|
|
||||||
Content string
|
|
||||||
WordCount int
|
|
||||||
}
|
|
@ -0,0 +1,93 @@
|
|||||||
|
package zk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mickael-menu/zk/util"
|
||||||
|
"github.com/mickael-menu/zk/util/errors"
|
||||||
|
"gopkg.in/djherbis/times.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoteMetadata holds information about a particular note.
|
||||||
|
type NoteMetadata struct {
|
||||||
|
Path Path
|
||||||
|
Title string
|
||||||
|
Body string
|
||||||
|
WordCount int
|
||||||
|
Created time.Time
|
||||||
|
Modified time.Time
|
||||||
|
Checksum string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Indexer interface {
|
||||||
|
Indexed() (<-chan FileMetadata, error)
|
||||||
|
Add(metadata NoteMetadata) error
|
||||||
|
Update(metadata NoteMetadata) error
|
||||||
|
Remove(path Path) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index indexes the content of the notes in the given directory.
|
||||||
|
func Index(dir Dir, indexer Indexer, logger util.Logger) error {
|
||||||
|
wrap := errors.Wrapper("indexation failed")
|
||||||
|
|
||||||
|
source := dir.Walk(logger)
|
||||||
|
target, err := indexer.Indexed()
|
||||||
|
if err != nil {
|
||||||
|
return wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Diff(source, target, func(change DiffChange) error {
|
||||||
|
switch change.Kind {
|
||||||
|
case DiffAdded:
|
||||||
|
metadata, err := noteMetadata(change.Path)
|
||||||
|
if err == nil {
|
||||||
|
err = indexer.Add(metadata)
|
||||||
|
}
|
||||||
|
logger.Err(err)
|
||||||
|
|
||||||
|
case DiffModified:
|
||||||
|
metadata, err := noteMetadata(change.Path)
|
||||||
|
if err == nil {
|
||||||
|
err = indexer.Update(metadata)
|
||||||
|
}
|
||||||
|
logger.Err(err)
|
||||||
|
|
||||||
|
case DiffRemoved:
|
||||||
|
indexer.Remove(change.Path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func noteMetadata(path Path) (NoteMetadata, error) {
|
||||||
|
metadata := NoteMetadata{
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(path.Abs)
|
||||||
|
if err != nil {
|
||||||
|
return metadata, err
|
||||||
|
}
|
||||||
|
contentStr := string(content)
|
||||||
|
metadata.Body = contentStr
|
||||||
|
metadata.WordCount = len(strings.Fields(contentStr))
|
||||||
|
metadata.Checksum = fmt.Sprintf("%x", sha256.Sum256(content))
|
||||||
|
|
||||||
|
times, err := times.Stat(path.Abs)
|
||||||
|
if err != nil {
|
||||||
|
return metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.Modified = times.ModTime()
|
||||||
|
if times.HasBirthTime() {
|
||||||
|
metadata.Created = times.BirthTime()
|
||||||
|
} else {
|
||||||
|
metadata.Created = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata, nil
|
||||||
|
}
|
Loading…
Reference in New Issue