Merge dir and filename to simplify paths handling

pull/6/head
Mickaël Menu 4 years ago
parent 5988b020af
commit ea3b6923c0
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -2,7 +2,6 @@ package sqlite
import ( import (
"database/sql" "database/sql"
"path/filepath"
"time" "time"
"github.com/mickael-menu/zk/core/file" "github.com/mickael-menu/zk/core/file"
@ -15,7 +14,6 @@ import (
// It implements the core ports note.Indexer and note.Finder. // It implements the core ports note.Indexer and note.Finder.
type NoteDAO struct { type NoteDAO struct {
tx Transaction tx Transaction
root string
logger util.Logger logger util.Logger
// Prepared SQL statements // Prepared SQL statements
@ -26,30 +24,29 @@ type NoteDAO struct {
existsStmt *LazyStmt existsStmt *LazyStmt
} }
func NewNoteDAO(tx Transaction, root string, logger util.Logger) *NoteDAO { func NewNoteDAO(tx Transaction, logger util.Logger) *NoteDAO {
return &NoteDAO{ return &NoteDAO{
tx: tx, tx: tx,
root: root,
logger: logger, logger: logger,
indexedStmt: tx.PrepareLazy(` indexedStmt: tx.PrepareLazy(`
SELECT dir, filename, modified from notes SELECT path, modified from notes
ORDER BY dir, filename ASC ORDER BY path ASC
`), `),
addStmt: tx.PrepareLazy(` addStmt: tx.PrepareLazy(`
INSERT INTO notes (dir, filename, title, body, word_count, checksum, created, modified) INSERT INTO notes (path, title, body, word_count, checksum, created, modified)
VALUES (?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
`), `),
updateStmt: tx.PrepareLazy(` updateStmt: tx.PrepareLazy(`
UPDATE notes UPDATE notes
SET title = ?, body = ?, word_count = ?, checksum = ?, modified = ? SET title = ?, body = ?, word_count = ?, checksum = ?, modified = ?
WHERE dir = ? AND filename = ? WHERE path = ?
`), `),
removeStmt: tx.PrepareLazy(` removeStmt: tx.PrepareLazy(`
DELETE FROM notes DELETE FROM notes
WHERE dir = ? AND filename = ? WHERE path = ?
`), `),
existsStmt: tx.PrepareLazy(` existsStmt: tx.PrepareLazy(`
SELECT EXISTS (SELECT 1 FROM notes WHERE dir = ? AND filename = ?) SELECT EXISTS (SELECT 1 FROM notes WHERE path = ?)
`), `),
} }
} }
@ -67,18 +64,18 @@ func (d *NoteDAO) Indexed() (<-chan file.Metadata, error) {
defer close(c) defer close(c)
defer rows.Close() defer rows.Close()
var ( var (
dir, filename string path string
modified time.Time modified time.Time
) )
for rows.Next() { for rows.Next() {
err := rows.Scan(&dir, &filename, &modified) err := rows.Scan(&path, &modified)
if err != nil { if err != nil {
d.logger.Err(wrap(err)) d.logger.Err(wrap(err))
} }
c <- file.Metadata{ c <- file.Metadata{
Path: file.Path{Dir: dir, Filename: filename, Abs: filepath.Join(d.root, dir, filename)}, Path: path,
Modified: modified, Modified: modified,
} }
} }
@ -94,8 +91,7 @@ func (d *NoteDAO) Indexed() (<-chan file.Metadata, error) {
func (d *NoteDAO) Add(note note.Metadata) error { func (d *NoteDAO) Add(note note.Metadata) error {
_, err := d.addStmt.Exec( _, err := d.addStmt.Exec(
note.Path.Dir, note.Path.Filename, note.Title, note.Path, note.Title, note.Body, note.WordCount, note.Checksum,
note.Body, note.WordCount, note.Checksum,
note.Created, note.Modified, note.Created, note.Modified,
) )
return errors.Wrapf(err, "%v: can't add note to the index", note.Path) return errors.Wrapf(err, "%v: can't add note to the index", note.Path)
@ -113,14 +109,13 @@ func (d *NoteDAO) Update(note note.Metadata) error {
} }
_, err = d.updateStmt.Exec( _, err = d.updateStmt.Exec(
note.Title, note.Body, note.WordCount, note.Title, note.Body, note.WordCount, note.Checksum, note.Modified,
note.Checksum, note.Modified, note.Path,
note.Path.Dir, note.Path.Filename,
) )
return errors.Wrapf(err, "%v: failed to update note index", note.Path) return errors.Wrapf(err, "%v: failed to update note index", note.Path)
} }
func (d *NoteDAO) Remove(path file.Path) error { func (d *NoteDAO) Remove(path string) error {
wrap := errors.Wrapperf("%v: failed to remove note index", path) wrap := errors.Wrapperf("%v: failed to remove note index", path)
exists, err := d.exists(path) exists, err := d.exists(path)
@ -131,12 +126,12 @@ func (d *NoteDAO) Remove(path file.Path) error {
return wrap(errors.New("note not found in the index")) return wrap(errors.New("note not found in the index"))
} }
_, err = d.removeStmt.Exec(path.Dir, path.Filename) _, err = d.removeStmt.Exec(path)
return wrap(err) return wrap(err)
} }
func (d *NoteDAO) exists(path file.Path) (bool, error) { func (d *NoteDAO) exists(path string) (bool, error) {
row, err := d.existsStmt.QueryRow(path.Dir, path.Filename) row, err := d.existsStmt.QueryRow(path)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -152,14 +147,14 @@ func (d *NoteDAO) Find(callback func(note.Match) error, filters ...note.Filter)
rows, err := func() (*sql.Rows, error) { rows, err := func() (*sql.Rows, error) {
if len(filters) == 0 { if len(filters) == 0 {
return d.tx.Query(` return d.tx.Query(`
SELECT id, dir, filename, title, body, word_count, created, modified, SELECT id, path, title, body, word_count, created, modified,
checksum, "" as snippet from notes checksum, "" as snippet from notes
ORDER BY title ASC ORDER BY title ASC
`) `)
} else { } else {
filter := filters[0].(note.QueryFilter) filter := filters[0].(note.QueryFilter)
return d.tx.Query(` return d.tx.Query(`
SELECT n.id, n.dir, n.filename, n.title, n.body, n.word_count, SELECT n.id, n.path, n.title, n.body, n.word_count,
n.created, n.modified, n.checksum, n.created, n.modified, n.checksum,
snippet(notes_fts, -1, '<zk:match>', '</zk:match>', '…', 20) as snippet snippet(notes_fts, -1, '<zk:match>', '</zk:match>', '…', 20) as snippet
FROM notes n FROM notes n
@ -179,13 +174,13 @@ func (d *NoteDAO) Find(callback func(note.Match) error, filters ...note.Filter)
for rows.Next() { for rows.Next() {
var ( var (
id, wordCount int id, wordCount int
title, body, snippet string title, body, snippet string
dir, filename, checksum string path, checksum string
created, modified time.Time created, modified time.Time
) )
err := rows.Scan(&id, &dir, &filename, &title, &body, &wordCount, &created, &modified, &checksum, &snippet) err := rows.Scan(&id, &path, &title, &body, &wordCount, &created, &modified, &checksum, &snippet)
if err != nil { if err != nil {
d.logger.Err(err) d.logger.Err(err)
continue continue
@ -195,11 +190,7 @@ func (d *NoteDAO) Find(callback func(note.Match) error, filters ...note.Filter)
ID: id, ID: id,
Snippet: snippet, Snippet: snippet,
Metadata: note.Metadata{ Metadata: note.Metadata{
Path: file.Path{ Path: path,
Dir: dir,
Filename: filename,
Abs: filepath.Join(d.root, dir, filename),
},
Title: title, Title: title,
Body: body, Body: body,
WordCount: wordCount, WordCount: wordCount,

@ -13,31 +13,31 @@ import (
func TestNoteDAOIndexed(t *testing.T) { func TestNoteDAOIndexed(t *testing.T) {
testTransaction(t, func(tx Transaction) { testTransaction(t, func(tx Transaction) {
dao := NewNoteDAO(tx, "/test", &util.NullLogger) dao := NewNoteDAO(tx, &util.NullLogger)
expected := []file.Metadata{ expected := []file.Metadata{
{ {
Path: file.Path{Dir: "", Filename: "f39c8.md", Abs: "/test/f39c8.md"}, Path: "f39c8.md",
Modified: date("2020-01-20T08:52:42.321024+01:00"), Modified: date("2020-01-20T08:52:42.321024+01:00"),
}, },
{ {
Path: file.Path{Dir: "", Filename: "index.md", Abs: "/test/index.md"}, Path: "index.md",
Modified: date("2019-12-04T12:17:21.720747+01:00"), Modified: date("2019-12-04T12:17:21.720747+01:00"),
}, },
{ {
Path: file.Path{Dir: "log", Filename: "2021-01-03.md", Abs: "/test/log/2021-01-03.md"}, Path: "log/2021-01-03.md",
Modified: date("2020-11-22T16:27:45.734454655+01:00"), Modified: date("2020-11-22T16:27:45.734454655+01:00"),
}, },
{ {
Path: file.Path{Dir: "log", Filename: "2021-01-04.md", Abs: "/test/log/2021-01-04.md"}, Path: "log/2021-01-04.md",
Modified: date("2020-11-29T08:20:18.138907236+01:00"), Modified: date("2020-11-29T08:20:18.138907236+01:00"),
}, },
{ {
Path: file.Path{Dir: "ref/test", Filename: "a.md", Abs: "/test/ref/test/a.md"}, Path: "ref/test/a.md",
Modified: date("2019-11-20T20:34:06.120375+01:00"), Modified: date("2019-11-20T20:34:06.120375+01:00"),
}, },
{ {
Path: file.Path{Dir: "ref/test", Filename: "b.md", Abs: "/test/ref/test/b.md"}, Path: "ref/test/b.md",
Modified: date("2019-11-20T20:34:06.120375+01:00"), Modified: date("2019-11-20T20:34:06.120375+01:00"),
}, },
} }
@ -55,14 +55,10 @@ func TestNoteDAOIndexed(t *testing.T) {
func TestNoteDAOAdd(t *testing.T) { func TestNoteDAOAdd(t *testing.T) {
testTransaction(t, func(tx Transaction) { testTransaction(t, func(tx Transaction) {
dao := NewNoteDAO(tx, "/test", &util.NullLogger) dao := NewNoteDAO(tx, &util.NullLogger)
err := dao.Add(note.Metadata{ err := dao.Add(note.Metadata{
Path: file.Path{ Path: "log/added.md",
Dir: "log",
Filename: "added.md",
Abs: "/test/log/added.md",
},
Title: "Added note", Title: "Added note",
Body: "Note body", Body: "Note body",
WordCount: 2, WordCount: 2,
@ -72,11 +68,10 @@ func TestNoteDAOAdd(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
row, err := queryNoteRow(tx, `filename = "added.md"`) row, err := queryNoteRow(tx, `path = "log/added.md"`)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, row, noteRow{ assert.Equal(t, row, noteRow{
Dir: "log", Path: "log/added.md",
Filename: "added.md",
Title: "Added note", Title: "Added note",
Body: "Note body", Body: "Note body",
WordCount: 2, WordCount: 2,
@ -90,28 +85,19 @@ func TestNoteDAOAdd(t *testing.T) {
// Check that we can't add a duplicate note with an existing path. // Check that we can't add a duplicate note with an existing path.
func TestNoteDAOAddExistingNote(t *testing.T) { func TestNoteDAOAddExistingNote(t *testing.T) {
testTransaction(t, func(tx Transaction) { testTransaction(t, func(tx Transaction) {
dao := NewNoteDAO(tx, "/test", &util.NullLogger) dao := NewNoteDAO(tx, &util.NullLogger)
err := dao.Add(note.Metadata{ err := dao.Add(note.Metadata{Path: "ref/test/a.md"})
Path: file.Path{ assert.Err(t, err, "ref/test/a.md: can't add note to the index: UNIQUE constraint failed: notes.path")
Dir: "ref/test",
Filename: "a.md",
},
})
assert.Err(t, err, "ref/test/a.md: can't add note to the index: UNIQUE constraint failed: notes.filename, notes.dir")
}) })
} }
func TestNoteDAOUpdate(t *testing.T) { func TestNoteDAOUpdate(t *testing.T) {
testTransaction(t, func(tx Transaction) { testTransaction(t, func(tx Transaction) {
dao := NewNoteDAO(tx, "/test", &util.NullLogger) dao := NewNoteDAO(tx, &util.NullLogger)
err := dao.Update(note.Metadata{ err := dao.Update(note.Metadata{
Path: file.Path{ Path: "ref/test/a.md",
Dir: "ref/test",
Filename: "a.md",
Abs: "/test/log/added.md",
},
Title: "Updated note", Title: "Updated note",
Body: "Updated body", Body: "Updated body",
WordCount: 42, WordCount: 42,
@ -121,11 +107,10 @@ func TestNoteDAOUpdate(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
row, err := queryNoteRow(tx, `dir = "ref/test" AND filename = "a.md"`) row, err := queryNoteRow(tx, `path = "ref/test/a.md"`)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, row, noteRow{ assert.Equal(t, row, noteRow{
Dir: "ref/test", Path: "ref/test/a.md",
Filename: "a.md",
Title: "Updated note", Title: "Updated note",
Body: "Updated body", Body: "Updated body",
WordCount: 42, WordCount: 42,
@ -138,14 +123,10 @@ func TestNoteDAOUpdate(t *testing.T) {
func TestNoteDAOUpdateUnknown(t *testing.T) { func TestNoteDAOUpdateUnknown(t *testing.T) {
testTransaction(t, func(tx Transaction) { testTransaction(t, func(tx Transaction) {
dao := NewNoteDAO(tx, "/test", &util.NullLogger) dao := NewNoteDAO(tx, &util.NullLogger)
err := dao.Update(note.Metadata{ err := dao.Update(note.Metadata{
Path: file.Path{ Path: "unknown/unknown.md",
Dir: "unknown",
Filename: "unknown.md",
Abs: "/test/unknown/unknown.md",
},
}) })
assert.Err(t, err, "unknown/unknown.md: failed to update note index: note not found in the index") assert.Err(t, err, "unknown/unknown.md: failed to update note index: note not found in the index")
}) })
@ -153,43 +134,35 @@ func TestNoteDAOUpdateUnknown(t *testing.T) {
func TestNoteDAORemove(t *testing.T) { func TestNoteDAORemove(t *testing.T) {
testTransaction(t, func(tx Transaction) { testTransaction(t, func(tx Transaction) {
dao := NewNoteDAO(tx, "/test", &util.NullLogger) dao := NewNoteDAO(tx, &util.NullLogger)
err := dao.Remove(file.Path{ err := dao.Remove("ref/test/a.md")
Dir: "ref/test",
Filename: "a.md",
Abs: "/test/ref/test/a.md",
})
assert.Nil(t, err) assert.Nil(t, err)
}) })
} }
func TestNoteDAORemoveUnknown(t *testing.T) { func TestNoteDAORemoveUnknown(t *testing.T) {
testTransaction(t, func(tx Transaction) { testTransaction(t, func(tx Transaction) {
dao := NewNoteDAO(tx, "/test", &util.NullLogger) dao := NewNoteDAO(tx, &util.NullLogger)
err := dao.Remove(file.Path{ err := dao.Remove("unknown/unknown.md")
Dir: "unknown",
Filename: "unknown.md",
Abs: "/test/unknown/unknown.md",
})
assert.Err(t, err, "unknown/unknown.md: failed to remove note index: note not found in the index") assert.Err(t, err, "unknown/unknown.md: failed to remove note index: note not found in the index")
}) })
} }
type noteRow struct { type noteRow struct {
Dir, Filename, Title, Body, Checksum string Path, Title, Body, Checksum string
WordCount int WordCount int
Created, Modified time.Time Created, Modified time.Time
} }
func queryNoteRow(tx Transaction, where string) (noteRow, error) { func queryNoteRow(tx Transaction, where string) (noteRow, error) {
var row noteRow var row noteRow
err := tx.QueryRow(fmt.Sprintf(` err := tx.QueryRow(fmt.Sprintf(`
SELECT dir, filename, title, body, word_count, checksum, created, modified SELECT path, title, body, word_count, checksum, created, modified
FROM notes FROM notes
WHERE %v WHERE %v
`, where)).Scan(&row.Dir, &row.Filename, &row.Title, &row.Body, &row.WordCount, &row.Checksum, &row.Created, &row.Modified) `, where)).Scan(&row.Path, &row.Title, &row.Body, &row.WordCount, &row.Checksum, &row.Created, &row.Modified)
return row, err return row, err
} }

@ -47,47 +47,36 @@ func (db *DB) Migrate() error {
if version == 0 { if version == 0 {
err = tx.ExecStmts([]string{ err = tx.ExecStmts([]string{
` `CREATE TABLE IF NOT EXISTS notes (
CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, path TEXT NOT NULL,
dir TEXT NOT NULL, title TEXT DEFAULT('') NOT NULL,
filename TEXT NOT NULL, body TEXT DEFAULT('') NOT NULL,
title TEXT DEFAULT('') NOT NULL, word_count INTEGER DEFAULT(0) NOT NULL,
body TEXT DEFAULT('') NOT NULL, checksum TEXT NOT NULL,
word_count INTEGER DEFAULT(0) NOT NULL, created DATETIME DEFAULT(CURRENT_TIMESTAMP) NOT NULL,
checksum TEXT NOT NULL, modified DATETIME DEFAULT(CURRENT_TIMESTAMP) NOT NULL,
created DATETIME DEFAULT(CURRENT_TIMESTAMP) NOT NULL, UNIQUE(path)
modified DATETIME DEFAULT(CURRENT_TIMESTAMP) NOT NULL, )`,
UNIQUE(filename, dir)
)
`,
`CREATE INDEX IF NOT EXISTS index_notes_checksum ON notes (checksum)`, `CREATE INDEX IF NOT EXISTS index_notes_checksum ON notes (checksum)`,
`CREATE INDEX IF NOT EXISTS index_notes_dir_filename ON notes (dir, filename)`, `CREATE INDEX IF NOT EXISTS index_notes_path ON notes (path)`,
` `CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(
CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5( path, title, body,
filename, title, body, content = notes,
content = notes, content_rowid = id,
content_rowid = id, tokenize = 'porter unicode61 remove_diacritics 1'
tokenize = 'porter unicode61 remove_diacritics 1' )`,
)
`,
// Triggers to keep the FTS index up to date. // Triggers to keep the FTS index up to date.
` `CREATE TRIGGER IF NOT EXISTS trigger_notes_ai AFTER INSERT ON notes BEGIN
CREATE TRIGGER IF NOT EXISTS notes_ai AFTER INSERT ON notes BEGIN INSERT INTO notes_fts(rowid, path, title, body) VALUES (new.id, new.path, new.title, new.body);
INSERT INTO notes_fts(rowid, filename, title, body) VALUES (new.id, new.filename, new.title, new.body); END`,
END `CREATE TRIGGER IF NOT EXISTS trigger_notes_ad AFTER DELETE ON notes BEGIN
`, INSERT INTO notes_fts(notes_fts, rowid, path, title, body) VALUES('delete', old.id, old.path, old.title, old.body);
` END`,
CREATE TRIGGER IF NOT EXISTS notes_ad AFTER DELETE ON notes BEGIN `CREATE TRIGGER IF NOT EXISTS trigger_notes_au AFTER UPDATE ON notes BEGIN
INSERT INTO notes_fts(notes_fts, rowid, filename, title, body) VALUES('delete', old.id, old.filename, old.title, old.body); INSERT INTO notes_fts(notes_fts, rowid, path, title, body) VALUES('delete', old.id, old.path, old.title, old.body);
END INSERT INTO notes_fts(rowid, path, title, body) VALUES (new.id, new.path, new.title, new.body);
`, END`,
`
CREATE TRIGGER IF NOT EXISTS notes_au AFTER UPDATE ON notes BEGIN
INSERT INTO notes_fts(notes_fts, rowid, filename, title, body) VALUES('delete', old.id, old.filename, old.title, old.body);
INSERT INTO notes_fts(rowid, filename, title, body) VALUES (new.id, new.filename, new.title, new.body);
END
`,
`PRAGMA user_version = 1`, `PRAGMA user_version = 1`,
}) })

@ -36,8 +36,8 @@ func TestMigrateFrom0(t *testing.T) {
assert.Equal(t, version, 1) assert.Equal(t, version, 1)
_, err = tx.Exec(` _, err = tx.Exec(`
INSERT INTO notes (dir, filename, title, body, word_count, checksum) INSERT INTO notes (path, title, body, word_count, checksum)
VALUES ("ref", "tx1.md", "A reference", "Content", 1, "qwfpg") VALUES ("ref/tx1.md", "A reference", "Content", 1, "qwfpg")
`) `)
assert.Nil(t, err) assert.Nil(t, err)

@ -1,6 +1,5 @@
- id: 1 - id: 1
dir: "log" path: "log/2021-01-03.md"
filename: "2021-01-03.md"
title: "January 3, 2021" title: "January 3, 2021"
body: "A daily note" body: "A daily note"
word_count: 3 word_count: 3
@ -9,8 +8,7 @@
modified: "2020-11-22T16:27:45.734454655+01:00" modified: "2020-11-22T16:27:45.734454655+01:00"
- id: 2 - id: 2
dir: "log" path: "log/2021-01-04.md"
filename: "2021-01-04.md"
title: "January 4, 2021" title: "January 4, 2021"
body: "A second daily note" body: "A second daily note"
word_count: 4 word_count: 4
@ -19,8 +17,7 @@
modified: "2020-11-29T08:20:18.138907236+01:00" modified: "2020-11-29T08:20:18.138907236+01:00"
- id: 3 - id: 3
dir: "" path: "index.md"
filename: "index.md"
title: "Index" title: "Index"
body: "Index of the Zettelkasten" body: "Index of the Zettelkasten"
word_count: 4 word_count: 4
@ -29,8 +26,7 @@
modified: "2019-12-04T12:17:21.720747+01:00" modified: "2019-12-04T12:17:21.720747+01:00"
- id: 4 - id: 4
dir: "" path: "f39c8.md"
filename: "f39c8.md"
title: "An interesting note" title: "An interesting note"
body: "Its content will surprise you" body: "Its content will surprise you"
word_count: 5 word_count: 5
@ -39,8 +35,7 @@
modified: "2020-01-20T08:52:42.321024+01:00" modified: "2020-01-20T08:52:42.321024+01:00"
- id: 5 - id: 5
dir: "ref/test" path: "ref/test/b.md"
filename: "b.md"
title: "A nested note" title: "A nested note"
body: "This one is in a sub sub directory" body: "This one is in a sub sub directory"
word_count: 8 word_count: 8
@ -49,8 +44,7 @@
modified: "2019-11-20T20:34:06.120375+01:00" modified: "2019-11-20T20:34:06.120375+01:00"
- id: 6 - id: 6
dir: "ref/test" path: "ref/test/a.md"
filename: "a.md"
title: "Another nested note" title: "Another nested note"
body: "It shall appear before b.md" body: "It shall appear before b.md"
word_count: 5 word_count: 5

@ -28,7 +28,7 @@ func (cmd *Index) Run(container *Container) error {
} }
return db.WithTransaction(func(tx sqlite.Transaction) error { return db.WithTransaction(func(tx sqlite.Transaction) error {
notes := sqlite.NewNoteDAO(tx, zk.Path, container.Logger) notes := sqlite.NewNoteDAO(tx, container.Logger)
return note.Index(*dir, notes, container.Logger) return note.Index(*dir, notes, container.Logger)
}) })
} }

@ -1,12 +1,12 @@
package file package file
// DiffChange represents a file change made in a slip box directory. // DiffChange represents a file change made in a directory.
type DiffChange struct { type DiffChange struct {
Path Path Path string
Kind DiffKind Kind DiffKind
} }
// DiffKind represents a type of file change made in a slip box directory. // DiffKind represents a type of file change made in a directory.
type DiffKind int type DiffKind int
const ( const (
@ -82,7 +82,7 @@ func (p *diffPair) diff() *DiffChange {
p.target = nil p.target = nil
default: // Different files, one has been added or removed. default: // Different files, one has been added or removed.
if p.source.Path.Less(p.target.Path) { if p.source.Path < p.target.Path {
change = &DiffChange{p.source.Path, DiffAdded} change = &DiffChange{p.source.Path, DiffAdded}
p.source = nil p.source = nil
} else { } else {

@ -22,15 +22,15 @@ func TestDiffEmpty(t *testing.T) {
func TestNoDiff(t *testing.T) { func TestNoDiff(t *testing.T) {
files := []Metadata{ files := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Modified: date2, Modified: date2,
}, },
{ {
Path: Path{Dir: "b", Filename: "1"}, Path: "b/1",
Modified: date3, Modified: date3,
}, },
} }
@ -41,15 +41,15 @@ func TestNoDiff(t *testing.T) {
func TestDiff(t *testing.T) { func TestDiff(t *testing.T) {
source := []Metadata{ source := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Modified: date2, Modified: date2,
}, },
{ {
Path: Path{Dir: "b", Filename: "1"}, Path: "b/1",
Modified: date3, Modified: date3,
}, },
} }
@ -57,33 +57,33 @@ func TestDiff(t *testing.T) {
target := []Metadata{ target := []Metadata{
{ {
// Date changed // Date changed
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1.Add(time.Hour), Modified: date1.Add(time.Hour),
}, },
// 2 is added // 2 is added
{ {
// 3 is removed // 3 is removed
Path: Path{Dir: "a", Filename: "3"}, Path: "a/3",
Modified: date3, Modified: date3,
}, },
{ {
// No change // No change
Path: Path{Dir: "b", Filename: "1"}, Path: "b/1",
Modified: date3, Modified: date3,
}, },
} }
test(t, source, target, []DiffChange{ test(t, source, target, []DiffChange{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Kind: DiffModified, Kind: DiffModified,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Kind: DiffAdded, Kind: DiffAdded,
}, },
{ {
Path: Path{Dir: "a", Filename: "3"}, Path: "a/3",
Kind: DiffRemoved, Kind: DiffRemoved,
}, },
}) })
@ -92,25 +92,25 @@ func TestDiff(t *testing.T) {
func TestDiffWithMoreInSource(t *testing.T) { func TestDiffWithMoreInSource(t *testing.T) {
source := []Metadata{ source := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Modified: date2, Modified: date2,
}, },
} }
target := []Metadata{ target := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
} }
test(t, source, target, []DiffChange{ test(t, source, target, []DiffChange{
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Kind: DiffAdded, Kind: DiffAdded,
}, },
}) })
@ -119,25 +119,25 @@ func TestDiffWithMoreInSource(t *testing.T) {
func TestDiffWithMoreInTarget(t *testing.T) { func TestDiffWithMoreInTarget(t *testing.T) {
source := []Metadata{ source := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
} }
target := []Metadata{ target := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Modified: date2, Modified: date2,
}, },
} }
test(t, source, target, []DiffChange{ test(t, source, target, []DiffChange{
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Kind: DiffRemoved, Kind: DiffRemoved,
}, },
}) })
@ -148,22 +148,22 @@ func TestDiffEmptySource(t *testing.T) {
target := []Metadata{ target := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Modified: date2, Modified: date2,
}, },
} }
test(t, source, target, []DiffChange{ test(t, source, target, []DiffChange{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Kind: DiffRemoved, Kind: DiffRemoved,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Kind: DiffRemoved, Kind: DiffRemoved,
}, },
}) })
@ -172,11 +172,11 @@ func TestDiffEmptySource(t *testing.T) {
func TestDiffEmptyTarget(t *testing.T) { func TestDiffEmptyTarget(t *testing.T) {
source := []Metadata{ source := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Modified: date2, Modified: date2,
}, },
} }
@ -185,11 +185,11 @@ func TestDiffEmptyTarget(t *testing.T) {
test(t, source, target, []DiffChange{ test(t, source, target, []DiffChange{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Kind: DiffAdded, Kind: DiffAdded,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Kind: DiffAdded, Kind: DiffAdded,
}, },
}) })
@ -198,11 +198,11 @@ func TestDiffEmptyTarget(t *testing.T) {
func TestDiffCancellation(t *testing.T) { func TestDiffCancellation(t *testing.T) {
source := []Metadata{ source := []Metadata{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Modified: date1, Modified: date1,
}, },
{ {
Path: Path{Dir: "a", Filename: "2"}, Path: "a/2",
Modified: date2, Modified: date2,
}, },
} }
@ -222,7 +222,7 @@ func TestDiffCancellation(t *testing.T) {
assert.Equal(t, received, []DiffChange{ assert.Equal(t, received, []DiffChange{
{ {
Path: Path{Dir: "a", Filename: "1"}, Path: "a/1",
Kind: DiffAdded, Kind: DiffAdded,
}, },
}) })

@ -1,36 +1,11 @@
package file package file
import ( import (
"path/filepath"
"time" "time"
) )
// Metadata holds information about a slip box file. // Metadata holds information about a slip box file.
type Metadata struct { type Metadata struct {
Path Path Path string
Modified time.Time 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
}
}
func (p Path) String() string {
return filepath.Join(p.Dir, p.Filename)
}

@ -47,11 +47,7 @@ func Walk(dir zk.Dir, extension string, logger util.Logger) <-chan Metadata {
} }
c <- Metadata{ c <- Metadata{
Path: Path{ Path: filepath.Join(dir.Name, curDir, filename),
Dir: filepath.Join(dir.Name, curDir),
Filename: filename,
Abs: abs,
},
Modified: info.ModTime(), Modified: info.ModTime(),
} }
} }

@ -15,29 +15,29 @@ var root = fixtures.Path("walk")
func TestWalkRootDir(t *testing.T) { func TestWalkRootDir(t *testing.T) {
dir := zk.Dir{Name: "", Path: root} dir := zk.Dir{Name: "", Path: root}
testEqual(t, Walk(dir, "md", &util.NullLogger), []Path{ testEqual(t, Walk(dir, "md", &util.NullLogger), []string{
{Dir: "", Filename: "a.md", Abs: filepath.Join(root, "a.md")}, "a.md",
{Dir: "", Filename: "b.md", Abs: filepath.Join(root, "b.md")}, "b.md",
{Dir: "dir1", Filename: "a.md", Abs: filepath.Join(root, "dir1/a.md")}, "dir1/a.md",
{Dir: "dir1", Filename: "b.md", Abs: filepath.Join(root, "dir1/b.md")}, "dir1/b.md",
{Dir: "dir1/dir1", Filename: "a.md", Abs: filepath.Join(root, "dir1/dir1/a.md")}, "dir1/dir1/a.md",
{Dir: "dir2", Filename: "a.md", Abs: filepath.Join(root, "dir2/a.md")}, "dir2/a.md",
}) })
} }
func TestWalkSubDir(t *testing.T) { func TestWalkSubDir(t *testing.T) {
dir := zk.Dir{Name: "dir1", Path: filepath.Join(root, "dir1")} dir := zk.Dir{Name: "dir1", Path: filepath.Join(root, "dir1")}
testEqual(t, Walk(dir, "md", &util.NullLogger), []Path{ testEqual(t, Walk(dir, "md", &util.NullLogger), []string{
{Dir: "dir1", Filename: "a.md", Abs: filepath.Join(root, "dir1/a.md")}, "dir1/a.md",
{Dir: "dir1", Filename: "b.md", Abs: filepath.Join(root, "dir1/b.md")}, "dir1/b.md",
{Dir: "dir1/dir1", Filename: "a.md", Abs: filepath.Join(root, "dir1/dir1/a.md")}, "dir1/dir1/a.md",
}) })
} }
func TestWalkSubSubDir(t *testing.T) { func TestWalkSubSubDir(t *testing.T) {
dir := zk.Dir{Name: "dir1/dir1", Path: filepath.Join(root, "dir1/dir1")} dir := zk.Dir{Name: "dir1/dir1", Path: filepath.Join(root, "dir1/dir1")}
testEqual(t, Walk(dir, "md", &util.NullLogger), []Path{ testEqual(t, Walk(dir, "md", &util.NullLogger), []string{
{Dir: "dir1/dir1", Filename: "a.md", Abs: filepath.Join(root, "dir1/dir1/a.md")}, "dir1/dir1/a.md",
}) })
} }
@ -46,10 +46,10 @@ func date(s string) time.Time {
return date return date
} }
func testEqual(t *testing.T, actual <-chan Metadata, expected []Path) { func testEqual(t *testing.T, actual <-chan Metadata, expected []string) {
popExpected := func() (Path, bool) { popExpected := func() (string, bool) {
if len(expected) == 0 { if len(expected) == 0 {
return Path{}, false return "", false
} }
item := expected[0] item := expected[0]
expected = expected[1:] expected = expected[1:]

@ -4,6 +4,7 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath"
"strings" "strings"
"time" "time"
@ -16,7 +17,7 @@ import (
// Metadata holds information about a particular note. // Metadata holds information about a particular note.
type Metadata struct { type Metadata struct {
Path file.Path Path string
Title string Title string
Body string Body string
WordCount int WordCount int
@ -34,7 +35,7 @@ type Indexer interface {
// Update updates the metadata of an already indexed note. // Update updates the metadata of an already indexed note.
Update(metadata Metadata) error Update(metadata Metadata) error
// Remove deletes a note from the index. // Remove deletes a note from the index.
Remove(path file.Path) error Remove(path string) error
} }
// Index indexes the content of the notes in the given directory. // Index indexes the content of the notes in the given directory.
@ -50,14 +51,14 @@ func Index(dir zk.Dir, indexer Indexer, logger util.Logger) error {
err = file.Diff(source, target, func(change file.DiffChange) error { err = file.Diff(source, target, func(change file.DiffChange) error {
switch change.Kind { switch change.Kind {
case file.DiffAdded: case file.DiffAdded:
metadata, err := metadata(change.Path) metadata, err := metadata(change.Path, dir.Path)
if err == nil { if err == nil {
err = indexer.Add(metadata) err = indexer.Add(metadata)
} }
logger.Err(err) logger.Err(err)
case file.DiffModified: case file.DiffModified:
metadata, err := metadata(change.Path) metadata, err := metadata(change.Path, dir.Path)
if err == nil { if err == nil {
err = indexer.Update(metadata) err = indexer.Update(metadata)
} }
@ -74,12 +75,13 @@ func Index(dir zk.Dir, indexer Indexer, logger util.Logger) error {
} }
// metadata retrieves note metadata for the given file. // metadata retrieves note metadata for the given file.
func metadata(path file.Path) (Metadata, error) { func metadata(path string, basePath string) (Metadata, error) {
metadata := Metadata{ metadata := Metadata{
Path: path, Path: path,
} }
content, err := ioutil.ReadFile(path.Abs) absPath := filepath.Join(basePath, path)
content, err := ioutil.ReadFile(absPath)
if err != nil { if err != nil {
return metadata, err return metadata, err
} }
@ -90,7 +92,7 @@ func metadata(path file.Path) (Metadata, error) {
metadata.WordCount = len(strings.Fields(contentStr)) metadata.WordCount = len(strings.Fields(contentStr))
metadata.Checksum = fmt.Sprintf("%x", sha256.Sum256(content)) metadata.Checksum = fmt.Sprintf("%x", sha256.Sum256(content))
times, err := times.Stat(path.Abs) times, err := times.Stat(absPath)
if err != nil { if err != nil {
return metadata, err return metadata, err
} }

Loading…
Cancel
Save