<commit_before>var mongoose = require('mongoose');
var FeedParser = require('feedparser');
var request = require('request');
var Stream = require('stream');
var chardet = require('chardet');
var iconv = require('iconv-lite');

var feedSchema = mongoose.Schema({
    _id:              { type: String, required: true, unique: true },
    title:            String,
    description:      String,
    link:             String,
    xmlUrl:           String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    language:         String,
    image:            { title: String, url: String },
    favicon:          String,
    copyright:        String,
    generator:        String,
    categories:       [String],
    lastModified:     String
});

var Feed = mongoose.model('Feed', feedSchema);

var articleSchema = mongoose.Schema({
    title:            String,
    description:      String,
    link:             String,
    origLink:         String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    guid:             String,
    comments:         String,
    image:            { title: String, url: String },
    categories:       [String],
    source:           { title: String, url: String },
    enclosures:       [{ url: String, type: String, length: String }],
    feed:             { type: String, ref: 'Feed' },
    savedAt:          { type: Date }
});

var Article = mongoose.model('Article', articleSchema);

var timeoutInMs = 10000;
var defaultMongoDbPort = 27017;
var updateIntervalHandle = null;

function connect(options) {
    options.port = options.port || defaultMongoDbPort;
    options.callback = options.callback || function() {};

    mongoose.connect(options.uri, options.database, options.port);

    mongoose.connection.on('error', function(e){
        logError("Could not connect to MongoDB: "+e.message);
        process.exit(1);
    });

    mongoose.connection.once('open', function callback () {
        logDebug("Connection to MongoDB was successful.");
        options.callback();
    });
}

function addFeed(url) {
    Feed.count({ _id: url }, function(error, count) {
        if(error != null) {
            logError('Failed to check if feed exists in MongoDB: '+error);
        }
        else {
            if(count == 0) {
                requestAndParseFeed(url);
            }
            else if(count == 1) {
                logWarning('Feed '+url+' already exists in the MongoDB. '
                + 'Skipping adding new feed to the storage.');
            }
            else {
                logError('Multiple documents with same url exist in '
                + 'MongoDB. This should not happen!');
            }
        }
    });
}

function removeFeed(url) {
    Feed.findByIdAndRemove(url, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to find feed to be removed: '+error);
        }
        else if(feedDocument != null) {
            setTimeout(function() { removeArticlesByFeedUrl(url) }, 5000);
        }
        else {
            logWarn('Feed not found with url '+url+'. Skipping removing.');
        }
    });
}

function removeArticlesByFeedUrl(url) {
    Article.remove({ feed: url }, function (error) {
        if(error != null) {
            logError('Failed to find articles to be removed: '+error);
        }
    });
}

function removeArticlesOlderThan(days) {
    var oldArticleDate = new Date();
    oldArticleDate.setDate(oldArticleDate.getDate()-days);

    Article.remove({ date: { $lt: oldArticleDate } }, function (error) {
        if(error != null) {
            logError('Failed to remove articles older than '+days+': '+error);
        }
    });
}

function requestAndParseFeed(url) {
    var requestObject = createDefaultRequest(url);

    Feed.findOne({ _id: url }, 'lastModified', function(error,
        partialFeedDocument) {
        if(error != null) {
            logError('Failed to get feeds Last-Modified from MongoDB: '+
                error);
        }
        else if(partialFeedDocument != null &&
            partialFeedDocument.lastModified != null) {
            requestObject.headers = { 'If-Modified-Since':
                partialFeedDocument.lastModified }
        }

        request(requestObject, function(error, response, body) {
            handleResponse(url, error, response, body)
        });
    });
}

function createDefaultRequest(url) {
    return { url: url, timeout: timeoutInMs, encoding: null };
}

function handleResponse(url, error, response, body) {
    if(error != null) {
        logError('Failed to handle HTTP response: '+error);
    }
    else if (response.statusCode == 200) {
        var lastModified = getLastModifiedFromResponseHeaders(response.headers);
        body = getBufferAsUtf8Buffer(body);

        //Feedparser no longer supports string, so convert body to stream again.
        //This is a little dirty, but needed because before parsing the body
        //it is a good idea to check http status code and Last-Modified
        var bodyAsStream = Stream.PassThrough();
        bodyAsStream.write(body);
        bodyAsStream.end();

        bodyAsStream.pipe(new FeedParser())
        .on('error', function (error) {
            handleParseError(error, url);
        })
        .on('meta', function (meta) {
            saveOrUpdateFeedMeta(meta, url, lastModified);
        })
        .on('readable', function() {
            var stream = this;
            var article;

            while (article = stream.read()) {
                saveOrUpdateArticle(article, url);
            }

        });
    }
    else if (response.statusCode == 304) {

    }
    else {
        logWarning('HTTP status code received that can not be handled by '
            + 'this module: '+response.statusCode);
    }
}

function getBufferAsUtf8Buffer(buffer) {
    var encoding = chardet.detect(buffer);

    if(encoding != 'UTF-8') {
        decodedString = iconv.decode(buffer, encoding);
        buffer = iconv.encode(decodedString, 'utf8');
    }

    return buffer;
}

function handleParseError(error, url) {
    logError('Could not parse feed at '+url+'. '+error);
}

function getLastModifiedFromResponseHeaders(headers) {
    var lastModified = null;

    for(var headerName in headers){
        if(headerName.toLowerCase() == 'last-modified') {
            lastModified = headers[headerName];
        }
    }

    return lastModified;
}

function saveOrUpdateFeedMeta(meta, url, lastModified) {
    Feed.findOne({ _id: url }, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to get feed from MongoDB: '+error);
        }
        else {
            if(feedDocument == null) {
                saveFeedMeta(meta, url, lastModified);
            }
            else {
                updateFeedMeta(feedDocument, meta, url, lastModified);
            }
        }
    });
}

function saveFeedMeta(meta, url, lastModified) {
    var feedDocument = createFeedDocument(meta, url, lastModified);

    feedDocument.save(function (error, feedDocument) {
        if(error != null) {
            logError('Failed to save feed meta to MongoDB: '+error);
        }
        else {
            logDebug('Added new feed "'+feedDocument._id+'" to MongoDB.');
        }
    });
}

function updateFeedMeta(feedDocument, meta, lastModified) {
    if(feedRequiresUpdate(feedDocument, meta, lastModified)) {
        feedDocument.title = meta.title;
        feedDocument.description = meta.description;
        feedDocument.link = meta.link;
        feedDocument.xmlUrl = meta.xmlUrl;
        feedDocument.date = meta.date;
        feedDocument.pubDate = meta.pubDate;
        feedDocument.author = meta.author;
        feedDocument.language = meta.language;
        feedDocument.image = meta.image;
        feedDocument.favicon = meta.favicon;
        feedDocument.copyright = meta.copyright;
        feedDocument.generator = meta.generator;
        feedDocument.categories = meta.categories;
        feedDocument.lastModified = lastModified;

        feedDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update feed meta to MongoDB: '+error);
            }
            else {
                logDebug('Updated feed "'+feedDocument._id+'" in MongoDB.');
            }
        });
    }
}

function feedRequiresUpdate(feedDocument, meta, lastModified) {
    var requiresUpdate = false;

    if(feedDocument.title != meta.title &&
        feedDocument.description != meta.description &&
        feedDocument.link != meta.link &&
        feedDocument.xmlUrl != meta.xmlUrl &&
        feedDocument.author != meta.author &&
        feedDocument.language != meta.language &&
        feedDocument.image.title != meta.image.title &&
        feedDocument.image.url != meta.image.url &&
        feedDocument.favicon != meta.favicon &&
        feedDocument.copyright != meta.copyright &&
        feedDocument.generator != meta.generator &&
        JSON.stringify(feedDocument.categories) !=
            JSON.stringify(meta.categories) &&
        feedDocument.lastModified != lastModified) {
        requiresUpdate = true;
    }

    return requiresUpdate;
}

function createFeedDocument(meta, url, lastModified) {
    return new Feed({ _id: url, title: meta.title,
        description: meta.description, link: meta.link, xmlUrl: meta.xmlUrl,
        date: meta.date, pubDate: meta.pubDate, author: meta.author,
        language: meta.language, image: meta.image, favicon: meta.favicon,
        copyright: meta.copyright, generator: meta.generator,
        categories: meta.categories, lastModified: lastModified });
}

function saveOrUpdateArticle(article, url) {
    Article.findOne({ guid: article.guid, feed: url }, function(error,
        articleDocument) {
        if(error != null) {
            logError('Failed to get article from MongoDB: '+error);
        }
        else {
            if(articleDocument == null) {
                saveArticle(article, url);
            }
            else {
                updateArticle(articleDocument, article);
            }
        }
    });
}

function saveArticle(article, url) {
    var articleDocument = createArticleDocument(article, url);

    articleDocument.save(function (error, articleDocument) {
        if(error != null) {
            logError('Failed to save article to MongoDB: '+error);
        }
        else {
            logDebug('Added new article "'+articleDocument.guid
            + '" to MongoDB.');
        }
    });
}

function updateArticle(articleDocument, article) {
    if(articleRequiresUpdate(articleDocument, article)) {
        articleDocument.title = article.title;
        articleDocument.description = article.description;
        articleDocument.link = article.link;
        articleDocument.xmlUrl = article.xmlUrl;
        articleDocument.date = article.date;
        articleDocument.pubDate = article.pubDate;
        articleDocument.author = article.author;
        articleDocument.language = article.language;
        articleDocument.image = article.image;
        articleDocument.favicon = article.favicon;
        articleDocument.copyright = article.copyright;
        articleDocument.generator = article.generator;
        articleDocument.categories = article.categories;

        articleDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update article to MongoDB: '+error);
            }
            else {
                logDebug('Updated article "'+articleDocument.guid
                +'" in MongoDB.');
            }
        });
    }
}

function articleRequiresUpdate(articleDocument, article) {
    var requiresUpdate = false;

    if(articleDocument.title != article.title &&
        articleDocument.description != article.description &&
        articleDocument.link != article.link &&
        articleDocument.origLink != article.origLink &&
        articleDocument.author != article.author &&
        articleDocument.guid != article.guid &&
        articleDocument.comments != article.comments &&
        articleDocument.image.title != article.image.title &&
        articleDocument.image.url != article.image.url &&
        JSON.stringify(articleDocument.categories) !=
            JSON.stringify(article.categories) &&
        articleDocument.source.title != article.source.title &&
        articleDocument.source.url != article.source.url &&
        articleDocument.enclosures.url != article.enclosures.url &&
        articleDocument.enclosures.type != article.enclosures.type &&
        articleDocument.enclosures.length != article.enclosures.length) {
        requiresUpdate = true;
    }

    return requiresUpdate;

}

function createArticleDocument(article, url) {
    return new Article({ title: article.title,
        description: article.description, link: article.link,
        origLink: article.origLink, date: article.date,
        pubDate: article.pubDate, author: article.author, guid: article.guid,
        comments: article.comments, image: article.image,
        categories: article.categories, source: article.source,
        enclosures: article.enclosures, feed: url, savedAt: new Date() });
}

function updateDatabase() {
    Feed.find(function(error, feedDocuments) {
        if(error != null) {
            logError('Failed to get feeds from MongoDB: '+error);
        }
        else {
            feedDocuments.forEach(function(feedDocument) {
                requestAndParseFeed(feedDocument._id);
            });
        }
    });
}

function updateDatabaseAtInterval(milliseconds) {
    if(updateIntervalHandle == null) {
        updateIntervalHandle = setInterval(updateDatabase, milliseconds);
    }
    else {
        logWarning('Database update interval already set. Skipping.');
    }
}

function stopUpdateDataBaseAtInterval() {
    if(updateIntervalHandle != null) {
        clearInterval(updateIntervalHandle);
        updateIntervalHandle = null;
    }
}

function getArticlesByKeyword(keyword, options, callback) {
    if(typeof callback == 'undefined' && typeof options == 'function') {
        callback = options;
        options = {};
    }

    var query = createQueryForArticles(keyword, options);
    executeArticleQuery(query, callback);
}

function getArticlesByKeywordArray(keywords, options, callback) {
    var keyword = '('+keywords.join('|')+')';
    getArticlesByKeyword(keyword, options, callback);
}

function createQueryForArticles(keyword, options) {
    var allowedCharsBeforeAndAfter = '\\s|\\.|\,|\!|\\?|:|-|_|\'|"|@|#|\$|'
        +'%|&|\\(|\\)|\\[|\\]|{|}|\\+';
    var searchTerm = new RegExp('.*(^|'+allowedCharsBeforeAndAfter+')+'
        +keyword+'('+allowedCharsBeforeAndAfter+'|$)+.*', 'i');
    var criteria = { $or: [ { title: searchTerm }, { description:
            searchTerm }, { author: searchTerm } ] };

    if(typeof options.from != 'undefined') {
        criteria.savedAt = criteria.savedAt || {};
        criteria.savedAt.$gte = options.from;
    }

    if(typeof options.to != 'undefined') {
        criteria.savedAt = criteria.savedAt || {};
        criteria.savedAt.$lte = options.to;
    }

    var query = Article.find(criteria);

    if(typeof options.limit != 'undefined') {
        query.limit(options.limit);
    }

    query.sort({savedAt: 'desc'});
    query.populate('feed', 'title');

    return query;
}

function executeArticleQuery(query, callback) {
    query.exec(function (error, articleDocuments) {
        if(error != null) {
            logError('Failed to get articles from MongoDB: '+error);
        }
        else {
            callback(articleDocuments);
        }
    });
}

function logDebug(message) {
    console.log(getTimeStampForLog()+' [DEBUG] '+message);
}

function logWarning(message) {
    console.warn(getTimeStampForLog()+' [WARN] '+message);
}

function logError(message) {
    console.error(getTimeStampForLog()+' [ERROR] '+message);
}

function getTimeStampForLog() {
    return new Date().toUTCString();
}

exports.connect = connect;
exports.addFeed = addFeed;
exports.updateDatabase = updateDatabase;
exports.updateDatabaseAtInterval = updateDatabaseAtInterval;
exports.stopUpdateDataBaseAtInterval = stopUpdateDataBaseAtInterval;
exports.getArticlesByKeyword = getArticlesByKeyword;
exports.getArticlesByKeywordArray = getArticlesByKeywordArray;
exports.removeFeed = removeFeed;
exports.removeArticlesOlderThan = removeArticlesOlderThan;
<commit_msg>Implemented callback possibility to public API.<commit_after>var mongoose = require('mongoose');
var FeedParser = require('feedparser');
var request = require('request');
var Stream = require('stream');
var chardet = require('chardet');
var iconv = require('iconv-lite');

var feedSchema = mongoose.Schema({
    _id:              { type: String, required: true, unique: true },
    title:            String,
    description:      String,
    link:             String,
    xmlUrl:           String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    language:         String,
    image:            { title: String, url: String },
    favicon:          String,
    copyright:        String,
    generator:        String,
    categories:       [String],
    lastModified:     String
});

var Feed = mongoose.model('Feed', feedSchema);

var articleSchema = mongoose.Schema({
    title:            String,
    description:      String,
    link:             String,
    origLink:         String,
    date:             { type: Date },
    pubDate:          { type: Date },
    author:           String,
    guid:             String,
    comments:         String,
    image:            { title: String, url: String },
    categories:       [String],
    source:           { title: String, url: String },
    enclosures:       [{ url: String, type: String, length: String }],
    feed:             { type: String, ref: 'Feed' },
    savedAt:          { type: Date }
});

var Article = mongoose.model('Article', articleSchema);

var timeoutInMs = 10000;
var defaultMongoDbPort = 27017;
var updateIntervalHandle = null;

function connect(options) {
    options.port = options.port || defaultMongoDbPort;
    options.callback = options.callback || function() {};

    mongoose.connect(options.uri, options.database, options.port);

    mongoose.connection.on('error', function(e){
        logError("Could not connect to MongoDB: "+e.message);
        process.exit(1);
    });

    mongoose.connection.once('open', function callback () {
        logDebug("Connection to MongoDB was successful.");
        options.callback();
    });
}

function addFeed(url, callback) {
    Feed.count({ _id: url }, function(error, count) {
        if(error != null) {
            logError('Failed to check if feed exists in MongoDB: '+error);
        }
        else {
            if(count == 0) {
                requestAndParseFeed(url, callback);
            }
            else if(count == 1) {
                logWarning('Feed '+url+' already exists in the MongoDB. '
                + 'Skipping adding new feed to the storage.');
            }
            else {
                logError('Multiple documents with same url exist in '
                + 'MongoDB. This should not happen!');
            }
        }
    });
}

function removeFeed(url, callback) {
    Feed.findByIdAndRemove(url, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to find feed to be removed: '+error);
        }
        else if(feedDocument != null) {
            removeArticlesByFeedUrl(url, callback);
        }
        else {
            logWarn('Feed not found with url '+url+'. Skipping removing.');
        }
    });
}

function removeArticlesByFeedUrl(url, callback) {
    Article.remove({ feed: url }, function (error) {
        if(error != null) {
            logError('Failed to find articles to be removed: '+error);
        }
        callback();
    });
}

function removeArticlesOlderThan(days) {
    var oldArticleDate = new Date();
    oldArticleDate.setDate(oldArticleDate.getDate()-days);

    Article.remove({ date: { $lt: oldArticleDate } }, function (error) {
        if(error != null) {
            logError('Failed to remove articles older than '+days+': '+error);
        }
    });
}

function requestAndParseFeed(url, callback) {
    var requestObject = createDefaultRequest(url);

    Feed.findOne({ _id: url }, 'lastModified', function(error,
        partialFeedDocument) {
        if(error != null) {
            logError('Failed to get feeds Last-Modified from MongoDB: '+
                error);
        }
        else if(partialFeedDocument != null &&
            partialFeedDocument.lastModified != null) {
            requestObject.headers = { 'If-Modified-Since':
                partialFeedDocument.lastModified }
        }

        request(requestObject, function(error, response, body) {
            handleResponse(url, error, response, body, callback);
        });
    });
}

function createDefaultRequest(url) {
    return { url: url, timeout: timeoutInMs, encoding: null };
}

function handleResponse(url, error, response, body, callback) {
    if(error != null) {
        logError('Failed to handle HTTP response: '+error);
    }
    else if (response.statusCode == 200) {
        var lastModified = getLastModifiedFromResponseHeaders(response.headers);
        body = getBufferAsUtf8Buffer(body);

        //Feedparser no longer supports string, so convert body to stream again.
        //This is a little dirty, but needed because before parsing the body
        //it is a good idea to check http status code and Last-Modified
        var bodyAsStream = Stream.PassThrough();
        bodyAsStream.write(body);
        bodyAsStream.end();

        bodyAsStream.pipe(new FeedParser())
        .on('error', function (error) {
            handleParseError(error, url);
        })
        .on('meta', function (meta) {
            saveOrUpdateFeedMeta(meta, url, lastModified);
        })
        .on('readable', function() {
            var stream = this;
            var article;

            while (article = stream.read()) {
                saveOrUpdateArticle(article, url);
            }

        })
        .on('end', function() {
            callback();
        });
    }
    else if (response.statusCode == 304) {

    }
    else {
        logWarning('HTTP status code received that can not be handled by '
            + 'this module: '+response.statusCode);
    }
}

function getBufferAsUtf8Buffer(buffer) {
    var encoding = chardet.detect(buffer);

    if(encoding != 'UTF-8') {
        decodedString = iconv.decode(buffer, encoding);
        buffer = iconv.encode(decodedString, 'utf8');
    }

    return buffer;
}

function handleParseError(error, url) {
    logError('Could not parse feed at '+url+'. '+error);
}

function getLastModifiedFromResponseHeaders(headers) {
    var lastModified = null;

    for(var headerName in headers){
        if(headerName.toLowerCase() == 'last-modified') {
            lastModified = headers[headerName];
        }
    }

    return lastModified;
}

function saveOrUpdateFeedMeta(meta, url, lastModified) {
    Feed.findOne({ _id: url }, function(error, feedDocument) {
        if(error != null) {
            logError('Failed to get feed from MongoDB: '+error);
        }
        else {
            if(feedDocument == null) {
                saveFeedMeta(meta, url, lastModified);
            }
            else {
                updateFeedMeta(feedDocument, meta, url, lastModified);
            }
        }
    });
}

function saveFeedMeta(meta, url, lastModified) {
    var feedDocument = createFeedDocument(meta, url, lastModified);

    feedDocument.save(function (error, feedDocument) {
        if(error != null) {
            logError('Failed to save feed meta to MongoDB: '+error);
        }
        else {
            logDebug('Added new feed "'+feedDocument._id+'" to MongoDB.');
        }
    });
}

function updateFeedMeta(feedDocument, meta, lastModified) {
    if(feedRequiresUpdate(feedDocument, meta, lastModified)) {
        feedDocument.title = meta.title;
        feedDocument.description = meta.description;
        feedDocument.link = meta.link;
        feedDocument.xmlUrl = meta.xmlUrl;
        feedDocument.date = meta.date;
        feedDocument.pubDate = meta.pubDate;
        feedDocument.author = meta.author;
        feedDocument.language = meta.language;
        feedDocument.image = meta.image;
        feedDocument.favicon = meta.favicon;
        feedDocument.copyright = meta.copyright;
        feedDocument.generator = meta.generator;
        feedDocument.categories = meta.categories;
        feedDocument.lastModified = lastModified;

        feedDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update feed meta to MongoDB: '+error);
            }
            else {
                logDebug('Updated feed "'+feedDocument._id+'" in MongoDB.');
            }
        });
    }
}

function feedRequiresUpdate(feedDocument, meta, lastModified) {
    var requiresUpdate = false;

    if(feedDocument.title != meta.title &&
        feedDocument.description != meta.description &&
        feedDocument.link != meta.link &&
        feedDocument.xmlUrl != meta.xmlUrl &&
        feedDocument.author != meta.author &&
        feedDocument.language != meta.language &&
        feedDocument.image.title != meta.image.title &&
        feedDocument.image.url != meta.image.url &&
        feedDocument.favicon != meta.favicon &&
        feedDocument.copyright != meta.copyright &&
        feedDocument.generator != meta.generator &&
        JSON.stringify(feedDocument.categories) !=
            JSON.stringify(meta.categories) &&
        feedDocument.lastModified != lastModified) {
        requiresUpdate = true;
    }

    return requiresUpdate;
}

function createFeedDocument(meta, url, lastModified) {
    return new Feed({ _id: url, title: meta.title,
        description: meta.description, link: meta.link, xmlUrl: meta.xmlUrl,
        date: meta.date, pubDate: meta.pubDate, author: meta.author,
        language: meta.language, image: meta.image, favicon: meta.favicon,
        copyright: meta.copyright, generator: meta.generator,
        categories: meta.categories, lastModified: lastModified });
}

function saveOrUpdateArticle(article, url) {
    Article.findOne({ guid: article.guid, feed: url }, function(error,
        articleDocument) {
        if(error != null) {
            logError('Failed to get article from MongoDB: '+error);
        }
        else {
            if(articleDocument == null) {
                saveArticle(article, url);
            }
            else {
                updateArticle(articleDocument, article);
            }
        }
    });
}

function saveArticle(article, url) {
    var articleDocument = createArticleDocument(article, url);

    articleDocument.save(function (error, articleDocument) {
        if(error != null) {
            logError('Failed to save article to MongoDB: '+error);
        }
        else {
            logDebug('Added new article "'+articleDocument.guid
            + '" to MongoDB.');
        }
    });
}

function updateArticle(articleDocument, article) {
    if(articleRequiresUpdate(articleDocument, article)) {
        articleDocument.title = article.title;
        articleDocument.description = article.description;
        articleDocument.link = article.link;
        articleDocument.xmlUrl = article.xmlUrl;
        articleDocument.date = article.date;
        articleDocument.pubDate = article.pubDate;
        articleDocument.author = article.author;
        articleDocument.language = article.language;
        articleDocument.image = article.image;
        articleDocument.favicon = article.favicon;
        articleDocument.copyright = article.copyright;
        articleDocument.generator = article.generator;
        articleDocument.categories = article.categories;

        articleDocument.save(function (error, feedDocument) {
            if(error != null) {
                logError('Failed to update article to MongoDB: '+error);
            }
            else {
                logDebug('Updated article "'+articleDocument.guid
                +'" in MongoDB.');
            }
        });
    }
}

function articleRequiresUpdate(articleDocument, article) {
    var requiresUpdate = false;

    if(articleDocument.title != article.title &&
        articleDocument.description != article.description &&
        articleDocument.link != article.link &&
        articleDocument.origLink != article.origLink &&
        articleDocument.author != article.author &&
        articleDocument.guid != article.guid &&
        articleDocument.comments != article.comments &&
        articleDocument.image.title != article.image.title &&
        articleDocument.image.url != article.image.url &&
        JSON.stringify(articleDocument.categories) !=
            JSON.stringify(article.categories) &&
        articleDocument.source.title != article.source.title &&
        articleDocument.source.url != article.source.url &&
        articleDocument.enclosures.url != article.enclosures.url &&
        articleDocument.enclosures.type != article.enclosures.type &&
        articleDocument.enclosures.length != article.enclosures.length) {
        requiresUpdate = true;
    }

    return requiresUpdate;

}

function createArticleDocument(article, url) {
    return new Article({ title: article.title,
        description: article.description, link: article.link,
        origLink: article.origLink, date: article.date,
        pubDate: article.pubDate, author: article.author, guid: article.guid,
        comments: article.comments, image: article.image,
        categories: article.categories, source: article.source,
        enclosures: article.enclosures, feed: url, savedAt: new Date() });
}

function updateDatabase() {
    Feed.find(function(error, feedDocuments) {
        if(error != null) {
            logError('Failed to get feeds from MongoDB: '+error);
        }
        else {
            feedDocuments.forEach(function(feedDocument) {
                requestAndParseFeed(feedDocument._id);
            });
        }
    });
}

function updateDatabaseAtInterval(milliseconds) {
    if(updateIntervalHandle == null) {
        updateIntervalHandle = setInterval(updateDatabase, milliseconds);
    }
    else {
        logWarning('Database update interval already set. Skipping.');
    }
}

function stopUpdateDataBaseAtInterval() {
    if(updateIntervalHandle != null) {
        clearInterval(updateIntervalHandle);
        updateIntervalHandle = null;
    }
}

function getArticlesByKeyword(keyword, options, callback) {
    if(typeof callback == 'undefined' && typeof options == 'function') {
        callback = options;
        options = {};
    }

    var query = createQueryForArticles(keyword, options);
    executeArticleQuery(query, callback);
}

function getArticlesByKeywordArray(keywords, options, callback) {
    var keyword = '('+keywords.join('|')+')';
    getArticlesByKeyword(keyword, options, callback);
}

function createQueryForArticles(keyword, options) {
    var allowedCharsBeforeAndAfter = '\\s|\\.|\,|\!|\\?|:|-|_|\'|"|@|#|\$|'
        +'%|&|\\(|\\)|\\[|\\]|{|}|\\+';
    var searchTerm = new RegExp('.*(^|'+allowedCharsBeforeAndAfter+')+'
        +keyword+'('+allowedCharsBeforeAndAfter+'|$)+.*', 'i');
    var criteria = { $or: [ { title: searchTerm }, { description:
            searchTerm }, { author: searchTerm } ] };

    if(typeof options.from != 'undefined') {
        criteria.savedAt = criteria.savedAt || {};
        criteria.savedAt.$gte = options.from;
    }

    if(typeof options.to != 'undefined') {
        criteria.savedAt = criteria.savedAt || {};
        criteria.savedAt.$lte = options.to;
    }

    var query = Article.find(criteria);

    if(typeof options.limit != 'undefined') {
        query.limit(options.limit);
    }

    query.sort({savedAt: 'desc'});
    query.populate('feed', 'title');

    return query;
}

function executeArticleQuery(query, callback) {
    query.exec(function (error, articleDocuments) {
        if(error != null) {
            logError('Failed to get articles from MongoDB: '+error);
        }
        else {
            callback(articleDocuments);
        }
    });
}

function logDebug(message) {
    console.log(getTimeStampForLog()+' [DEBUG] '+message);
}

function logWarning(message) {
    console.warn(getTimeStampForLog()+' [WARN] '+message);
}

function logError(message) {
    console.error(getTimeStampForLog()+' [ERROR] '+message);
}

function getTimeStampForLog() {
    return new Date().toUTCString();
}

exports.connect = connect;
exports.addFeed = addFeed;
exports.updateDatabase = updateDatabase;
exports.updateDatabaseAtInterval = updateDatabaseAtInterval;
exports.stopUpdateDataBaseAtInterval = stopUpdateDataBaseAtInterval;
exports.getArticlesByKeyword = getArticlesByKeyword;
exports.getArticlesByKeywordArray = getArticlesByKeywordArray;
exports.removeFeed = removeFeed;
exports.removeArticlesOlderThan = removeArticlesOlderThan;
<|endoftext|>


<commit_before>/**
 *
 * Created by shouhewu on 6/8/17.
 *
 */
var bcservice=require('./service/bcservice.js')

var express = require("express");
var path = require('path');
var app = express();

//app.use(express.static('source'));
//app.use('/source', express.static('source'));
app.use('/source', express.static('public'));

//app.use(express.static(path.join(__dirname, '/source')))

var query=require('./app/query.js');

//指定模板引擎
app.set("view engine", 'ejs');
//指定模板位置
app.set('views', __dirname + '/views');

// =======================   控制器绑定  ===================

/**
 *  /channels
 *  /channels/:channelName
 *  /channels/:channelName/block/:blockNum
 *  /channels/:channelName/block/:blockHash
 *  /channels/:channelName/block/:blockNum/:txHash
 *  /channels/:channelName/chaincodes
 *
 *  /orgs
 *  /orgs/peers
 *
 *
 */

//首页
app.get("/", function(req, res) {
    res.render('index.ejs', {
        name: 'tinyphp',item_index_active:'1'
    });
});

//首页
app.get("/index", function(req, res) {

    // var orgs = orgconfig.getAllOrgs();
    //
    // test();
    // console.log(util.inspect(orgs));

    //console.log(util.inspect(bcserver.getAllPeerRequest()));

    var peers = bcservice.getAllPeerRequest();

    peers.forEach((item,index)=>{

        console.log(` ${index}  ==> ${item.name}  ==> ${item.org}`);

        query.getChannels(item.name,'admin',item.org).then(response=>{

            console.log(response)

            console.log("   =======  ")

        }).catch(err=>{
            console.log(err)
        })

    })

    res.render('index.ejs', {
        name: 'tinyphp',item_index_active:'1'
    });

});

//组织列表
app.get("/orgs", function(req, res) {
    var orgs=bcservice.getAllOrgs()
    res.json(orgs)
});

app.get("/orgs/peers", function(req, res) {
    res.render('orgs.ejs', {
        name: 'tinyphp',item_index_orgs:'1'
    });
});

//channel列表
app.get("/channels", function(req, res) {
    bcservice.getAllChannels().then(channels=>{
        res.send(channels)
    }).catch(err=>{
        res.send(err)
    })
});

//channel详情
app.get("/channels/:channelName", function(req, res) {
    var channelName=req.params.channelName
    bcservice.getChainInfo(channelName).then(chainInfo=>{
        res.send(chaiinInfo)
    }).catch(err=>{
        res.send(err)
    })
});

//区块列表
app.get("/channels/:channelName/block/:blockNum", function(req, res) {
    var channelName=req.params.channelName
    var blockNum=req.params.blockNum
    bcservice.getBlock4Channel(channelName,blockNum).then(block=>{
        res.send(block)
    }).catch(err=>{
        res.send(err)
    })
});

//区块详情
/*app.get("/channels/:channelName/block/:blockHash", function(req, res) {
 res.render('peer_detail.ejs', {
 name: 'tinyphp',item_index_peers:'1'
 });
 });*/

//交易详情
app.get("/channels/:channelName/:txHash", function(req, res) {
    var channelName=req.params.channelName
    var txHash=req.params.txHash
    bcservice.getTans4Chain(channelName,txHash).then(tx=>{
        res.send(tx)
    }).catch(err=>{
        res.send(err)
    })
});

//
app.get("/channels/:channelName/chaincodes", function(req, res) {
    var channelName=req.params.channelName
    bcservice.getChainCode4Channel(channelName).then(chaincode=>{
        res.send(chaincode)
    }).catch(err=>{
        res.send(err)
    })
});

// ============= 启动服务器 =======================

var server = app.listen(8080, function() {
    console.log("请在浏览器访问：http://localhost:8080/");
    //console.log(path.join(__dirname, 'source'));
});<commit_msg>modify1<commit_after>/**
 *
 * Created by shouhewu on 6/8/17.
 *
 */
var bcservice=require('./service/bcservice.js')

var express = require("express");
var path = require('path');
var app = express();

//app.use(express.static('source'));
//app.use('/source', express.static('source'));
app.use('/source', express.static('public'));

//app.use(express.static(path.join(__dirname, '/source')))

var query=require('./app/query.js');

//指定模板引擎
app.set("view engine", 'ejs');
//指定模板位置
app.set('views', __dirname + '/views');

// =======================   控制器绑定  ===================

/**
 *  /channels
 *  /channels/:channelName
 *  /channels/:channelName/block/:blockNum
 *  /channels/:channelName/block/:blockHash
 *  /channels/:channelName/block/:blockNum/:txHash
 *  /channels/:channelName/chaincodes
 *
 *  /orgs
 *  /orgs/peers
 *
 *
 */

//首页
app.get("/", function(req, res) {
    res.render('index.ejs', {
        name: 'tinyphp',item_index_active:'1'
    });
});

//首页
app.get("/index", function(req, res) {

    // var orgs = orgconfig.getAllOrgs();
    //
    // test();
    // console.log(util.inspect(orgs));

    //console.log(util.inspect(bcserver.getAllPeerRequest()));

    var peers = bcservice.getAllPeerRequest();

    /*peers.forEach((item,index)=>{

        console.log(` ${index}  ==> ${item.name}  ==> ${item.org}`);

        query.getChannels(item.name,'admin',item.org).then(response=>{

            console.log(response)
            console.log("   =======  ")

        }).catch(err=>{
            console.log(err)
        })

    })*/

    query.getChainInfo('peer1','mychannel','admin','org1').then(response_payloads=>{
        console.info(response_payloads.height)
    }).catch(err=>{
        console.info(err)
    })

    res.render('index.ejs', {
        name: 'tinyphp',item_index_active:'1'
    });

});

//组织列表
app.get("/orgs", function(req, res) {
    var orgs=bcservice.getAllOrgs()
    //res.json(orgs)

    res.render('orgs.ejs', {
        name: 'tinyphp',item_index_orgs:'1'
    });
});

//组织列表
app.get("/peers", function(req, res) {
    var orgs=bcservice.getAllOrgs()
    //res.json(orgs)

    res.render('peers.ejs', {
        name: 'tinyphp',item_index_peers:'1'
    });
});

//节点详情
app.get("/peer_detail", function(req, res) {
    res.render('peer_detail.ejs', {
        name: 'tinyphp',item_index_peers:'1'
    });
});

//区块详情
app.get("/block_detail", function(req, res) {
    res.render('block_detail.ejs', {
        name: 'tinyphp',item_index_peers:'1'
    });
});

//交易详情
app.get("/trans_detail", function(req, res) {
    res.render('trans_detail.ejs', {
        name: 'tinyphp',item_index_peers:'1'
    });
});

//channel列表
app.get("/channels", function(req, res) {

    res.render('channels.ejs', {
        name: 'tinyphp',item_index_channels:'1'
    });

    /*bcservice.getAllChannels().then(channels=>{
        res.send(channels)
    }).catch(err=>{
        res.send(err)
    })*/
});

//账本详情
app.get("/channel_detail", function(req, res) {
    res.render('channel_detail.ejs', {
        name: 'tinyphp',item_index_channels:'1'
    });
});

app.get("/orgs/peers", function(req, res) {
    res.render('orgs.ejs', {
        name: 'tinyphp',item_index_orgs:'1'
    });
});

//channel详情
app.get("/channels/:channelName", function(req, res) {
    var channelName=req.params.channelName
    bcservice.getChainInfo(channelName).then(chainInfo=>{
        res.send(chaiinInfo)
    }).catch(err=>{
        res.send(err)
    })
});

//区块列表
app.get("/channels/:channelName/block/:blockNum", function(req, res) {
    var channelName=req.params.channelName
    var blockNum=req.params.blockNum
    bcservice.getBlock4Channel(channelName,blockNum).then(block=>{
        res.send(block)
    }).catch(err=>{
        res.send(err)
    })
});

//区块详情
/*app.get("/channels/:channelName/block/:blockHash", function(req, res) {
 res.render('peer_detail.ejs', {
 name: 'tinyphp',item_index_peers:'1'
 });
 });*/

//交易详情
app.get("/channels/:channelName/:txHash", function(req, res) {
    var channelName=req.params.channelName
    var txHash=req.params.txHash
    bcservice.getTans4Chain(channelName,txHash).then(tx=>{
        res.send(tx)
    }).catch(err=>{
        res.send(err)
    })
});

//
app.get("/channels/:channelName/chaincodes", function(req, res) {
    var channelName=req.params.channelName
    bcservice.getChainCode4Channel(channelName).then(chaincode=>{
        res.send(chaincode)
    }).catch(err=>{
        res.send(err)
    })
});

// ============= 启动服务器 =======================

var server = app.listen(8080, function() {
    console.log("请在浏览器访问：http://localhost:8080/");
    //console.log(path.join(__dirname, 'source'));
});<|endoftext|>


<commit_before>
var util         = require ('util');
var EventEmitter = require ('events').EventEmitter;
var os           = require ('os');
var async        = require ('async');
var MongoDB      = require ('mongodb');
var submergence  = require ('submergence');
var substation   = require ('substation');
var filth        = require ('filth');
var cachew       = require ('cachew');
var Gateway      = require ('./lib/Gateway');
var Action       = require ('./lib/Action');
var Exacter      = require ('./lib/Exacter');

var GetSession   = require ('./lib/ControlActions/GetSession');
var PostSession  = require ('./lib/ControlActions/PostSession');
var PostEvent    = require ('./lib/ControlActions/PostEvent');
var GetConfig    = require ('./lib/ControlActions/GetConfig');
var PutConfig    = require ('./lib/ControlActions/PutConfig');
var RootActions  = require ('./lib/ControlActions/root');
var AccountActions  = require ('./lib/ControlActions/account');
var SessionActions  = require ('./lib/ControlActions/session');
var DomainActions  = require ('./lib/ControlActions/domain');
var PermitActions  = require ('./lib/ControlActions/permit');
var ConfirmActions  = require ('./lib/ControlActions/confirm');

/**     @struct sublayer.Configuration
    @super submergence.Configuration

@member/String databaseName
    @default `"sublayer"`
@member/String applicationName
    @default `"sublayer"`
@member/Object actionDefaults
    Default configuration options used for all Actions.
*/
var DEFAULT_CONFIG = {
    databaseName:               "sublayer",
    applicationName:            "sublayer",
    domainCollectionName:       "Domain",
    userCollectionName:         "User",
    usernameCollectionName:     "Username",
    clientCollectionName:       "Client",
    confirmationCollectionName: "Confirm",
    notificationCollectionName: "Notice",
    invoicesCollectionName:     "Invoice",
    actionDefaults:             {},
    domainCacheLength:          2048,
    domainCacheTimout:          1000 * 60
};

/**     @module/class sublayer
    Realtime and peer to peer service layer.
@argument/.Configuration config
@returns/sublayer
    If the `new` keyword is not used, an instance is created and returned.
@event userOnline
@event clientOnline
@event userOffline
@event clientOffline
*/
function sublayer (config) {
    if (!(this instanceof sublayer))
        return new sublayer (config);

    this.config = filth.clone (submergence.DEFAULT_CONFIG);
    filth.merge (this.config, DEFAULT_CONFIG);
    filth.merge (this.config, config);

    this.server = new submergence (this.config);
    this.logger = this.server.logger;
    this.gateway = new Gateway (this, this.config, this.server);
    this.adminRouter = new substation.Router (this, this.config);
    this.exacter = new Exacter (this, this.config);
}
module.exports = sublayer;

sublayer.prototype.getDomain = function (domain, callback) {
    var cached;
    if (this.domainCache && ( cached = this.domainCache.get (domain) ))
        return callback (undefined, cached);

    var self = this;
    this.DomainCollection.findOne ({ domain:domain }, function (err, domainRecord) {
        if (err) {
            self.logger.error ({ domain:domain }, 'failed to look up domain');
            return callback (err);
        }
        if (!domainRecord)
            return callback();

        if (domainRecord.actions && domainRecord.actions.length) {
            for (var i=0,j=domainRecord.actions.length; i<j; i++)
                domainRecord.actions[i] = new Action (
                    domain,
                    domainRecord.apiHash,
                    domainRecord.actions[i]
                );
        }

        if (self.domainCache)
            self.domainCache.set (domain, domainRecord);

        callback (undefined, domainRecord);
    });
};

/**     @member/Function listen
@argument/Number port
@callback
    @argument/Error|undefined err
*/
sublayer.prototype.listen = function (port, adminPort, callback) {
    var self = this;

    if (this.config.domainCacheLength)
        this.domainCache = new cachew.ChainCache (
            this.config.domainCacheLength,
            this.config.domainCacheTimout
        );

    if (this.config.DomainCollection) {
        this.DomainCollection = this.config.DomainCollection;
        this.gateway.init (router, function(){
            self.server.listen (port, self.gateway, function(){
                self.server.listen (adminPort, self.adminRouter, callback);
            });
        });
        return;
    }

    this.adminRouter.addAction ('GET', 'user_session', GetSession);
    this.adminRouter.addAction ('POST', 'user_session', PostSession);
    this.adminRouter.addAction ('POST', 'event', PostEvent);
    this.adminRouter.addAction ('GET', 'config', GetConfig);
    this.adminRouter.addAction ('PUT', 'config', PutConfig);
    this.adminRouter.addAction ('GET', new RegExp ('/account/([\\w\\-_]+)$'), AccountActions.GET);
    this.adminRouter.addAction ('PUT', new RegExp ('/account/([\\w\\-_]+)$'), AccountActions.PUT);
    this.adminRouter.addAction ('POST', 'account', AccountActions.POST);
    this.adminRouter.addAction ('POST', 'session', SessionActions.POST);
    this.adminRouter.addAction ('DELETE', 'session', SessionActions.DELETE);
     // we can't DELETE from a form, so let's make an RPC-style handler as well
    this.adminRouter.addAction ('GET', 'logout', SessionActions.DELETE);
    this.adminRouter.addAction ('GET', new RegExp ('/domain/([\\w\\-_]+)$'), DomainActions.GET);
    this.adminRouter.addAction ('PUT', new RegExp ('/domain/([\\w\\-_]+)$'), DomainActions.PUT);
    this.adminRouter.addAction ('POST', 'domain', DomainActions.POST);
    this.adminRouter.addAction ('GET', new RegExp ('/permit/([\\w\\-_]+)$'), PermitActions.GET);
    this.adminRouter.addAction ('POST', new RegExp ('/permit/([\\w\\-_]+)$'), PermitActions.POST);
    this.adminRouter.addAction ('DELETE', new RegExp ('/permit/([\\w\\-_]+)/([\\w\\-_]+)$'), PermitActions.DELETE);
    this.adminRouter.addAction ('GET', new RegExp ('/confirm/([\\w\\-_]+)$'), ConfirmActions.GET);
    this.adminRouter.addAction ('GET', undefined, RootActions.GET);

    var Database = new MongoDB.Db (
        this.config.databaseName,
        new MongoDB.Server (this.config.databaseAddress, this.config.databasePort),
        { w:'majority', journal:true }
    );
    Database.open (function (err) {
        if (err) {
            self.logger.fatal (err);
            return;
        }
        async.parallel ([
            function (callback) {
                Database.collection (self.config.domainCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.DomainCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.userCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.UserCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.usernameCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.UsernameCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.clientCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.ClientCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.confirmationCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.ConfirmationCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.notificationCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.NotificationCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.invoiceCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.InvoiceCollection = collection;
                    callback();
                });
            }
        ], function(){
            self.gateway.init (function(){
                self.server.listen (port, self.gateway, function(){
                    self.adminRouter.init (function(){
                        self.server.listen (adminPort, self.adminRouter, function(){
                            self.nodeInfo = {
                                port:       port,
                                adminPort:  adminPort,
                                hostname:   os.hostname()
                            };
                            callback();
                        });
                    });
                });
            });
        });
    });
};
<commit_msg>fixed name error<commit_after>
var util         = require ('util');
var EventEmitter = require ('events').EventEmitter;
var os           = require ('os');
var async        = require ('async');
var MongoDB      = require ('mongodb');
var submergence  = require ('submergence');
var substation   = require ('substation');
var filth        = require ('filth');
var cachew       = require ('cachew');
var Gateway      = require ('./lib/Gateway');
var Action       = require ('./lib/Action');
var Exacter      = require ('./lib/Exacter');

var GetSession   = require ('./lib/ControlActions/GetSession');
var PostSession  = require ('./lib/ControlActions/PostSession');
var PostEvent    = require ('./lib/ControlActions/PostEvent');
var GetConfig    = require ('./lib/ControlActions/GetConfig');
var PutConfig    = require ('./lib/ControlActions/PutConfig');
var RootActions  = require ('./lib/ControlActions/root');
var AccountActions  = require ('./lib/ControlActions/account');
var SessionActions  = require ('./lib/ControlActions/session');
var DomainActions  = require ('./lib/ControlActions/domain');
var PermitActions  = require ('./lib/ControlActions/permit');
var ConfirmActions  = require ('./lib/ControlActions/confirm');

/**     @struct sublayer.Configuration
    @super submergence.Configuration

@member/String databaseName
    @default `"sublayer"`
@member/String applicationName
    @default `"sublayer"`
@member/Object actionDefaults
    Default configuration options used for all Actions.
*/
var DEFAULT_CONFIG = {
    databaseName:               "sublayer",
    applicationName:            "sublayer",
    domainCollectionName:       "Domain",
    userCollectionName:         "User",
    usernameCollectionName:     "Username",
    clientCollectionName:       "Client",
    confirmationCollectionName: "Confirm",
    notificationCollectionName: "Notice",
    invoiceCollectionName:      "Invoice",
    actionDefaults:             {},
    domainCacheLength:          2048,
    domainCacheTimout:          1000 * 60
};

/**     @module/class sublayer
    Realtime and peer to peer service layer.
@argument/.Configuration config
@returns/sublayer
    If the `new` keyword is not used, an instance is created and returned.
@event userOnline
@event clientOnline
@event userOffline
@event clientOffline
*/
function sublayer (config) {
    if (!(this instanceof sublayer))
        return new sublayer (config);

    this.config = filth.clone (submergence.DEFAULT_CONFIG);
    filth.merge (this.config, DEFAULT_CONFIG);
    filth.merge (this.config, config);

    this.server = new submergence (this.config);
    this.logger = this.server.logger;
    this.gateway = new Gateway (this, this.config, this.server);
    this.adminRouter = new substation.Router (this, this.config);
    this.exacter = new Exacter (this, this.config);
}
module.exports = sublayer;

sublayer.prototype.getDomain = function (domain, callback) {
    var cached;
    if (this.domainCache && ( cached = this.domainCache.get (domain) ))
        return callback (undefined, cached);

    var self = this;
    this.DomainCollection.findOne ({ domain:domain }, function (err, domainRecord) {
        if (err) {
            self.logger.error ({ domain:domain }, 'failed to look up domain');
            return callback (err);
        }
        if (!domainRecord)
            return callback();

        if (domainRecord.actions && domainRecord.actions.length) {
            for (var i=0,j=domainRecord.actions.length; i<j; i++)
                domainRecord.actions[i] = new Action (
                    domain,
                    domainRecord.apiHash,
                    domainRecord.actions[i]
                );
        }

        if (self.domainCache)
            self.domainCache.set (domain, domainRecord);

        callback (undefined, domainRecord);
    });
};

/**     @member/Function listen
@argument/Number port
@callback
    @argument/Error|undefined err
*/
sublayer.prototype.listen = function (port, adminPort, callback) {
    var self = this;

    if (this.config.domainCacheLength)
        this.domainCache = new cachew.ChainCache (
            this.config.domainCacheLength,
            this.config.domainCacheTimout
        );

    if (this.config.DomainCollection) {
        this.DomainCollection = this.config.DomainCollection;
        this.gateway.init (router, function(){
            self.server.listen (port, self.gateway, function(){
                self.server.listen (adminPort, self.adminRouter, callback);
            });
        });
        return;
    }

    this.adminRouter.addAction ('GET', 'user_session', GetSession);
    this.adminRouter.addAction ('POST', 'user_session', PostSession);
    this.adminRouter.addAction ('POST', 'event', PostEvent);
    this.adminRouter.addAction ('GET', 'config', GetConfig);
    this.adminRouter.addAction ('PUT', 'config', PutConfig);
    this.adminRouter.addAction ('GET', new RegExp ('/account/([\\w\\-_]+)$'), AccountActions.GET);
    this.adminRouter.addAction ('PUT', new RegExp ('/account/([\\w\\-_]+)$'), AccountActions.PUT);
    this.adminRouter.addAction ('POST', 'account', AccountActions.POST);
    this.adminRouter.addAction ('POST', 'session', SessionActions.POST);
    this.adminRouter.addAction ('DELETE', 'session', SessionActions.DELETE);
     // we can't DELETE from a form, so let's make an RPC-style handler as well
    this.adminRouter.addAction ('GET', 'logout', SessionActions.DELETE);
    this.adminRouter.addAction ('GET', new RegExp ('/domain/([\\w\\-_]+)$'), DomainActions.GET);
    this.adminRouter.addAction ('PUT', new RegExp ('/domain/([\\w\\-_]+)$'), DomainActions.PUT);
    this.adminRouter.addAction ('POST', 'domain', DomainActions.POST);
    this.adminRouter.addAction ('GET', new RegExp ('/permit/([\\w\\-_]+)$'), PermitActions.GET);
    this.adminRouter.addAction ('POST', new RegExp ('/permit/([\\w\\-_]+)$'), PermitActions.POST);
    this.adminRouter.addAction ('DELETE', new RegExp ('/permit/([\\w\\-_]+)/([\\w\\-_]+)$'), PermitActions.DELETE);
    this.adminRouter.addAction ('GET', new RegExp ('/confirm/([\\w\\-_]+)$'), ConfirmActions.GET);
    this.adminRouter.addAction ('GET', undefined, RootActions.GET);

    var Database = new MongoDB.Db (
        this.config.databaseName,
        new MongoDB.Server (this.config.databaseAddress, this.config.databasePort),
        { w:'majority', journal:true }
    );
    Database.open (function (err) {
        if (err) {
            self.logger.fatal (err);
            return;
        }
        async.parallel ([
            function (callback) {
                Database.collection (self.config.domainCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.DomainCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.userCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.UserCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.usernameCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.UsernameCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.clientCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.ClientCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.confirmationCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.ConfirmationCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.notificationCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.NotificationCollection = collection;
                    callback();
                });
            },
            function (callback) {
                Database.collection (self.config.invoiceCollectionName, function (err, collection) {
                    if (err) {
                        self.logger.fatal (err);
                        return process.exit (1);
                    }
                    self.InvoiceCollection = collection;
                    callback();
                });
            }
        ], function(){
            self.gateway.init (function(){
                self.server.listen (port, self.gateway, function(){
                    self.adminRouter.init (function(){
                        self.server.listen (adminPort, self.adminRouter, function(){
                            self.nodeInfo = {
                                port:       port,
                                adminPort:  adminPort,
                                hostname:   os.hostname()
                            };
                            callback();
                        });
                    });
                });
            });
        });
    });
};
<|endoftext|>


<commit_before>/****************************************************************************
Copyright (c) 2016-2017 zilongshanren

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/

'use strict';
const Fs = require('fire-fs');
const Path = require('fire-path');
const Async = require('async');
const Del = require('del');

let sharpPath;
if (Editor.dev) {
  sharpPath = 'sharp';
} else {
  sharpPath = Editor.url('unpack://utils/sharp');
}
const Sharp = require(sharpPath);

const dontSelectCorrectAssetMsg = {
  type: 'warning',
  buttons: ['OK'],
  titile: 'Unpack Texture Packer Atlas',
  message: 'Please select a Texture Packer asset at first!',
  defaultId: 0,
  noLink: true
};

module.exports = {
  load () {
    // execute when package loaded
  },

  unload () {
    // execute when package unloaded
  },

  // register your ipc messages here
  messages: {
    'unpack' () {
      Editor.Metrics.trackEvent({
        category: 'Packages',
        label: 'unpack-textureatlas',
        action: 'Open By Menu'
      }, null);

      let currentSelection = Editor.Selection.curSelection('asset');
      if (currentSelection.length > 0) {
        let selectionUUid = currentSelection[0];
        let selectionMeta = Editor.assetdb.loadMetaByUuid(selectionUUid);
        let selectionUrl = Editor.assetdb.uuidToUrl(selectionUUid);
        let assetInfo = Editor.assetdb.assetInfoByUuid(selectionUUid);
        const textureAtlasPath = Editor.assetdb.uuidToFspath(selectionMeta.rawTextureUuid);

        if (!textureAtlasPath) {
          Editor.Dialog.messageBox(dontSelectCorrectAssetMsg);
          return;
        }
        let textureAtlasSubMetas = selectionMeta.getSubMetas();

        if (assetInfo.type === 'sprite-atlas'
            && selectionMeta.type === 'Texture Packer'
            && textureAtlasSubMetas) {

          let extractedImageSaveFolder = Path.join(Editor.projectPath, 'temp', Path.basenameNoExt(textureAtlasPath) + '_unpack');
          Fs.mkdirsSync(extractedImageSaveFolder);

          let spriteFrameNames = Object.keys(textureAtlasSubMetas);
          Async.forEach(spriteFrameNames, function (spriteFrameName, next) {
            let spriteFrameObj = textureAtlasSubMetas[spriteFrameName];
            let isRotated = spriteFrameObj.rotated;
            let originalSize = cc.size(spriteFrameObj.rawWidth, spriteFrameObj.rawHeight);
            let rect = cc.rect(spriteFrameObj.trimX, spriteFrameObj.trimY, spriteFrameObj.width,spriteFrameObj.height);
            let offset = cc.p(spriteFrameObj.offsetX, spriteFrameObj.offsetY);
            let trimmedLeft = offset.x + (originalSize.width - rect.width) / 2;
            let trimmedRight = (originalSize.width - rect.width) / 2 - offset.x;
            let trimmedTop = (originalSize.height - rect.height) / 2 - offset.y;
            let trimmedBottom = offset.y + (originalSize.height - rect.height) / 2;

            let sharpCallback = (err) => {
              if (err) {
                Editor.error('Generating ' + spriteFrameName + ' error occurs, details:' + err);
              }

              Editor.log(spriteFrameName + ' is generated successfully!');
              next();
            };

            let extractedSmallPngSavePath = Path.join(extractedImageSaveFolder, spriteFrameName);
            if (isRotated) {
              Sharp(textureAtlasPath).extract({left: rect.x, top: rect.y, width: rect.height, height:rect.width})
                .background('rgba(0,0,0,0)')
                .extend({top: trimmedTop, bottom: trimmedBottom, left: trimmedLeft, right: trimmedRight})
                .rotate(270)
                .toFile(extractedSmallPngSavePath, sharpCallback);

            } else {
              Sharp(textureAtlasPath).extract({left: rect.x, top: rect.y, width: rect.width, height:rect.height})
                .background('rgba(0,0,0,0)')
                .extend({top: trimmedTop, bottom: trimmedBottom, left: trimmedLeft, right: trimmedRight})
                .rotate(0)
                .toFile(extractedSmallPngSavePath, sharpCallback);
            }
          }, () => {
            Editor.log(`There are ${spriteFrameNames.length} textures are generated!`);
            //start importing the generated textures folder
            Editor.Ipc.sendToMain( 'asset-db:import-assets', [extractedImageSaveFolder], Path.dirname(selectionUrl), true, (err) => {
              if (err) Editor.log('Importing assets error occurs: details' + err);

              Del(extractedImageSaveFolder, { force: true });
            }, -1);

          }); // end of Async.forEach

        } else {
          Editor.Dialog.messageBox(dontSelectCorrectAssetMsg);
        }
      } else {
         Editor.Dialog.messageBox(dontSelectCorrectAssetMsg);
      }
    },
  },
};
<commit_msg>适配编辑器2.2.2版本api<commit_after>/****************************************************************************
Copyright (c) 2016-2017 zilongshanren

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/

'use strict';
const Fs = require('fire-fs');
const Path = require('fire-path');
const Async = require('async');
const Del = require('del');

let sharpPath;
if (Editor.dev) {
  sharpPath = 'sharp';
} else {
  sharpPath = Editor.url('unpack://utils/sharp');
}
const Sharp = require(sharpPath);

const dontSelectCorrectAssetMsg = {
  type: 'warning',
  buttons: ['OK'],
  titile: 'Unpack Texture Packer Atlas',
  message: 'Please select a Texture Packer asset at first!',
  defaultId: 0,
  noLink: true
};

module.exports = {
  load () {
    // execute when package loaded
  },

  unload () {
    // execute when package unloaded
  },

  // register your ipc messages here
  messages: {
    'unpack' () {
      Editor.Metrics.trackEvent({
        category: 'Packages',
        label: 'unpack-textureatlas',
        action: 'Open By Menu'
      }, null);

      let currentSelection = Editor.Selection.curSelection('asset');
      if (currentSelection.length > 0) {
        let selectionUUid = currentSelection[0];
        let selectionMeta = Editor.assetdb.loadMetaByUuid(selectionUUid);
        let selectionUrl = Editor.assetdb.uuidToUrl(selectionUUid);
        let assetInfo = Editor.assetdb.assetInfoByUuid(selectionUUid);
        const textureAtlasPath = Editor.assetdb.uuidToFspath(selectionMeta.rawTextureUuid);

        if (!textureAtlasPath) {
          Editor.Dialog.messageBox(dontSelectCorrectAssetMsg);
          return;
        }
        let textureAtlasSubMetas = selectionMeta.getSubMetas();

        if (assetInfo.type === 'sprite-atlas'
            && selectionMeta.type === 'Texture Packer'
            && textureAtlasSubMetas) {
          // In Creator Editor version 2.2.2,use Editor.Project.path
          let extractedImageSaveFolder = Path.join(Editor.Project.path, 'temp', Path.basenameNoExt(textureAtlasPath) + '_unpack');
          Fs.mkdirsSync(extractedImageSaveFolder);

          let spriteFrameNames = Object.keys(textureAtlasSubMetas);
          Async.forEach(spriteFrameNames, function (spriteFrameName, next) {
            let spriteFrameObj = textureAtlasSubMetas[spriteFrameName];
            let isRotated = spriteFrameObj.rotated;
            let originalSize = cc.size(spriteFrameObj.rawWidth, spriteFrameObj.rawHeight);
            let rect = cc.rect(spriteFrameObj.trimX, spriteFrameObj.trimY, spriteFrameObj.width,spriteFrameObj.height);
            let offset = cc.p(spriteFrameObj.offsetX, spriteFrameObj.offsetY);
            let trimmedLeft = offset.x + (originalSize.width - rect.width) / 2;
            let trimmedRight = (originalSize.width - rect.width) / 2 - offset.x;
            let trimmedTop = (originalSize.height - rect.height) / 2 - offset.y;
            let trimmedBottom = offset.y + (originalSize.height - rect.height) / 2;

            let sharpCallback = (err) => {
              if (err) {
                Editor.error('Generating ' + spriteFrameName + ' error occurs, details:' + err);
              }

              Editor.log(spriteFrameName + ' is generated successfully!');
              next();
            };

            let extractedSmallPngSavePath = Path.join(extractedImageSaveFolder, spriteFrameName);
            if (isRotated) {

              Sharp(textureAtlasPath).extract({left: rect.x, top: rect.y, width: rect.height, height:rect.width})
                // .background('rgba(0,0,0,0)')
                .extend({top: trimmedTop, bottom: trimmedBottom, left: trimmedLeft, right: trimmedRight})
                .rotate(270)
                .toFile(extractedSmallPngSavePath, sharpCallback);

            } else {
              Sharp(textureAtlasPath).extract({left: rect.x, top: rect.y, width: rect.width, height:rect.height})
                // .background('rgba(0,0,0,0)') -- In Creator Editor version 2.2.2,background is undefined
                .extend({top: trimmedTop, bottom: trimmedBottom, left: trimmedLeft, right: trimmedRight})
                .rotate(0)
                .toFile(extractedSmallPngSavePath, sharpCallback);
            }
          }, () => {
            Editor.log(`There are ${spriteFrameNames.length} textures are generated!`);
            //start importing the generated textures folder
            Editor.Ipc.sendToMain( 'asset-db:import-assets', [extractedImageSaveFolder], Path.dirname(selectionUrl), true, (err) => {
              if (err) Editor.log('Importing assets error occurs: details' + err);

              Del(extractedImageSaveFolder, { force: true });
            }, -1);

          }); // end of Async.forEach

        } else {
          Editor.Dialog.messageBox(dontSelectCorrectAssetMsg);
        }
      } else {
         Editor.Dialog.messageBox(dontSelectCorrectAssetMsg);
      }
    },
  },
};
<|endoftext|>


<commit_before>'use strict';
var todoApp = function() {
  var UI = {
    input : document.querySelector("input[name='todo']"),
    button : document.querySelector("button[name='save']"),
    list : document.querySelector("#todo-list")
  };
  var tasks = [];
  var template = '
[text]\
        <button data-id="[id-task]">X</button>\
        <input type="checkbox" value="[id-task]" data-done="[id-task]">\

';
  var deleteItem = function(event) {
    var item = document.querySelector('#item-' + event.target.getAttribute('data-id'));
    item.parentElement.removeChild(item);
  };
  var doneItem = function(event) {
    var item = document.querySelector('#item-' + event.target.value);
    item.className = 'done';
  };
  UI.button.addEventListener('click', function(event) {
    event.preventDefault();
    tasks.push(UI.input.value);
    var item = template.replace('[text]', UI.input.value).replace(/\[id\-task\]/g, tasks.length);
    UI.list.insertAdjacentHTML('beforeEnd', item);
    UI.input.value = '';
    document.querySelector('button[data-id="' + tasks.length + '"]').addEventListener('click', deleteItem);
    document.querySelector('input[data-done="' + tasks.length + '"]').addEventListener('click', doneItem);
  });
};

window.onload = todoApp;<commit_msg>Task from array to index count number<commit_after>'use strict';
var todoApp = function() {
  var UI = {
    input : document.querySelector("input[name='todo']"),
    button : document.querySelector("button[name='save']"),
    list : document.querySelector("#todo-list")
  };
  var tasks = 0;
  var template = '
[text]\
        <button data-id="[id-task]">X</button>\
        <input type="checkbox" value="[id-task]" data-done="[id-task]">\

';
  var deleteItem = function(event) {
    var item = document.querySelector('#item-' + event.target.getAttribute('data-id'));
    item.parentElement.removeChild(item);
    tasks--;
  };
  var doneItem = function(event) {
    var item = document.querySelector('#item-' + event.target.value);
    item.className = 'done';
  };
  UI.button.addEventListener('click', function(event) {
    event.preventDefault();
    tasks++;
    var item = template.replace('[text]', UI.input.value).replace(/\[id\-task\]/g, tasks);
    UI.list.insertAdjacentHTML('beforeEnd', item);
    UI.input.value = '';
    document.querySelector('button[data-id="' + tasks + '"]').addEventListener('click', deleteItem);
    document.querySelector('input[data-done="' + tasks + '"]').addEventListener('click', doneItem);
  });
};

window.onload = todoApp;<|endoftext|>


<commit_before>/********************************************************************************/
/********************************************************************************/
/*Function Calculate Current Date*/
function get_current_date(){
	var today = new Date();
    var dd = today.getDate();
    var mm = today.getMonth()+1; //January is 0!
    var yyyy = today.getFullYear();
    if(dd<10){ dd='0'+dd; }
    if(mm<10){ mm='0'+mm; }
    var today = mm+'/'+dd+'/'+yyyy;
	return today;
}
/********************************************************************************/
/********************************************************************************/
/*Function Calculate Current Date Difference*/
function get_date_difference(end_date)
{
var d1 = new Date(end_date);
var d2 = new Date(get_current_date());
var timeDiff = d1.getTime() - d2.getTime();
var DaysDiff = timeDiff / (1000 * 3600 * 24);
   if(DaysDiff > 0){
	  $('.badges').append('');
   }
}
<commit_msg>Update badge-code.js<commit_after>/********************************************************************************/
/********************************************************************************/
/*Function Calculate Current Date*/
function get_current_date(){
	var today = new Date();
    var dd = today.getDate();
    var mm = today.getMonth()+1; //January is 0!
    var yyyy = today.getFullYear();
    if(dd<10){ dd='0'+dd; }
    if(mm<10){ mm='0'+mm; }
    var today = mm+'/'+dd+'/'+yyyy;
	return today;
}
/********************************************************************************/
/********************************************************************************/
/*Function Calculate Current Date Difference*/
function get_date_difference(end_date)
{
var d1 = new Date(end_date);
var d2 = new Date(get_current_date());
var timeDiff = d1.getTime() - d2.getTime();
var DaysDiff = timeDiff / (1000 * 3600 * 24);
   if(DaysDiff > 0){
	  $('.badges').append('');
   }
}
<|endoftext|>


<commit_before>"use strict";

require('./src/css/style.css');
require('./src/js/web.js');
<commit_msg>Fix last lint warnings<commit_after>"use strict";

require("./src/css/style.css");
require("./src/js/web.js");
<|endoftext|>


<commit_before>$(function() {
  /**
   * Create a constructor for sparklines that takes some sensible defaults and merges in the individual
   * chart options. This function is also available from the jQuery plugin as $(element).highcharts('SparkLine').
   */
  Highcharts.SparkLine = function (options, callback) {
    var defaultOptions = {
      series: {
        pointStart: 1
      },
      chart: {
        renderTo: (options.chart && options.chart.renderTo) || this,
        backgroundColor: null,
        borderWidth: 0,
        type: 'area',
        margin: [2, 0, 2, 0],
        width: 120,
        height: 20,
        style: {
          overflow: 'visible'
        },
        skipClone: true
      },
      title: {
        text: ''
      },
      credits: {
        enabled: false
      },
      xAxis: {
        labels: {
          enabled: false
        },
        title: {
          text: null
        },
        startOnTick: false,
        endOnTick: false,
        tickPositions: []
      },
      yAxis: {
        endOnTick: false,
        startOnTick: false,
        labels: {
          enabled: false
        },
        title: {
          text: null
        },
        tickPositions: [0]
      },
      legend: {
        enabled: false
      },
      tooltip: {
        backgroundColor: null,
        borderWidth: 0,
        shadow: false,
        useHTML: true,
        hideDelay: 0,
        shared: true,
        padding: 0,
        positioner: function (w, h, point) {
          return { x: point.plotX - w / 2, y: point.plotY - h};
        },
        headerFormat: '',
        pointFormat: '{point.y}'
      },
      plotOptions: {
        series: {
          animation: false,
          lineWidth: 1,
          shadow: false,
          states: {
            hover: {
              lineWidth: 1
            }
          },
          marker: {
            radius: 1,
            states: {
              hover: {
                radius: 2
              }
            }
          },
          fillOpacity: 0.25
        },
        column: {
          negativeColor: '#910000',
          borderColor: 'silver'
        }
      }
    };
    options = Highcharts.merge(defaultOptions, options);

    return new Highcharts.Chart(options, callback);
  };

  $('*[data-sparkline]').each(function(i, e) {
    var $e = $(e);

    var opts = {
      series: [{
        data: $.map($e.data('sparkline').split(','), parseFloat),
      }]
    };

    if ($e.data('label')) {
      opts.tooltip = {pointFormat: $e.data('label')};
    }

    $e.highcharts('SparkLine', opts);
  });
});
<commit_msg>Make sparkline a jquery plugin<commit_after>$(function() {
  /**
   * Create a constructor for sparklines that takes some sensible defaults and merges in the individual
   * chart options. This function is also available from the jQuery plugin as $(element).highcharts('SparkLine').
   */
  Highcharts.SparkLine = function (options, callback) {
    var defaultOptions = {
      series: {
        pointStart: 1
      },
      chart: {
        renderTo: (options.chart && options.chart.renderTo) || this,
        backgroundColor: null,
        borderWidth: 0,
        type: 'area',
        margin: [2, 0, 2, 0],
        width: 120,
        height: 20,
        style: {
          overflow: 'visible'
        },
        skipClone: true
      },
      title: {
        text: ''
      },
      credits: {
        enabled: false
      },
      xAxis: {
        labels: {
          enabled: false
        },
        title: {
          text: null
        },
        startOnTick: false,
        endOnTick: false,
        tickPositions: []
      },
      yAxis: {
        endOnTick: false,
        startOnTick: false,
        labels: {
          enabled: false
        },
        title: {
          text: null
        },
        tickPositions: [0]
      },
      legend: {
        enabled: false
      },
      tooltip: {
        backgroundColor: null,
        borderWidth: 0,
        shadow: false,
        useHTML: true,
        hideDelay: 0,
        shared: true,
        padding: 0,
        positioner: function (w, h, point) {
          return { x: point.plotX - w / 2, y: point.plotY - h};
        },
        headerFormat: '',
        pointFormat: '{point.y}'
      },
      plotOptions: {
        series: {
          animation: false,
          lineWidth: 1,
          shadow: false,
          states: {
            hover: {
              lineWidth: 1
            }
          },
          marker: {
            radius: 1,
            states: {
              hover: {
                radius: 2
              }
            }
          },
          fillOpacity: 0.25
        },
        column: {
          negativeColor: '#910000',
          borderColor: 'silver'
        }
      }
    };
    options = Highcharts.merge(defaultOptions, options);

    return new Highcharts.Chart(options, callback);
  };

  $.fn.sparkline = function(options) {
    this.each(function(i, e) {
      var $e = $(e);

      var opts = {
        series: [{
          data: $.map($e.data('sparkline').split(','), parseFloat),
        }]
      };

      if ($e.data('label')) {
        opts.tooltip = {pointFormat: $e.data('label')};
      }

      opts = Highcharts.merge(opts, options);

      $e.highcharts('SparkLine', opts);
    });
  };

  $('*[data-sparkline]').sparkline();
});
<|endoftext|>


<commit_before>// @flow

import * as React from 'react'
import {View, Text, StyleSheet, Platform} from 'react-native'
import Icon from 'react-native-vector-icons/Entypo'
import type {ViewType} from '../views'
import LinearGradient from 'react-native-linear-gradient'
import {Touchable} from '../components/touchable'
import * as c from '../components/colors'
import {iPhoneX} from '../components/device'

type Props = {
	view: ViewType,
	onPress: () => any,
}

export function HomeScreenButton({view, onPress}: Props) {
	const foreground =
		view.foreground === 'light' ? styles.lightForeground : styles.darkForeground

	return (
		<TouchableButton
			gradient={view.gradient}
			label={view.title}
			onPress={onPress}
			tint={view.tint}
		>
			<View style={styles.contents}>
				<Icon name={view.icon} size={32} style={[foreground, styles.icon]} />
				<Text style={[foreground, styles.text]}>{view.title}</Text>
			</View>
		</TouchableButton>
	)
}

function TouchableButton({onPress, label, children, tint, gradient}) {
	if (Platform.OS === 'android') {
		return (
			<Tint gradient={gradient} tint={tint}>
				<TouchableWrapper label={label} onPress={onPress}>
					{children}
				</TouchableWrapper>
			</Tint>
		)
	} else {
		return (
			<TouchableWrapper label={label} onPress={onPress}>
				<Tint gradient={gradient} tint={tint}>
					{children}
				</Tint>
			</TouchableWrapper>
		)
	}
}

function TouchableWrapper({onPress, children, label}) {
	return (
		<Touchable
			accessibilityComponentType="button"
			accessibilityLabel={label}
			accessibilityTraits="button"
			highlight={false}
			onPress={onPress}
		>
			{children}
		</Touchable>
	)
}

function Tint({tint = 'black', gradient, children}) {
	if (!gradient) {
		const bg = {backgroundColor: tint}
		return <View style={[styles.button, bg]}>{children}</View>
	}

	return (
		<LinearGradient
			colors={gradient}
			end={{x: 1, y: 0.85}}
			start={{x: 0, y: 0.05}}
			style={styles.button}
		>
			{children}
		</LinearGradient>
	)
}

export const CELL_MARGIN = 10
const cellVerticalPadding = 8
const cellHorizontalPadding = 4

const styles = StyleSheet.create({
	button: {
		elevation: 2,
		borderRadius: Platform.OS === 'ios' ? (iPhoneX ? 17 : 6) : 3,

		marginBottom: CELL_MARGIN,
		marginLeft: CELL_MARGIN / 2,
		marginRight: CELL_MARGIN / 2,
	},
	contents: {
		alignItems: 'center',
		justifyContent: 'center',

		paddingTop: cellVerticalPadding,
		paddingBottom: cellVerticalPadding / 2,
		paddingHorizontal: cellHorizontalPadding,
	},
	icon: {
		backgroundColor: c.transparent,
	},
	text: {
		backgroundColor: c.transparent,
		fontFamily: Platform.OS === 'ios' ? 'System' : 'sans-serif-condensed',
		fontSize: 14,
	},
	lightForeground: {
		color: c.homescreenForegroundLight,
	},
	darkForeground: {
		color: c.homescreenForegroundDark,
	},
})
<commit_msg>mark homescreen buttons as "accessible" to prevent screen readers from reading their content<commit_after>// @flow

import * as React from 'react'
import {View, Text, StyleSheet, Platform} from 'react-native'
import Icon from 'react-native-vector-icons/Entypo'
import type {ViewType} from '../views'
import LinearGradient from 'react-native-linear-gradient'
import {Touchable} from '../components/touchable'
import * as c from '../components/colors'
import {iPhoneX} from '../components/device'

type Props = {
	view: ViewType,
	onPress: () => any,
}

export function HomeScreenButton({view, onPress}: Props) {
	const foreground =
		view.foreground === 'light' ? styles.lightForeground : styles.darkForeground

	return (
		<TouchableButton
			gradient={view.gradient}
			label={view.title}
			onPress={onPress}
			tint={view.tint}
		>
			<View style={styles.contents}>
				<Icon name={view.icon} size={32} style={[foreground, styles.icon]} />
				<Text style={[foreground, styles.text]}>{view.title}</Text>
			</View>
		</TouchableButton>
	)
}

function TouchableButton({onPress, label, children, tint, gradient}) {
	if (Platform.OS === 'android') {
		return (
			<Tint gradient={gradient} tint={tint}>
				<TouchableWrapper label={label} onPress={onPress}>
					{children}
				</TouchableWrapper>
			</Tint>
		)
	} else {
		return (
			<TouchableWrapper label={label} onPress={onPress}>
				<Tint gradient={gradient} tint={tint}>
					{children}
				</Tint>
			</TouchableWrapper>
		)
	}
}

function TouchableWrapper({onPress, children, label}) {
	return (
		<Touchable
			accessibilityComponentType="button"
			accessibilityLabel={label}
			accessibilityTraits="button"
			accessible={true}
			highlight={false}
			onPress={onPress}
		>
			{children}
		</Touchable>
	)
}

function Tint({tint = 'black', gradient, children}) {
	if (!gradient) {
		const bg = {backgroundColor: tint}
		return <View style={[styles.button, bg]}>{children}</View>
	}

	return (
		<LinearGradient
			colors={gradient}
			end={{x: 1, y: 0.85}}
			start={{x: 0, y: 0.05}}
			style={styles.button}
		>
			{children}
		</LinearGradient>
	)
}

export const CELL_MARGIN = 10
const cellVerticalPadding = 8
const cellHorizontalPadding = 4

const styles = StyleSheet.create({
	button: {
		elevation: 2,
		borderRadius: Platform.OS === 'ios' ? (iPhoneX ? 17 : 6) : 3,

		marginBottom: CELL_MARGIN,
		marginLeft: CELL_MARGIN / 2,
		marginRight: CELL_MARGIN / 2,
	},
	contents: {
		alignItems: 'center',
		justifyContent: 'center',

		paddingTop: cellVerticalPadding,
		paddingBottom: cellVerticalPadding / 2,
		paddingHorizontal: cellHorizontalPadding,
	},
	icon: {
		backgroundColor: c.transparent,
	},
	text: {
		backgroundColor: c.transparent,
		fontFamily: Platform.OS === 'ios' ? 'System' : 'sans-serif-condensed',
		fontSize: 14,
	},
	lightForeground: {
		color: c.homescreenForegroundLight,
	},
	darkForeground: {
		color: c.homescreenForegroundDark,
	},
})
<|endoftext|>


<commit_before>// Copyright 2011 Mark Bao
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

function envious () {

  // defaults
  var environments = {};

  // init function
  this.init = function(environments) {
    // save environments
    this.environments = environments;
  }

  //
}

module.exports = new envious();
<commit_msg>simply use environments var<commit_after>// Copyright 2011 Mark Bao
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

function envious () {

  // defaults
  var environments = {};

}

module.exports = new envious();
<|endoftext|>