diff --git a/lib/box/index.js b/lib/box/index.js index db98381779..8d29715c02 100644 --- a/lib/box/index.js +++ b/lib/box/index.js @@ -58,13 +58,10 @@ function getHash(path) { } Box.prototype._createFileClass = function() { - var Cache = this.Cache; var ctx = this.context; var _File = function(data) { File.call(this, data); - - this._typeSolved = data.type === File.TYPE_DELETE; }; require('util').inherits(_File, File); @@ -77,48 +74,17 @@ Box.prototype._createFileClass = function() { options = {}; } - var self = this; - - return this.read().then(function(content) { - return ctx.render.render({ - text: content, - path: self.source - }, options); - }).asCallback(callback); + return ctx.render.render({ + path: this.source + }, options).asCallback(callback); }; _File.prototype.renderSync = function(options) { return ctx.render.renderSync({ - text: this.readSync(), path: this.source }, options); }; - _File.prototype.changed = function(callback) { - if (this._typeSolved) { - return Promise.resolve(this.type !== File.TYPE_SKIP).asCallback(callback); - } - - var self = this; - - // TODO: Share file type between processors - return Cache.compareFile( - this.source.substring(ctx.base_dir.length), - function() { - return getHash(self.source); - }, - - function() { - return self.stat(); - } - ).then(function(result) { - self.type = result.type; - self._typeSolved = true; - - return result.type !== File.TYPE_SKIP; - }).asCallback(callback); - }; - return _File; }; @@ -137,17 +103,20 @@ Box.prototype.addProcessor = function(pattern, fn) { }); }; -function readDir(base, fn, prefix) { +Box.prototype._readDir = function(base, fn, prefix) { prefix = prefix || ''; + var self = this; + return fs.readdir(base).map(function(path) { return fs.stat(join(base, path)).then(function(stats) { if (stats.isDirectory()) { - return readDir(join(base, path), fn, prefix + path + '/'); + return self._readDir(join(base, path), fn, prefix + path + '/'); } - var relativePath = prefix + path; - return fn(relativePath).thenReturn(relativePath); + return self._checkFileStatus(prefix + path).then(function(file) { + return fn(file).thenReturn(file); + }); }); }).catch(function(err) { if (err.cause && err.cause.code === 'ENOENT') return; @@ -155,7 +124,29 @@ function readDir(base, fn, prefix) { }).reduce(function(files, item) { return files.concat(item); }, []); -} +}; + +Box.prototype._checkFileStatus = function(path) { + var Cache = this.Cache; + var src = join(this.base, path); + var ctx = this.context; + + return Cache.compareFile( + src.substring(ctx.base_dir.length), + function() { + return getHash(src); + }, + + function() { + return fs.stat(src); + } + ).then(function(result) { + return { + type: result.type, + path: path + }; + }); +}; Box.prototype.process = function(callback) { var self = this; @@ -175,10 +166,10 @@ Box.prototype.process = function(callback) { }); // Read files from directory - return readDir(base, function(path) { - // If the files is not in the cache, means it's new - var type = ~cacheFiles.indexOf(path) ? File.TYPE_UPDATE : File.TYPE_CREATE; - return self._processFile(type, path); + return self._readDir(base, function(file) { + return self._processFile(file.type, file.path); + }).map(function(file) { + return file.path; }).then(function(files) { // Handle deleted files return Promise.filter(cacheFiles, function(path) { @@ -273,8 +264,8 @@ Box.prototype.watch = function(callback) { var prefix = getPath(path); if (prefix) prefix += '/'; - readDir(path, function(path) { - return self._processFile(File.TYPE_CREATE, path); + self._readDir(path, function(file) { + return self._processFile(file.type, file.path); }, prefix); }); }).asCallback(callback); diff --git a/lib/plugins/processor/asset.js b/lib/plugins/processor/asset.js index bf49e6ee47..20a3a2c03e 100644 --- a/lib/plugins/processor/asset.js +++ b/lib/plugins/processor/asset.js @@ -15,6 +15,10 @@ module.exports = function(ctx) { var config = ctx.config; var timezone = config.timezone; + if (file.type === 'skip' && doc) { + return; + } + if (file.type === 'delete') { if (doc) { return doc.remove(); @@ -23,65 +27,61 @@ module.exports = function(ctx) { return; } - return file.changed().then(function(changed) { - if (!changed && doc) return; - - return Promise.all([ - file.stat(), - file.read() - ]).spread(function(stats, content) { - var data = yfm(content); - var output = ctx.render.getOutput(path); + return Promise.all([ + file.stat(), + file.read() + ]).spread(function(stats, content) { + var data = yfm(content); + var output = ctx.render.getOutput(path); - data.source = path; - data.raw = content; + data.source = path; + data.raw = content; - data.date = common.toDate(data.date); + data.date = common.toDate(data.date); - if (data.date) { - if (timezone) data.date = common.timezone(data.date, timezone); - } else { - data.date = stats.ctime; - } - - data.updated = common.toDate(data.updated); + if (data.date) { + if (timezone) data.date = common.timezone(data.date, timezone); + } else { + data.date = stats.ctime; + } - if (data.updated) { - if (timezone) data.updated = common.timezone(data.updated, timezone); - } else { - data.updated = stats.mtime; - } + data.updated = common.toDate(data.updated); - if (data.permalink) { - data.path = data.permalink; - delete data.permalink; + if (data.updated) { + if (timezone) data.updated = common.timezone(data.updated, timezone); + } else { + data.updated = stats.mtime; + } - if (data.path[data.path.length - 1] === '/') { - data.path += 'index'; - } + if (data.permalink) { + data.path = data.permalink; + delete data.permalink; - if (!pathFn.extname(data.path)) { - data.path += '.' + output; - } - } else { - var extname = pathFn.extname(path); - data.path = path.substring(0, path.length - extname.length) + '.' + output; + if (data.path[data.path.length - 1] === '/') { + data.path += 'index'; } - if (!data.layout && output !== 'html' && output !== 'htm') { - data.layout = false; + if (!pathFn.extname(data.path)) { + data.path += '.' + output; } + } else { + var extname = pathFn.extname(path); + data.path = path.substring(0, path.length - extname.length) + '.' + output; + } - // FIXME: Data may be inserted when reading files. Load it again to prevent - // race condition. We have to solve this in warehouse. - var doc = Page.findOne({source: path}); + if (!data.layout && output !== 'html' && output !== 'htm') { + data.layout = false; + } - if (doc) { - return doc.replace(data); - } + // FIXME: Data may be inserted when reading files. Load it again to prevent + // race condition. We have to solve this in warehouse. + var doc = Page.findOne({source: path}); + + if (doc) { + return doc.replace(data); + } - return Page.insert(data); - }); + return Page.insert(data); }); } @@ -98,13 +98,11 @@ module.exports = function(ctx) { return; } - return file.changed().then(function(changed) { - return Asset.save({ - _id: id, - path: file.path, - modified: changed, - renderable: file.params.renderable - }); + return Asset.save({ + _id: id, + path: file.path, + modified: file.type !== 'skip', + renderable: file.params.renderable }); } diff --git a/lib/plugins/processor/data.js b/lib/plugins/processor/data.js index f1ff3f0214..1f5cd3bbff 100644 --- a/lib/plugins/processor/data.js +++ b/lib/plugins/processor/data.js @@ -14,6 +14,10 @@ module.exports = function(ctx) { var id = path.substring(0, path.length - extname.length); var doc = Data.findById(id); + if (file.type === 'skip' && doc) { + return; + } + if (file.type === 'delete') { if (doc) { return doc.remove(); @@ -22,11 +26,7 @@ module.exports = function(ctx) { return; } - return file.changed().then(function(changed) { - if (!changed && doc) return; - - return file.render(); - }).then(function(result) { + return file.render().then(function(result) { if (result == null) return; return Data.save({ diff --git a/lib/plugins/processor/post.js b/lib/plugins/processor/post.js index f668822a6e..399ed1a7de 100644 --- a/lib/plugins/processor/post.js +++ b/lib/plugins/processor/post.js @@ -33,6 +33,10 @@ module.exports = function(ctx) { var timezone = config.timezone; var categories, tags; + if (file.type === 'skip' && doc) { + return; + } + if (file.type === 'delete') { if (doc) { return doc.remove(); @@ -41,107 +45,103 @@ module.exports = function(ctx) { return; } - return file.changed().then(function(changed) { - if (!changed && doc) return; + return Promise.all([ + file.stat(), + file.read() + ]).spread(function(stats, content) { + var data = yfm(content); + var info = parseFilename(config.new_post_name, path); + var keys = Object.keys(info); + var key; + + data.source = file.path; + data.raw = content; + data.slug = info.title; + + if (file.params.published) { + if (!data.hasOwnProperty('published')) data.published = true; + } else { + data.published = false; + } + + for (var i = 0, len = keys.length; i < len; i++) { + key = keys[i]; + if (!preservedKeys[key]) data[key] = info[key]; + } + + if (data.date) { + data.date = common.toDate(data.date); + } else if (info && info.year && (info.month || info.i_month) && (info.day || info.i_day)) { + data.date = new Date( + info.year, + parseInt(info.month || info.i_month, 10) - 1, + parseInt(info.day || info.i_day, 10) + ); + } + + if (data.date) { + if (timezone) data.date = common.timezone(data.date, timezone); + } else { + data.date = stats.birthtime; + } + + data.updated = common.toDate(data.updated); + + if (data.updated) { + if (timezone) data.updated = common.timezone(data.updated, timezone); + } else { + data.updated = stats.mtime; + } + + if (data.category && !data.categories) { + data.categories = data.category; + delete data.category; + } + + if (data.tag && !data.tags) { + data.tags = data.tag; + delete data.tag; + } + + categories = data.categories || []; + tags = data.tags || []; + + if (!Array.isArray(categories)) categories = [categories]; + if (!Array.isArray(tags)) tags = [tags]; + if (data.photo && !data.photos) { + data.photos = data.photo; + delete data.photo; + } + + if (data.photos && !Array.isArray(data.photos)) { + data.photos = [data.photos]; + } + + if (data.link && !data.title) { + data.title = data.link.replace(/^https?:\/\/|\/$/g, ''); + } + + if (data.permalink) { + data.slug = data.permalink; + delete data.permalink; + } + + // FIXME: Data may be inserted when reading files. Load it again to prevent + // race condition. We have to solve this in warehouse. + var doc = Post.findOne({source: file.path}); + + if (doc) { + return doc.replace(data); + } + + return Post.insert(data); + }).then(function(doc) { return Promise.all([ - file.stat(), - file.read() - ]).spread(function(stats, content) { - var data = yfm(content); - var info = parseFilename(config.new_post_name, path); - var keys = Object.keys(info); - var key; - - data.source = file.path; - data.raw = content; - data.slug = info.title; - - if (file.params.published) { - if (!data.hasOwnProperty('published')) data.published = true; - } else { - data.published = false; - } - - for (var i = 0, len = keys.length; i < len; i++) { - key = keys[i]; - if (!preservedKeys[key]) data[key] = info[key]; - } - - if (data.date) { - data.date = common.toDate(data.date); - } else if (info && info.year && (info.month || info.i_month) && (info.day || info.i_day)) { - data.date = new Date( - info.year, - parseInt(info.month || info.i_month, 10) - 1, - parseInt(info.day || info.i_day, 10) - ); - } - - if (data.date) { - if (timezone) data.date = common.timezone(data.date, timezone); - } else { - data.date = stats.birthtime; - } - - data.updated = common.toDate(data.updated); - - if (data.updated) { - if (timezone) data.updated = common.timezone(data.updated, timezone); - } else { - data.updated = stats.mtime; - } - - if (data.category && !data.categories) { - data.categories = data.category; - delete data.category; - } - - if (data.tag && !data.tags) { - data.tags = data.tag; - delete data.tag; - } - - categories = data.categories || []; - tags = data.tags || []; - - if (!Array.isArray(categories)) categories = [categories]; - if (!Array.isArray(tags)) tags = [tags]; - - if (data.photo && !data.photos) { - data.photos = data.photo; - delete data.photo; - } - - if (data.photos && !Array.isArray(data.photos)) { - data.photos = [data.photos]; - } - - if (data.link && !data.title) { - data.title = data.link.replace(/^https?:\/\/|\/$/g, ''); - } - - if (data.permalink) { - data.slug = data.permalink; - delete data.permalink; - } - - // FIXME: Data may be inserted when reading files. Load it again to prevent - // race condition. We have to solve this in warehouse. - var doc = Post.findOne({source: file.path}); - - if (doc) { - return doc.replace(data); - } - - return Post.insert(data); - }).then(function(doc) { - return Promise.all([ - doc.setCategories(categories), - doc.setTags(tags), - scanAssetDir(doc) - ]); - }); + doc.setCategories(categories), + doc.setTags(tags), + scanAssetDir(doc) + ]); }); } @@ -190,29 +190,27 @@ module.exports = function(ctx) { return; } - return file.changed().then(function(changed) { - // TODO: Better post searching - var posts = Post.toArray(); - var post; - - for (var i = 0, len = posts.length; i < len; i++) { - post = posts[i]; - - if (_.startsWith(file.source, post.asset_dir)) { - return PostAsset.save({ - _id: id, - slug: file.source.substring(post.asset_dir.length), - post: post._id, - modified: changed, - renderable: file.params.renderable - }); - } + // TODO: Better post searching + var posts = Post.toArray(); + var post; + + for (var i = 0, len = posts.length; i < len; i++) { + post = posts[i]; + + if (_.startsWith(file.source, post.asset_dir)) { + return PostAsset.save({ + _id: id, + slug: file.source.substring(post.asset_dir.length), + post: post._id, + modified: file.type !== 'skip', + renderable: file.params.renderable + }); } + } - if (doc) { - return doc.remove(); - } - }); + if (doc) { + return doc.remove(); + } } return { diff --git a/lib/theme/processors/source.js b/lib/theme/processors/source.js index fe463b7ad2..5024e94ca2 100644 --- a/lib/theme/processors/source.js +++ b/lib/theme/processors/source.js @@ -18,12 +18,10 @@ exports.process = function(file) { return; } - return file.changed().then(function(changed) { - return Asset.save({ - _id: id, - path: path, - modified: changed - }); + return Asset.save({ + _id: id, + path: path, + modified: file.type !== 'skip' }); }; diff --git a/test/scripts/box/box.js b/test/scripts/box/box.js index 38c75059c6..263ec07dcc 100644 --- a/test/scripts/box/box.js +++ b/test/scripts/box/box.js @@ -127,43 +127,99 @@ describe('Box', function() { it('process() - create', function() { var box = newBox('test'); - var path = pathFn.join(box.base, 'a.txt'); - - var processor = sinon.spy(function(file) { - file.type.should.eql('create'); - }); + var name = 'a.txt'; + var path = pathFn.join(box.base, name); + var processor = sinon.spy(); box.addProcessor(processor); return fs.writeFile(path, 'a').then(function() { return box.process(); }).then(function() { - processor.calledOnce.should.be.true; + var file = processor.args[0][0]; + file.type.should.eql('create'); + file.path.should.eql(name); }).finally(function() { return fs.rmdir(box.base); }); }); - it('process() - update', function() { + it('process() - mtime changed', function() { var box = newBox('test'); - var path = pathFn.join(box.base, 'a.txt'); - var cacheId = 'test/a.txt'; - - var processor = sinon.spy(function(file) { - file.type.should.eql('update'); - }); + var name = 'a.txt'; + var path = pathFn.join(box.base, name); + var cacheId = 'test/' + name; + var processor = sinon.spy(); box.addProcessor(processor); return Promise.all([ fs.writeFile(path, 'a'), box.Cache.insert({ - _id: cacheId + _id: cacheId, + modified: 0 }) ]).then(function() { return box.process(); }).then(function() { - processor.calledOnce.should.be.true; + var file = processor.args[0][0]; + file.type.should.eql('update'); + file.path.should.eql(name); + }).finally(function() { + return fs.rmdir(box.base); + }); + }); + + it('process() - hash changed', function() { + var box = newBox('test'); + var name = 'a.txt'; + var path = pathFn.join(box.base, name); + var cacheId = 'test/' + name; + + var processor = sinon.spy(); + box.addProcessor(processor); + + return fs.writeFile(path, 'a').then(function() { + return fs.stat(path); + }).then(function(stats) { + return box.Cache.insert({ + _id: cacheId, + modified: stats.mtime + }); + }).then(function() { + return box.process(); + }).then(function() { + var file = processor.args[0][0]; + file.type.should.eql('update'); + file.path.should.eql(name); + }).finally(function() { + return fs.rmdir(box.base); + }); + }); + + it('process() - skip', function() { + var box = newBox('test'); + var name = 'a.txt'; + var path = pathFn.join(box.base, name); + var cacheId = 'test/' + name; + + var processor = sinon.spy(); + box.addProcessor(processor); + + return fs.writeFile(path, 'a').then(function() { + return fs.stat(path); + }).then(function(stats) { + return box.Cache.insert({ + _id: cacheId, + modified: stats.mtime, + hash: util.hash('a').toString('hex') + }); + }).then(function() { + return box.process(); + }).then(function() { + var file = processor.args[0][0]; + file.type.should.eql('skip'); + file.path.should.eql(name); }).finally(function() { return fs.rmdir(box.base); }); diff --git a/test/scripts/box/file.js b/test/scripts/box/file.js index 8d14b0f6ba..101ed41fdf 100644 --- a/test/scripts/box/file.js +++ b/test/scripts/box/file.js @@ -6,7 +6,6 @@ var Promise = require('bluebird'); var fs = require('hexo-fs'); var yaml = require('js-yaml'); var _ = require('lodash'); -var util = require('hexo-util'); describe('File', function() { var Hexo = require('../../../lib/hexo'); @@ -14,7 +13,6 @@ describe('File', function() { var Box = require('../../../lib/box'); var box = new Box(hexo, pathFn.join(hexo.base_dir, 'file_test')); var File = box.File; - var Cache = box.Cache; var body = [ 'name:', @@ -30,8 +28,6 @@ describe('File', function() { var obj = yaml.load(body); var path = 'test.yml'; - var hash = util.hash(body).toString('hex'); - var stats; function makeFile(path, props) { return new File(_.assign({ @@ -40,14 +36,6 @@ describe('File', function() { }, props)); } - function getCacheId(path) { - return pathFn.join('file_test', path); - } - - function removeCache(path) { - return Cache.removeById(getCacheId(path)); - } - var file = makeFile(path, { source: pathFn.join(box.base, path), path: path, @@ -61,8 +49,6 @@ describe('File', function() { hexo.init() ]).then(function() { return fs.stat(file.source); - }).then(function(stats_) { - stats = stats_; }); }); @@ -124,106 +110,4 @@ describe('File', function() { it('renderSync()', function() { file.renderSync().should.eql(obj); }); - - it('changed() - create', function() { - var file = makeFile(path, { - type: 'create' - }); - - return file.changed().then(function(changed) { - changed.should.be.true; - return removeCache(path); - }); - }); - - it('changed() - mtime changed', function() { - var file = makeFile(path, { - type: 'update' - }); - - return Cache.insert({ - _id: getCacheId(path), - modified: 0 - }).then(function() { - return file.changed(); - }).then(function(changed) { - var cache = Cache.findById(getCacheId(path)); - - changed.should.be.true; - cache.modified.should.eql(stats.mtime.getTime()); - cache.hash.should.eql(hash); - - return removeCache(path); - }); - }); - - it('changed() - hash changed', function() { - var file = makeFile(path, { - type: 'update' - }); - - return Cache.insert({ - _id: getCacheId(path), - modified: stats.mtime, - hash: 'ewrowerjoweijr' - }).then(function() { - return file.changed(); - }).then(function(changed) { - var cache = Cache.findById(getCacheId(path)); - - changed.should.be.true; - cache.modified.should.eql(stats.mtime.getTime()); - cache.hash.should.eql(hash); - - return removeCache(path); - }); - }); - - it('changed() - skip', function() { - var file = makeFile(path, { - type: 'update' - }); - - return Cache.insert({ - _id: getCacheId(path), - modified: stats.mtime, - hash: hash - }).then(function() { - return file.changed(); - }).then(function(changed) { - changed.should.be.false; - return removeCache(path); - }); - }); - - it('changed() - skip solved', function() { - var file = makeFile(path, { - type: 'skip' - }); - - file._typeSolved = true; - - return file.changed().should.eventually.be.false; - }); - - it('changed() - delete', function() { - var file = makeFile(path, { - type: 'delete' - }); - - return file.changed().should.eventually.be.true; - }); - - it('changed() - callback', function(callback) { - var file = makeFile(path, { - type: 'create' - }); - - file.changed(function(err, changed) { - if (err) return callback(err); - - changed.should.be.true; - removeCache(path).asCallback(callback); - }); - }); }); diff --git a/test/scripts/processors/asset.js b/test/scripts/processors/asset.js index d25149292f..5cd0d716cc 100644 --- a/test/scripts/processors/asset.js +++ b/test/scripts/processors/asset.js @@ -127,50 +127,13 @@ describe('asset', function() { }); }); - it('asset - changed', function() { + it('asset - type: skip', function() { var file = newFile({ path: 'foo.jpg', - type: 'update', + type: 'skip', renderable: false }); - file.changed = function() { - return Promise.resolve(true); - }; - - var id = 'source/' + file.path; - - return Promise.all([ - fs.writeFile(file.source, 'test'), - Asset.insert({ - _id: id, - path: file.path, - modified: false - }) - ]).then(function() { - return process(file); - }).then(function() { - var asset = Asset.findById(id); - asset.modified.should.be.true; - }).finally(function() { - return Promise.all([ - Asset.removeById(id), - fs.unlink(file.source) - ]); - }); - }); - - it('asset - unchanged', function() { - var file = newFile({ - path: 'foo.jpg', - type: 'update', - renderable: false - }); - - file.changed = function() { - return Promise.resolve(false); - }; - var id = 'source/' + file.path; return Promise.all([ diff --git a/test/scripts/processors/data.js b/test/scripts/processors/data.js index e880dc5afc..724e7370fa 100644 --- a/test/scripts/processors/data.js +++ b/test/scripts/processors/data.js @@ -115,10 +115,6 @@ describe('data', function() { type: 'update' }); - file.changed = function() { - return Promise.resolve(true); - }; - return Promise.all([ fs.writeFile(file.source, body), Data.insert({ diff --git a/test/scripts/processors/post.js b/test/scripts/processors/post.js index 0a51b3873b..7d7407b456 100644 --- a/test/scripts/processors/post.js +++ b/test/scripts/processors/post.js @@ -158,10 +158,6 @@ describe('post', function() { renderable: false }); - file.changed = function() { - return Promise.resolve(true); - }; - var id = 'source/' + file.path; var postId; @@ -195,20 +191,16 @@ describe('post', function() { }); }); - it('asset - unchanged', function() { + it('asset - type: skip', function() { hexo.config.post_asset_folder = true; var file = newFile({ path: 'foo/bar.jpg', published: true, - type: 'update', + type: 'skip', renderable: false }); - file.changed = function() { - return Promise.resolve(false); - }; - var id = 'source/' + file.path; var postId; diff --git a/test/scripts/tags/code.js b/test/scripts/tags/code.js index e5bf8233a8..5abce69b7b 100644 --- a/test/scripts/tags/code.js +++ b/test/scripts/tags/code.js @@ -126,7 +126,6 @@ describe('code', function() { '});' ].join('\n'); var result = code('mark:1,7-8,10', source); - console.log(result); result.should.eql(highlight(source, { mark: [1, 7, 8, 10] })); diff --git a/test/scripts/theme_processors/source.js b/test/scripts/theme_processors/source.js index 5924a61733..06421014f3 100644 --- a/test/scripts/theme_processors/source.js +++ b/test/scripts/theme_processors/source.js @@ -79,10 +79,6 @@ describe('source', function() { type: 'update' }); - file.changed = function() { - return Promise.resolve(true); - }; - var id = 'themes/test/' + file.path; return Promise.all([ @@ -106,16 +102,12 @@ describe('source', function() { }); }); - it('unchanged', function() { + it('type: skip', function() { var file = newFile({ path: 'style.css', - type: 'update' + type: 'skip' }); - file.changed = function() { - return Promise.resolve(false); - }; - var id = 'themes/test/' + file.path; return Promise.all([