mirror of
https://github.com/wassname/nvatom.git
synced 2026-06-27 16:10:36 +08:00
136 lines
4.6 KiB
CoffeeScript
136 lines
4.6 KiB
CoffeeScript
chokidar = require 'chokidar'
|
|
fs = require 'fs'
|
|
lunr = require 'lunr'
|
|
path = require 'path'
|
|
_ = require 'underscore-plus'
|
|
zlib = require 'zlib'
|
|
{EventEmitter} = require 'events'
|
|
NoteCache = require './note-cache'
|
|
|
|
module.exports =
|
|
class NoteWatcher extends EventEmitter
|
|
@cacheVersion = 0
|
|
|
|
constructor: (@_baseDirectory, @_extensions, @_maxItems, @_enableLunrPipeline) ->
|
|
@_maxItems = @_maxItems ? 100
|
|
@_extensions = @_extensions ? ['.txt', '.md']
|
|
@_enableLunrPipeline = @_enableLunrPipeline ? false
|
|
@_noteCache = new NoteCache(@_baseDirectory, @_maxItems)
|
|
@_restoreSearchIndex() || @_initSearchIndex()
|
|
@_initWatcher()
|
|
|
|
save: ->
|
|
return unless @_state == 'ready'
|
|
cache = {
|
|
baseDirectory: @_baseDirectory,
|
|
extensions: @_extensions,
|
|
enableLunrPipeline: @_enableLunrPipeline,
|
|
version: @_cacheVersion,
|
|
searchIndex: JSON.stringify(@_searchIndex),
|
|
noteCache: @_noteCache.toJSON(),
|
|
}
|
|
fs.writeFileSync(
|
|
path.join(@_baseDirectory, 'nvatom.cache'),
|
|
zlib.deflateSync(JSON.stringify(cache)))
|
|
|
|
close: ->
|
|
@_watcher?.close()
|
|
|
|
search: (query) ->
|
|
if query? and query.length > 0
|
|
return @_searchIndex.search(query)
|
|
.slice(0, @_maxItems)
|
|
.map((x) => @_noteCache.getNote(x.ref))
|
|
else
|
|
return @_noteCache.getRecentNotes()
|
|
|
|
length: ->
|
|
@_noteCache.length()
|
|
|
|
_initSearchIndex: ->
|
|
@_state = 'initializing'
|
|
@_searchIndex = lunr(() ->
|
|
@field('title', { boost: 10 })
|
|
@field('body')
|
|
)
|
|
if !@_enableLunrPipeline
|
|
@_searchIndex.pipeline.reset()
|
|
|
|
_restoreSearchIndex: ->
|
|
cacheFilePath = path.join(@_baseDirectory, 'nvatom.cache')
|
|
return false unless fs.existsSync(cacheFilePath)
|
|
cache = JSON.parse(zlib.inflateSync(fs.readFileSync(cacheFilePath)))
|
|
return false if cache == null
|
|
return false unless _.isEqual(cache.baseDirectory, @_baseDirectory)
|
|
return false unless _.isEqual(cache.extensions, @_extensions)
|
|
return false unless _.isEqual(cache.enableLunrPipeline, @_enableLunrPipeline)
|
|
return false unless _.isEqual(cache.version, @_cacheVersion)
|
|
@_searchIndex = lunr.Index.load(JSON.parse(cache.searchIndex))
|
|
# TODO: Make this as an interface. Old note cache does not have to know upsert, remove, ready and so on.
|
|
# It only needs to know getNoteIds, hasNoteId, and getModifiedAt.
|
|
@_oldNoteCache = new NoteCache(@_baseDirectory, @_maxItems).load(JSON.parse(cache.noteCache))
|
|
@_state = 'recovering'
|
|
return true
|
|
|
|
_initWatcher: ->
|
|
options = {
|
|
ignored: (filePath, fileStat) =>
|
|
if fileStat?.isFile() then @_extensions.indexOf(path.extname(filePath)) < 0 else false
|
|
}
|
|
|
|
@_watcher = chokidar
|
|
.watch @_baseDirectory, options
|
|
.on 'add', (args) => @_add(args)
|
|
.on 'change', (args) => @_change(args)
|
|
.on 'unlink', (args) => @_unlink(args)
|
|
.on 'ready', () => @_ready()
|
|
|
|
_add: (filePath) ->
|
|
@_noteCache.upsert(@_toNoteId(filePath), fs.statSync(filePath).mtime)
|
|
if @_state != 'recovering'
|
|
@_searchIndex.add(@_toFileDetails(filePath))
|
|
@emit 'update', filePath
|
|
|
|
_change: (filePath) ->
|
|
@_noteCache.upsert(@_toNoteId(filePath), fs.statSync(filePath).mtime)
|
|
if @_state != 'recovering'
|
|
@_searchIndex.update(@_toFileDetails(filePath))
|
|
@emit 'update', filePath
|
|
|
|
_unlink: (filePath) ->
|
|
@_noteCache.remove(@_toNoteId(filePath))
|
|
if @_state != 'recovering'
|
|
@_searchIndex.remove(@_toFileDetails(filePath))
|
|
@emit 'update', filePath
|
|
|
|
_ready: () ->
|
|
@_noteCache.ready()
|
|
if @_state == 'recovering'
|
|
@_updateSearchIndex(@_oldNoteCache, @_noteCache)
|
|
delete @_oldNoteCache
|
|
@_state = 'ready'
|
|
@emit 'ready'
|
|
|
|
_updateSearchIndex: (oldNoteCache, newNoteCache) ->
|
|
for noteId in oldNoteCache.getNoteIds()
|
|
if !newNoteCache.hasNoteId(noteId)
|
|
@_searchIndex.remove(@_toFileDetails(@_toFilePath(noteId)))
|
|
else if oldNoteCache.getModifiedAt(noteId) <= newNoteCache.getModifiedAt(noteId)
|
|
@_searchIndex.update(@_toFileDetails(@_toFilePath(noteId)))
|
|
for noteId in newNoteCache.getNoteIds()
|
|
if !oldNoteCache.hasNoteId(noteId)
|
|
@_searchIndex.add(@_toFileDetails(@_toFilePath(noteId)))
|
|
|
|
_toNoteId: (filePath) ->
|
|
path.relative(@_baseDirectory, filePath)
|
|
|
|
_toFilePath: (noteId) ->
|
|
path.join(@_baseDirectory, noteId)
|
|
|
|
_toFileDetails: (filePath) ->
|
|
fileName = path.basename(filePath)
|
|
return {
|
|
id: @_toNoteId(filePath),
|
|
title: path.basename(fileName, path.extname(fileName)),
|
|
body: if fs.existsSync(filePath) then fs.readFileSync(filePath, { encoding: 'utf8' }) else ''
|
|
} |