Refactor file diff and indexation

This commit is contained in:
Mickaël Menu 2021-01-03 17:09:10 +01:00
parent 0573852529
commit 4ca1595f1f
No known key found for this signature in database
GPG Key ID: 53D73664CD359895
19 changed files with 141 additions and 117 deletions

View File

@ -5,11 +5,14 @@ import (
"path/filepath"
"time"
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/core/file"
"github.com/mickael-menu/zk/core/note"
"github.com/mickael-menu/zk/util"
)
type Indexer struct {
// NoteIndexer retrieves and stores notes indexation in the SQLite database.
// It implements the Core port note.Indexer.
type NoteIndexer struct {
tx *sql.Tx
root string
logger util.Logger
@ -21,8 +24,11 @@ type Indexer struct {
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")
func NewNoteIndexer(tx *sql.Tx, root string, logger util.Logger) (*NoteIndexer, error) {
indexedStmt, err := tx.Prepare(`
SELECT filename, dir, modified from notes
ORDER BY dir, filename ASC
`)
if err != nil {
return nil, err
}
@ -52,7 +58,7 @@ func NewIndexer(tx *sql.Tx, root string, logger util.Logger) (*Indexer, error) {
return nil, err
}
return &Indexer{
return &NoteIndexer{
tx: tx,
root: root,
logger: logger,
@ -63,13 +69,13 @@ func NewIndexer(tx *sql.Tx, root string, logger util.Logger) (*Indexer, error) {
}, nil
}
func (i *Indexer) Indexed() (<-chan zk.FileMetadata, error) {
func (i *NoteIndexer) Indexed() (<-chan file.Metadata, error) {
rows, err := i.indexedStmt.Query()
if err != nil {
return nil, err
}
c := make(chan zk.FileMetadata)
c := make(chan file.Metadata)
go func() {
defer close(c)
defer rows.Close()
@ -84,8 +90,8 @@ func (i *Indexer) Indexed() (<-chan zk.FileMetadata, error) {
i.logger.Err(err)
}
c <- zk.FileMetadata{
Path: zk.Path{Dir: dir, Filename: filename, Abs: filepath.Join(i.root, dir, filename)},
c <- file.Metadata{
Path: file.Path{Dir: dir, Filename: filename, Abs: filepath.Join(i.root, dir, filename)},
Modified: modified,
}
}
@ -99,7 +105,7 @@ func (i *Indexer) Indexed() (<-chan zk.FileMetadata, error) {
return c, nil
}
func (i *Indexer) Add(metadata zk.NoteMetadata) error {
func (i *NoteIndexer) Add(metadata note.Metadata) error {
_, err := i.addStmt.Exec(
metadata.Path.Filename, metadata.Path.Dir, metadata.Title,
metadata.Body, metadata.WordCount, metadata.Checksum,
@ -108,7 +114,7 @@ func (i *Indexer) Add(metadata zk.NoteMetadata) error {
return err
}
func (i *Indexer) Update(metadata zk.NoteMetadata) error {
func (i *NoteIndexer) Update(metadata note.Metadata) error {
_, err := i.updateStmt.Exec(
metadata.Title, metadata.Body, metadata.WordCount,
metadata.Checksum, metadata.Modified,
@ -117,7 +123,7 @@ func (i *Indexer) Update(metadata zk.NoteMetadata) error {
return err
}
func (i *Indexer) Remove(path zk.Path) error {
func (i *NoteIndexer) Remove(path file.Path) error {
_, err := i.updateStmt.Exec(path.Filename, path.Dir)
return err
}

View File

@ -2,6 +2,7 @@ package cmd
import (
"github.com/mickael-menu/zk/adapter/sqlite"
"github.com/mickael-menu/zk/core/note"
"github.com/mickael-menu/zk/core/zk"
)
@ -11,17 +12,17 @@ type Index struct {
}
func (cmd *Index) Run(container *Container) error {
z, err := zk.Open(".")
zk, err := zk.Open(".")
if err != nil {
return err
}
dir, err := z.RequireDirAt(cmd.Directory)
dir, err := zk.RequireDirAt(cmd.Directory)
if err != nil {
return err
}
db, err := container.Database(z)
db, err := container.Database(zk)
if err != nil {
return err
}
@ -30,11 +31,11 @@ func (cmd *Index) Run(container *Container) error {
if err != nil {
return err
}
indexer, err := sqlite.NewIndexer(tx, z.Path, container.Logger)
indexer, err := sqlite.NewNoteIndexer(tx, zk.Path, container.Logger)
if err != nil {
return err
}
err = zk.Index(*dir, indexer, container.Logger)
err = note.Index(*dir, indexer, container.Logger)
if err != nil {
return err
}

View File

@ -1,4 +1,4 @@
package zk
package file
// DiffChange represents a note change made in a slip box directory.
type DiffChange struct {
@ -15,14 +15,14 @@ const (
DiffRemoved
)
// Diff compares two sources of FileMetadata and report the note changes, using
// the file checksum and modification date.
// Diff compares two sources of FileMetadata and report the file changes, using
// the file modification date.
//
// Warning: The FileMetadata have to be sorted by their Path for the diffing to
// work properly.
func Diff(source, target <-chan FileMetadata, callback func(DiffChange) error) error {
func Diff(source, target <-chan Metadata, callback func(DiffChange) error) error {
var err error
var sourceFile, targetFile FileMetadata
var sourceFile, targetFile Metadata
var sourceOpened, targetOpened bool = true, true
pair := diffPair{}
@ -50,8 +50,8 @@ func Diff(source, target <-chan FileMetadata, callback func(DiffChange) error) e
// diffPair holds the current two files to be diffed.
type diffPair struct {
source *FileMetadata
target *FileMetadata
source *Metadata
target *Metadata
}
// diff compares the source and target files in the current pair.
@ -82,7 +82,7 @@ func (p *diffPair) diff() *DiffChange {
p.target = nil
default: // Different files, one has been added or removed.
if isAscendingOrder(p.source.Path, p.target.Path) {
if p.source.Path.Less(p.target.Path) {
change = &DiffChange{p.source.Path, DiffAdded}
p.source = nil
} else {
@ -93,15 +93,3 @@ func (p *diffPair) diff() *DiffChange {
return change
}
// isAscendingOrder returns true if the source note's path is before the target one.
func isAscendingOrder(source, target Path) bool {
switch {
case source.Dir < target.Dir:
return true
case source.Dir > target.Dir:
return false
default:
return source.Filename < target.Filename
}
}

View File

@ -1,4 +1,4 @@
package zk
package file
import (
"errors"
@ -14,13 +14,13 @@ var date3 = time.Date(2014, 12, 10, 3, 34, 58, 651387237, time.UTC)
var date4 = time.Date(2016, 13, 11, 4, 34, 58, 651387237, time.UTC)
func TestDiffEmpty(t *testing.T) {
source := []FileMetadata{}
target := []FileMetadata{}
source := []Metadata{}
target := []Metadata{}
test(t, source, target, []DiffChange{})
}
func TestNoDiff(t *testing.T) {
files := []FileMetadata{
files := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -39,7 +39,7 @@ func TestNoDiff(t *testing.T) {
}
func TestDiff(t *testing.T) {
source := []FileMetadata{
source := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -54,7 +54,7 @@ func TestDiff(t *testing.T) {
},
}
target := []FileMetadata{
target := []Metadata{
{
// Date changed
Path: Path{Dir: "a", Filename: "1"},
@ -90,7 +90,7 @@ func TestDiff(t *testing.T) {
}
func TestDiffWithMoreInSource(t *testing.T) {
source := []FileMetadata{
source := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -101,7 +101,7 @@ func TestDiffWithMoreInSource(t *testing.T) {
},
}
target := []FileMetadata{
target := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -117,14 +117,14 @@ func TestDiffWithMoreInSource(t *testing.T) {
}
func TestDiffWithMoreInTarget(t *testing.T) {
source := []FileMetadata{
source := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
},
}
target := []FileMetadata{
target := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -144,9 +144,9 @@ func TestDiffWithMoreInTarget(t *testing.T) {
}
func TestDiffEmptySource(t *testing.T) {
source := []FileMetadata{}
source := []Metadata{}
target := []FileMetadata{
target := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -170,7 +170,7 @@ func TestDiffEmptySource(t *testing.T) {
}
func TestDiffEmptyTarget(t *testing.T) {
source := []FileMetadata{
source := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -181,7 +181,7 @@ func TestDiffEmptyTarget(t *testing.T) {
},
}
target := []FileMetadata{}
target := []Metadata{}
test(t, source, target, []DiffChange{
{
@ -196,7 +196,7 @@ func TestDiffEmptyTarget(t *testing.T) {
}
func TestDiffCancellation(t *testing.T) {
source := []FileMetadata{
source := []Metadata{
{
Path: Path{Dir: "a", Filename: "1"},
Modified: date1,
@ -207,7 +207,7 @@ func TestDiffCancellation(t *testing.T) {
},
}
target := []FileMetadata{}
target := []Metadata{}
received := make([]DiffChange, 0)
err := Diff(toChannel(source), toChannel(target), func(change DiffChange) error {
@ -229,7 +229,7 @@ func TestDiffCancellation(t *testing.T) {
assert.Err(t, err, "cancelled")
}
func test(t *testing.T, source, target []FileMetadata, expected []DiffChange) {
func test(t *testing.T, source, target []Metadata, expected []DiffChange) {
received := make([]DiffChange, 0)
err := Diff(toChannel(source), toChannel(target), func(change DiffChange) error {
received = append(received, change)
@ -239,8 +239,8 @@ func test(t *testing.T, source, target []FileMetadata, expected []DiffChange) {
assert.Equal(t, received, expected)
}
func toChannel(fm []FileMetadata) <-chan FileMetadata {
c := make(chan FileMetadata)
func toChannel(fm []Metadata) <-chan Metadata {
c := make(chan Metadata)
go func() {
for _, m := range fm {
c <- m

29
core/file/file.go Normal file
View File

@ -0,0 +1,29 @@
package file
import "time"
// Metadata holds information about a slip box file.
type Metadata struct {
Path Path
Modified time.Time
}
// Path holds a file path relative to a slip box.
type Path struct {
Dir string
Filename string
Abs string
}
// Less returns whether ther receiver path is located before the given one,
// lexicographically.
func (p Path) Less(other Path) bool {
switch {
case p.Dir < other.Dir:
return true
case p.Dir > other.Dir:
return false
default:
return p.Filename < other.Filename
}
}

View File

@ -1,37 +1,21 @@
package zk
package file
import (
"os"
"path/filepath"
"strings"
"time"
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/util"
)
// Dir represents a directory inside a slip box.
type Dir struct {
// Name of the directory, which is the path relative to the slip box's root.
Name string
// Absolute path to the directory.
Path string
// User configuration for this directory.
Config DirConfig
}
// FileMetadata holds information about a note file.
type FileMetadata struct {
Path Path
Modified time.Time
}
// Walk emits the metadata of each note stored in the directory.
func (d Dir) Walk(logger util.Logger) <-chan FileMetadata {
c := make(chan FileMetadata, 50)
// Walk emits the metadata of each file stored in the directory.
func Walk(dir zk.Dir, logger util.Logger) <-chan Metadata {
c := make(chan Metadata, 50)
go func() {
defer close(c)
err := filepath.Walk(d.Path, func(abs string, info os.FileInfo, err error) error {
err := filepath.Walk(dir.Path, func(abs string, info os.FileInfo, err error) error {
if err != nil {
return err
}
@ -50,7 +34,7 @@ func (d Dir) Walk(logger util.Logger) <-chan FileMetadata {
return nil
}
path, err := filepath.Rel(d.Path, abs)
path, err := filepath.Rel(dir.Path, abs)
if err != nil {
logger.Println(err)
return nil
@ -61,9 +45,9 @@ func (d Dir) Walk(logger util.Logger) <-chan FileMetadata {
curDir = ""
}
c <- FileMetadata{
c <- Metadata{
Path: Path{
Dir: filepath.Join(d.Name, curDir),
Dir: filepath.Join(dir.Name, curDir),
Filename: filename,
Abs: abs,
},

View File

@ -1,10 +1,11 @@
package zk
package file
import (
"path/filepath"
"testing"
"time"
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/util"
"github.com/mickael-menu/zk/util/assert"
"github.com/mickael-menu/zk/util/fixtures"
@ -13,9 +14,9 @@ import (
var root = fixtures.Path("walk")
func TestWalkRootDir(t *testing.T) {
dir := Dir{Name: "", Path: root}
res := toSlice(dir.Walk(&util.NullLogger))
assert.Equal(t, res, []FileMetadata{
dir := zk.Dir{Name: "", Path: root}
res := toSlice(Walk(dir, &util.NullLogger))
assert.Equal(t, res, []Metadata{
{
Path: Path{Dir: "", Filename: "a.md", Abs: filepath.Join(root, "a.md")},
Modified: date("2021-01-03T11:30:26.069257899+01:00"),
@ -44,9 +45,9 @@ func TestWalkRootDir(t *testing.T) {
}
func TestWalkSubDir(t *testing.T) {
dir := Dir{Name: "dir1", Path: filepath.Join(root, "dir1")}
res := toSlice(dir.Walk(&util.NullLogger))
assert.Equal(t, res, []FileMetadata{
dir := zk.Dir{Name: "dir1", Path: filepath.Join(root, "dir1")}
res := toSlice(Walk(dir, &util.NullLogger))
assert.Equal(t, res, []Metadata{
{
Path: Path{Dir: "dir1", Filename: "a.md", Abs: filepath.Join(root, "dir1/a.md")},
Modified: date("2021-01-03T11:31:18.961628888+01:00"),
@ -63,9 +64,9 @@ func TestWalkSubDir(t *testing.T) {
}
func TestWalkSubSubDir(t *testing.T) {
dir := Dir{Name: "dir1/dir1", Path: filepath.Join(root, "dir1/dir1")}
res := toSlice(dir.Walk(&util.NullLogger))
assert.Equal(t, res, []FileMetadata{
dir := zk.Dir{Name: "dir1/dir1", Path: filepath.Join(root, "dir1/dir1")}
res := toSlice(Walk(dir, &util.NullLogger))
assert.Equal(t, res, []Metadata{
{
Path: Path{Dir: "dir1/dir1", Filename: "a.md", Abs: filepath.Join(root, "dir1/dir1/a.md")},
Modified: date("2021-01-03T11:31:27.900472856+01:00"),
@ -78,8 +79,8 @@ func date(s string) time.Time {
return date
}
func toSlice(c <-chan FileMetadata) []FileMetadata {
s := make([]FileMetadata, 0)
func toSlice(c <-chan Metadata) []Metadata {
s := make([]Metadata, 0)
for fm := range c {
s = append(s, fm)
}

View File

@ -1,4 +1,4 @@
package zk
package note
import (
"crypto/sha256"
@ -7,14 +7,16 @@ import (
"strings"
"time"
"github.com/mickael-menu/zk/core/file"
"github.com/mickael-menu/zk/core/zk"
"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
// Metadata holds information about a particular note.
type Metadata struct {
Path file.Path
Title string
Body string
WordCount int
@ -23,48 +25,57 @@ type NoteMetadata struct {
Checksum string
}
// Indexer persists the notes index.
type Indexer interface {
Indexed() (<-chan FileMetadata, error)
Add(metadata NoteMetadata) error
Update(metadata NoteMetadata) error
Remove(path Path) error
// Indexed returns the list of indexed note file metadata.
Indexed() (<-chan file.Metadata, error)
// Add indexes a new note from its metadata.
Add(metadata Metadata) error
// Update updates the metadata of an already indexed note.
Update(metadata Metadata) error
// Remove deletes a note from the index.
Remove(path file.Path) error
}
// Index indexes the content of the notes in the given directory.
func Index(dir Dir, indexer Indexer, logger util.Logger) error {
func Index(dir zk.Dir, indexer Indexer, logger util.Logger) error {
wrap := errors.Wrapper("indexation failed")
source := dir.Walk(logger)
source := file.Walk(dir, logger)
target, err := indexer.Indexed()
if err != nil {
return wrap(err)
}
return Diff(source, target, func(change DiffChange) error {
err = file.Diff(source, target, func(change file.DiffChange) error {
switch change.Kind {
case DiffAdded:
metadata, err := noteMetadata(change.Path)
case file.DiffAdded:
metadata, err := metadata(change.Path)
if err == nil {
err = indexer.Add(metadata)
}
logger.Err(err)
case DiffModified:
metadata, err := noteMetadata(change.Path)
case file.DiffModified:
metadata, err := metadata(change.Path)
if err == nil {
err = indexer.Update(metadata)
}
logger.Err(err)
case DiffRemoved:
indexer.Remove(change.Path)
case file.DiffRemoved:
err := indexer.Remove(change.Path)
logger.Err(err)
}
return nil
})
return wrap(err)
}
func noteMetadata(path Path) (NoteMetadata, error) {
metadata := NoteMetadata{
// metadata retrieves note metadata for the given file.
func metadata(path file.Path) (Metadata, error) {
metadata := Metadata{
Path: path,
}

View File

@ -7,6 +7,7 @@ type IDOptions struct {
Case Case
}
// Charset is a set of characters.
type Charset []rune
var (

View File

@ -24,11 +24,14 @@ type Zk struct {
Config Config
}
// Path holds a file path relative to a slip box.
type Path struct {
Dir string
Filename string
Abs string
// Dir represents a directory inside a slip box.
type Dir struct {
// Name of the directory, which is the path relative to the slip box's root.
Name string
// Absolute path to the directory.
Path string
// User configuration for this directory.
Config DirConfig
}
// Open locates a slip box at the given path and parses its configuration.