Created
November 17, 2014 14:03
-
-
Save octavian-nita/073dd3a964988e0551c3 to your computer and use it in GitHub Desktop.
Simple gallery entity for Node.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 'use strict'; | |
| var | |
| fs = require('fs'), | |
| path = require('path'), | |
| config = require('./config'), | |
| log = require('./log'), | |
| x = require('./xutil'); | |
| function Gallery(slug, title, description) { | |
| if (!(this instanceof Gallery)) { return new Gallery(slug, title, description); } | |
| if (!(slug = slug && slug.trim())) { throw new Error('cannot create a gallery without a slug (short name)'); } | |
| this.slug = slug; | |
| this.title = title || this.slug.split(config.slugSeparator).join(' '); | |
| this.description = description || ''; | |
| this.featuredItem = ''; | |
| this.about = ''; | |
| this.items = []; | |
| // Non-enumerable properties: | |
| Object.defineProperties(this, { | |
| 'loadedAt': { value: 0, writable: true }, | |
| 'loadedFrom': { value: '', writable: true } | |
| }); | |
| } | |
| Gallery.prototype.reload = function(callback) { | |
| var self = this; | |
| if (!self.loadedFrom) { | |
| log.warn('No path has been set to load the gallery from; ignoring reload request...'); | |
| return callback(null, self); | |
| } | |
| fs.stat(self.loadedFrom, function(err, galleryStat) { | |
| if (err) { return callback(err, self); } | |
| if (self.loadedAt && self.loadedAt >= galleryStat.ctime.getTime()) { | |
| log.debug('Gallery', self.slug, 'already loaded (latest version)'); | |
| return callback(null, self); | |
| } | |
| fs.readdir(self.loadedFrom, function(err, galleryItems) { | |
| var i, scanCount = 0, itemCount = galleryItems.length, galleryPath = self.loadedFrom; | |
| if (err) { return callback(err, self); } // cannot load a gallery if directory reading fails | |
| self.items.length = 0; | |
| for (i = 0; i < itemCount; i++) { | |
| loadItem(path.join(galleryPath, galleryItems[i]), self, onItemLoaded); | |
| } | |
| function onItemLoaded(err, itemPath) { | |
| if (err) { | |
| log.warn(err, 'Cannot load item at', itemPath, 'in gallery', self.slug + '; ignoring...'); | |
| } | |
| if (++scanCount === itemCount) { // all possible items have been loaded | |
| self.items.sort(); | |
| self.loadedAt = Date.now(); | |
| callback(null, self); | |
| } | |
| } | |
| }); | |
| }); | |
| }; | |
| /** | |
| * This function may choose to <em>ignore</em> (not load) the item at the specified path for various reasons: not a file | |
| * or not an index or about or supported image type, etc. It should, however log at least at DEBUG level such decisions. | |
| * In this case the provided callback gets called normally, with <code>null</code> as first argument (i.e. no error). As | |
| * such, there's no quick way the callback can tell whether the item has indeed been loaded or not (although one could | |
| * probably check the contents of the gallery to see whether it contains the new item). Any actual error gets passed to | |
| * the callback as the first argument. | |
| */ | |
| function loadItem(itemPath, gallery, onItemLoaded) { | |
| log.debug('Scanning gallery item at', itemPath); | |
| fs.stat(itemPath, function(err, itemStat) { | |
| var basename; | |
| if (err) { return onItemLoaded(err, itemPath, gallery); } | |
| if (!itemStat.isFile()) { | |
| log.debug('Ignoring item', itemPath, 'in gallery', gallery.slug, '(not a file)'); | |
| return onItemLoaded(null, itemPath, gallery); | |
| } | |
| basename = path.basename(itemPath); | |
| if (basename.substr(itemPath.length - 10).toLowerCase() === 'index.json') { | |
| fs.readFile(itemPath, {encoding: 'utf-8'}, parseIndex); | |
| } else if (basename.substr(itemPath.length - 8).toLowerCase() === 'about.md') { | |
| fs.readFile(itemPath, {encoding: 'utf-8'}, parseAbout); | |
| } else { | |
| if (/\.(jpe?g|a?png|gif|svg)$/i.test(path.extname(basename))) { | |
| if (basename.substr(0, 8).toLowerCase() === 'featured') { | |
| gallery.featuredItem = itemPath.replace(/\\+/g, '/'); | |
| } else { | |
| gallery.items.push(itemPath.replace(/\\+/g, '/')); | |
| } | |
| } else { | |
| log.debug('Ignoring item', itemPath, 'in gallery', gallery.slug, '(not a supported image type)'); | |
| } | |
| onItemLoaded(null, itemPath, gallery); | |
| } | |
| function parseIndex(err, text) { | |
| var index; | |
| if (err) { | |
| log.warn(err, 'Cannot read the index file in gallery', gallery.slug + '; ignoring index...'); | |
| } else { | |
| try { | |
| index = JSON.parse(text); | |
| gallery.title = index.title || gallery.title; | |
| gallery.description = index.description || gallery.description; | |
| gallery.featuredItem = index.featuredItem || gallery.featuredItem; | |
| } catch (jsonParseError) { | |
| log.warn(jsonParseError, 'Cannot parse the index file in gallery', gallery.slug + '; ignoring index...'); | |
| } | |
| } | |
| onItemLoaded(null, itemPath, gallery); | |
| } | |
| function parseAbout(err, text) { | |
| if (err) { | |
| log.warn(err, 'Cannot read the about file in gallery', gallery.slug + '; ignoring about...'); | |
| } else { | |
| gallery.about = text; | |
| } | |
| onItemLoaded(null, itemPath, gallery); | |
| } | |
| }); | |
| } | |
| Gallery.create = function(galleryPath, slug, callback) { | |
| var gallery; | |
| if (!slug) { slug = x.slug; } | |
| try { | |
| gallery = new Gallery(typeof slug === 'function' || | |
| slug instanceof Function ? slug(path.basename(galleryPath)) : slug); | |
| gallery.loadedFrom = galleryPath; | |
| gallery.reload(callback); | |
| } catch (err) { | |
| return callback(err); // cannot load a gallery if the Gallery instance creation fails | |
| } | |
| }; | |
| Gallery.alphaCompare = function(galleryLeft, galleryRight) { | |
| if (!galleryLeft) { return -1; } | |
| if (!galleryRight) { return 1; } | |
| return galleryLeft.slug.localeCompare(galleryRight.slug); | |
| }; | |
| module.exports = Gallery; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment