<commit_before>/*
 *  Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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.
 *
 */
asset.manager = function(ctx){

    var buildPublishedQuery = function(query) {
        query = query || {};
        var isLCEnabled = ctx.rxtManager.isLifecycleEnabled(ctx.assetType);
        //If lifecycles are not enabled then do nothing
        if(!isLCEnabled){
            log.warn('lifecycles disabled,not adding published states to search query');
            return query;
        }
        //Get all of the published assets
        var publishedStates = ctx.rxtManager.getPublishedStates(ctx.assetType) || [];
        //Determine if there are any published states
        if (publishedStates.length == 0) {
            return query;
        }
        //TODO: Even though an array is sent in only the first search value is accepted
        query.lcState=[publishedStates[0]];
        return query;
    };

    return {
    search: function(query, paging) {
        query=buildPublishedQuery(query);
        var assets = this._super.search.call(this, query, paging);
        return assets;
    },
    get:function(id){
       // log.info('Calling custom get of asset');
       return this._super.get.call(this,id);
    }
        /*search : function(query, paging) {
            var carbonAPI = require('carbon');
            var tenantDomain = carbonAPI.server.tenantDomain({
                tenantId : ctx.tenantId
            });
            var server = require('store').server;
            var user = server.current(ctx.session);
            var userName = '__wso2.am.anon__';

            if (user != null) {
                userName = user.username;
            }
            log.info('============== user name ==========' + userName);
            var apistore = require('apistore').apistore.instance(userName);
            var assetApi = apistore.getAllPaginatedAPIsByStatus(tenantDomain,
                    0, 100, '');
            log.info('This is the custom APIM search method');
            log.info('============== api json  ==========' + assetApi);
            // return assetApi;

            var json = JSON.parse(assetApi);

            var apisArray = [];
            if (json.apis.length > 0) {
                apisArray = json.apis;
            }
            log.info('============== array ===========' + apisArray);
            return apisArray;
        }*/
    };
};

asset.configure = function(ctx) {
    return {
        meta: {
            search: {
                searchableFields: ['overview_provider', 'overview_name']
            }
        }
    }
};
asset.server = function(ctx) {
    return {
         onUserLoggedIn : function(){
            var userName=ctx.username;
            var apistore = require('apistore').apistore.instance(userName);
            var subscriber=apistore.getSubscriber(userName);
            if(!subscriber){
            apistore.addSubscriber(userName,ctx.tenantId);
            }
        },
        endpoints: {
            pages: [{
                title: 'Prototyped APIs',
                url: 'prototyped_apis',
                path: 'prototyped_apis.jag'
            }, {
                title: 'My Applications',
                url: 'my_applications',
                path: 'my_applications.jag',
                secured: true
            },/** {
            title: 'API Details'
            url: 'details'
            path: 'details.jag'
            },*/
            {
                title: 'My Subscriptions',
                url: 'my_subscriptions',
                path: 'my_subscriptions.jag',
                secured: true
            }, {
                title: 'Forum',
                url: 'forum',
                path: 'forum.jag'
            }, {
                title: 'Statistics',
                url: 'statistics',
                path: 'statistics.jag'
            }]
        }
    };
};
asset.renderer = function(ctx) {
    var twitterLink = function(assetUrl, asset) {
        var attr = asset.attributes || {};
        var description = attr.overview_description ? attr.overview_description : 'No description';
        return 'https://twitter.com/share?text=' + description + '&url=' + assetUrl;
    };
    var facebookLink = function(assetUrl, asset) {
        return 'https://facebook.com/sharer.php?u=' + assetUrl;
    };
    var gplusLink = function(assetUrl, asset) {
        return 'https://plus.google.com/share?url=' + assetUrl;
    };
    var diggitLink = function(assetUrl, asset) {
        return 'https://digg.com/submit?url=' + assetUrl;
    };
    return {
        details:function(page){
            // log.info('Details page rendered!!!');

    //=================== Getting subscription details ========================

    var carbonAPI = require('carbon');
    var tenantDomain = carbonAPI.server.tenantDomain({
        tenantId : ctx.tenantId
    });
    var server = require('store').server;
    var user = server.current(ctx.session);
    var userName = '__wso2.am.anon__';

    if (user != null) {
        userName = user.username;
    }
    var lenI,lenJ,i,j,result,apidata,deniedTiers,tiers,appsList=[],subscribedToDefault=false,showSubscribe=false,status,selectedDefault=false;
    var apistore = require('apistore').apistore.instance(userName);
    var applications = JSON.parse(apistore.getApplications(userName));
    var asset = page.assets;
    if(asset!=null){
    status=asset.lifecycleState;
    if(status!=null){
    status=status.toUpperCase()
    }
    }
    var resultapi=apistore.getAPI(asset.attributes.overview_provider,asset.name,asset.attributes.overview_version);
    var apidata=JSON.parse(resultapi);
    if(apidata!=null){
    tiers=apidata.tiers;
    if(status=="PUBLISHED"){
    showSubscribe=true;
    }
    }
    var subscriptions=JSON.parse(apistore.getAPISubscriptions(asset.attributes.overview_provider,asset.name,asset.attributes.overview_version,userName));
    if(applications!=null){
                lenI = applications.length;
            }if(subscriptions!=null){
                lenJ = subscriptions.length;
            }
            Label1:
                    for (i = 0; i < lenI; i++) {
                        var application = applications[i];
                        for (j = 0; j < lenJ; j++) {
                            var subscription = subscriptions[j];
                            log.name("initiationn appp namee"+application.name);
                            if (subscription.applicationId == application.id) {
                                log.name("inside if"+application.name);
                                if(application.name=="DefaultApplication"){
                                   selectedDefault=true;
                                }
                                continue Label1;
                            }else{
                              log.name("appp namee"+application.name);
                              if(application.name=="DefaultApplication"){
                                  log.name("appp namee"+application.name);
                                   subscribedToDefault=true;
                                }
                            }
                        }

                        if(application.status=="APPROVED"){
                        application.selectedDefault=selectedDefault;
                        appsList.push(application);
                        }
                    }
            result = apistore.getDeniedTiers();
            deniedTiers = result.tiers;
            var k,m,allowedTiers=[],denied = false, tiersAvailable = false;
                    if(tiers!=null){
                    var tiersVal=tiers.split(",");
                    for(var m=0;m<tiersVal.length;m++){
                       if(deniedTiers!=null){
                       var deniedTiersVal=tiers.split(",");
                       for (var k=0;k<deniedTiersVal.length;k++) {
                       if (tiersVal[m].tierName == deniedTiersVal[k].tierName) {
                            denied = true;
                       }
                       }
                       }
                       if (!denied) {
                       allowedTiers.push(tiersVal[m]);
                       tiersAvailable = true;
                    }
                    denied = false;
                    }
                    }

    page.applications= appsList;
    page.tiersAvailable=tiersAvailable;
    page.tiers=allowedTiers;
    page.subscribedToDefault=subscribedToDefault;
    page.showSubscribe=showSubscribe;
    page.api=apidata;
    page.status=status;

    //=================== Getting subscription details ========================
        },
        pageDecorators: {
            populateEndPoints : function(page){
                if (page.assets && page.assets.id) {
                    var httpEndpoint = page.api.serverURL.split(",")[1];
                    var httpsEndpoint = page.api.serverURL.split(",")[2];

                    page.assets.httpEndpoint = httpEndpoint;
                    page.assets.httpsEndpoint = httpsEndpoint;

                    //var prodEps = parse(page.assets.attributes.overview_endpointConfig).production_endpoints;
                    //var sandBoxEps = parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints;
                    //
                    //if(prodEps != null){
                    //    var prodEpArry = [];
                    //
                    //    for(var i = 0; prodEps.length > i; i++){
                    //        prodEpArry.push(prodEps[i].url);
                    //    }
                    //
                    //    page.assets.production_endpoint = prodEpArry.join(',');
                    //
                    //}else {
                    //    page.assets.production_endpoint = parse(page.assets.attributes.overview_endpointConfig).production_endpoints.url;
                    //}
                    //
                    //if(sandBoxEps != null){
                    //    var sandBoxEpArry = [];
                    //
                    //    for(var i = 0; prodEps.length > i; i++){
                    //        sandBoxEpArry.push(sandBoxEps[i].url);
                    //    }
                    //
                    //    page.assets.sandbox_endpoint = sandBoxEpArry.join(',');
                    //
                    //} else if(parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints != null && parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints.url != null) {
                    //    page.assets.sandbox_endpoint = parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints.url;
                    //}
                }
            },
            socialSitePopulator: function(page, meta) {
                var utils = require('utils');
                //If the
                if (!utils.reflection.isArray(page.assets || [])) {
                    var asset = page.assets;
                    var assetUrl = this.buildAssetPageUrl('api', '/details/' + asset.id);
                    page.social_sites = {};
                    var facebook = page.social_sites.facebook = {};
                    var gplus = page.social_sites.gplus = {};
                    var twitter = page.social_sites.twitter = {};
                    var diggit = page.social_sites.diggit = {};
                    facebook.href = facebookLink(assetUrl, asset);
                    gplus.href = gplusLink(assetUrl, asset);
                    twitter.href = twitterLink(assetUrl, asset);
                    diggit.href = diggitLink(assetUrl, asset);
                }
            },
            embedLinkPopulator: function(page, meta) {
                var utils = require('utils');
                if (!utils.reflection.isArray(page.assets || [])) {
                    var asset = page.assets;
                    var attributes = asset.attributes || {};
                    var widgetLink = '/apis/widget';
                    var assetUrl = widgetLink+'?name=' + asset.name + '&version=' + attributes.overview_version + '&provider=' + attributes.overview_provider;
                    page.api_embed_links = '<iframe width="450" height="120" src="' + assetUrl + '" frameborder="0" allowfullscreen></iframe>';
                }
            }
        }
    };
}
<commit_msg>Fixing store browsing issue<commit_after>/*
 *  Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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.
 *
 */
asset.manager = function(ctx){

    var buildPublishedQuery = function(query) {
        query = query || {};
        var isLCEnabled = ctx.rxtManager.isLifecycleEnabled(ctx.assetType);
        //If lifecycles are not enabled then do nothing
        if(!isLCEnabled){
            log.warn('lifecycles disabled,not adding published states to search query');
            return query;
        }
        //Get all of the published assets
        var publishedStates = ctx.rxtManager.getPublishedStates(ctx.assetType) || [];
        //Determine if there are any published states
        if (publishedStates.length == 0) {
            return query;
        }
        //TODO: Even though an array is sent in only the first search value is accepted
        query.lcState=[publishedStates[0]];
        return query;
    };

    return {
    search: function(query, paging) {
        query=buildPublishedQuery(query);
        var assets = this._super.search.call(this, query, paging);
        return assets;
    },
    get:function(id){
       // log.info('Calling custom get of asset');
       return this._super.get.call(this,id);
    }
        /*search : function(query, paging) {
            var carbonAPI = require('carbon');
            var tenantDomain = carbonAPI.server.tenantDomain({
                tenantId : ctx.tenantId
            });
            var server = require('store').server;
            var user = server.current(ctx.session);
            var userName = '__wso2.am.anon__';

            if (user != null) {
                userName = user.username;
            }
            log.info('============== user name ==========' + userName);
            var apistore = require('apistore').apistore.instance(userName);
            var assetApi = apistore.getAllPaginatedAPIsByStatus(tenantDomain,
                    0, 100, '');
            log.info('This is the custom APIM search method');
            log.info('============== api json  ==========' + assetApi);
            // return assetApi;

            var json = JSON.parse(assetApi);

            var apisArray = [];
            if (json.apis.length > 0) {
                apisArray = json.apis;
            }
            log.info('============== array ===========' + apisArray);
            return apisArray;
        }*/
    };
};

asset.configure = function(ctx) {
    return {
        meta: {
            search: {
                searchableFields: ['overview_provider', 'overview_name']
            }
        }
    }
};
asset.server = function(ctx) {
    return {
         onUserLoggedIn : function(){
            var userName=ctx.username;
            var apistore = require('apistore').apistore.instance(userName);
            var subscriber=apistore.getSubscriber(userName);
            if(!subscriber){
            apistore.addSubscriber(userName,ctx.tenantId);
            }
        },
        endpoints: {
            pages: [{
                title: 'Prototyped APIs',
                url: 'prototyped_apis',
                path: 'prototyped_apis.jag'
            }, {
                title: 'My Applications',
                url: 'my_applications',
                path: 'my_applications.jag',
                secured: true
            },/** {
            title: 'API Details'
            url: 'details'
            path: 'details.jag'
            },*/
            {
                title: 'My Subscriptions',
                url: 'my_subscriptions',
                path: 'my_subscriptions.jag',
                secured: true
            }, {
                title: 'Forum',
                url: 'forum',
                path: 'forum.jag'
            }, {
                title: 'Statistics',
                url: 'statistics',
                path: 'statistics.jag'
            }]
        }
    };
};
asset.renderer = function(ctx) {
    var twitterLink = function(assetUrl, asset) {
        var attr = asset.attributes || {};
        var description = attr.overview_description ? attr.overview_description : 'No description';
        return 'https://twitter.com/share?text=' + description + '&url=' + assetUrl;
    };
    var facebookLink = function(assetUrl, asset) {
        return 'https://facebook.com/sharer.php?u=' + assetUrl;
    };
    var gplusLink = function(assetUrl, asset) {
        return 'https://plus.google.com/share?url=' + assetUrl;
    };
    var diggitLink = function(assetUrl, asset) {
        return 'https://digg.com/submit?url=' + assetUrl;
    };
    return {
        details:function(page){
            // log.info('Details page rendered!!!');

    //=================== Getting subscription details ========================

    var carbonAPI = require('carbon');
    var tenantDomain = carbonAPI.server.tenantDomain({
        tenantId : ctx.tenantId
    });
    var server = require('store').server;
    var user = server.current(ctx.session);
    var userName = '__wso2.am.anon__';

    if (user != null) {
        userName = user.username;
    }
    var lenI,lenJ,i,j,result,apidata,deniedTiers,tiers,appsList=[],subscribedToDefault=false,showSubscribe=false,status,selectedDefault=false;
    var apistore = require('apistore').apistore.instance(userName);
    var applications = JSON.parse(apistore.getApplications(userName));
    var asset = page.assets;
    if(asset!=null){
    status=asset.lifecycleState;
    if(status!=null){
    status=status.toUpperCase()
    }
    }
    var resultapi=apistore.getAPI(asset.attributes.overview_provider,asset.name,asset.attributes.overview_version);
    var apidata=JSON.parse(resultapi);
    if(apidata!=null){
    tiers=apidata.tiers;
    if(status=="PUBLISHED"){
    showSubscribe=true;
    }
    }
    var subscriptions=JSON.parse(apistore.getAPISubscriptions(asset.attributes.overview_provider,asset.name,asset.attributes.overview_version,userName));
    if(applications!=null){
                lenI = applications.length;
            }if(subscriptions!=null){
                lenJ = subscriptions.length;
            }
            Label1:
                    for (i = 0; i < lenI; i++) {
                        var application = applications[i];
                        for (j = 0; j < lenJ; j++) {
                            var subscription = subscriptions[j];
                            if (subscription.applicationId == application.id) {
                                if(application.name=="DefaultApplication"){
                                   selectedDefault=true;
                                }
                                continue Label1;
                            }else{
                              if(application.name=="DefaultApplication"){
                                   subscribedToDefault=true;
                                }
                            }
                        }

                        if(application.status=="APPROVED"){
                        application.selectedDefault=selectedDefault;
                        appsList.push(application);
                        }
                    }
            result = apistore.getDeniedTiers();
            deniedTiers = result.tiers;
            var k,m,allowedTiers=[],denied = false, tiersAvailable = false;
                    if(tiers!=null){
                    var tiersVal=tiers.split(",");
                    for(var m=0;m<tiersVal.length;m++){
                       if(deniedTiers!=null){
                       var deniedTiersVal=tiers.split(",");
                       for (var k=0;k<deniedTiersVal.length;k++) {
                       if (tiersVal[m].tierName == deniedTiersVal[k].tierName) {
                            denied = true;
                       }
                       }
                       }
                       if (!denied) {
                       allowedTiers.push(tiersVal[m]);
                       tiersAvailable = true;
                    }
                    denied = false;
                    }
                    }

    page.applications= appsList;
    page.tiersAvailable=tiersAvailable;
    page.tiers=allowedTiers;
    page.subscribedToDefault=subscribedToDefault;
    page.showSubscribe=showSubscribe;
    page.api=apidata;
    page.status=status;

    //=================== Getting subscription details ========================
        },
        pageDecorators: {
            populateEndPoints : function(page){
                if (page.assets && page.assets.id) {
                    var httpEndpoint = page.api.serverURL.split(",")[1];
                    var httpsEndpoint = page.api.serverURL.split(",")[2];

                    page.assets.httpEndpoint = httpEndpoint;
                    page.assets.httpsEndpoint = httpsEndpoint;

                    //var prodEps = parse(page.assets.attributes.overview_endpointConfig).production_endpoints;
                    //var sandBoxEps = parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints;
                    //
                    //if(prodEps != null){
                    //    var prodEpArry = [];
                    //
                    //    for(var i = 0; prodEps.length > i; i++){
                    //        prodEpArry.push(prodEps[i].url);
                    //    }
                    //
                    //    page.assets.production_endpoint = prodEpArry.join(',');
                    //
                    //}else {
                    //    page.assets.production_endpoint = parse(page.assets.attributes.overview_endpointConfig).production_endpoints.url;
                    //}
                    //
                    //if(sandBoxEps != null){
                    //    var sandBoxEpArry = [];
                    //
                    //    for(var i = 0; prodEps.length > i; i++){
                    //        sandBoxEpArry.push(sandBoxEps[i].url);
                    //    }
                    //
                    //    page.assets.sandbox_endpoint = sandBoxEpArry.join(',');
                    //
                    //} else if(parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints != null && parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints.url != null) {
                    //    page.assets.sandbox_endpoint = parse(page.assets.attributes.overview_endpointConfig).sandbox_endpoints.url;
                    //}
                }
            },
            socialSitePopulator: function(page, meta) {
                var utils = require('utils');
                //If the
                if (!utils.reflection.isArray(page.assets || [])) {
                    var asset = page.assets;
                    var assetUrl = this.buildAssetPageUrl('api', '/details/' + asset.id);
                    page.social_sites = {};
                    var facebook = page.social_sites.facebook = {};
                    var gplus = page.social_sites.gplus = {};
                    var twitter = page.social_sites.twitter = {};
                    var diggit = page.social_sites.diggit = {};
                    facebook.href = facebookLink(assetUrl, asset);
                    gplus.href = gplusLink(assetUrl, asset);
                    twitter.href = twitterLink(assetUrl, asset);
                    diggit.href = diggitLink(assetUrl, asset);
                }
            },
            embedLinkPopulator: function(page, meta) {
                var utils = require('utils');
                if (!utils.reflection.isArray(page.assets || [])) {
                    var asset = page.assets;
                    var attributes = asset.attributes || {};
                    var widgetLink = '/apis/widget';
                    var assetUrl = widgetLink+'?name=' + asset.name + '&version=' + attributes.overview_version + '&provider=' + attributes.overview_provider;
                    page.api_embed_links = '<iframe width="450" height="120" src="' + assetUrl + '" frameborder="0" allowfullscreen></iframe>';
                }
            }
        }
    };
}
<|endoftext|>


<commit_before>var exec = require( "cordova/exec" );

var exists;
module.exports = {
    exists: function ( callback ) {
        if ( typeof exists == "undefined" ) {
            try {
                exec(function () {
                    exists = true;
                    callback( exists );
                }, function () {
                    exists = false;
                    callback( exists );
                }, "imagepicker", "exists" );
            } catch ( err ) {
                exists = false;
                callback( exists );
            }
        }
    },

    pick: function ( success, error ) {
        module.exports.exists(function ( exists ) {
            if ( !exists ) {
                return error( "imagepicker is only available on iOS" );
            }
            exec(function ( results ) {
                success( results.map(function ( url ) {
                    return { localURL: url };
                }));
            }, error, "imagepicker", "pick" );
        })
    }
}<commit_msg>Fixes a bug where exists didn't call the callback<commit_after>var exec = require( "cordova/exec" );

var exists;
var imagepicker = module.exports = {
    exists: function ( callback ) {
        if ( typeof exists == "undefined" ) {
            try {
                exec(function () {
                    exists = true;
                    callback( exists );
                }, function () {
                    exists = false;
                    callback( exists );
                }, "imagepicker", "exists" );
            } catch ( err ) {
                exists = false;
                callback( exists );
            }
        } else {
            callback( exists );
        }
    },

    pick: function ( success, error ) {
        imagepicker.exists(function ( exists ) {
            if ( !exists ) {
                return error( "imagepicker is only available on iOS" );
            }
            exec(function ( results ) {
                success( results.map(function ( url ) {
                    return { localURL: url };
                }));
            }, error, "imagepicker", "pick" );
        })
    }
}<|endoftext|>


<commit_before>#!/usr/bin/env node

/* global Config, Log */
'use strict';
const fs = require('fs');
const S3rver = require('s3rver');
const os = require('os');
const Path = require('path');
const AWS = require('aws-sdk');
const rmdir = require('rimraf');
const walk = require('walk');
const chokidar = require('chokidar');
const nconf = require('nconf');

const DEFAULT_HTTP_PORT = 4569;

const args = require('yargs')
  .usage('Usage: $0 [args]')
  .option('d', {
    alias: 'data',
    describe: 'Load data directory from filesystem',
    type: 'string',
    required: true
  })
  .option('h', {
    alias: 'hostname',
    describe: 'The hostname or ip for the server',
    default: '127.0.0.1',
    type: 'string'
  })
  .option('p', {
    alias: 'port',
    describe: 'The port number of the http server',
    default: DEFAULT_HTTP_PORT,
    type: 'number'
  })
  .help('help')
  .argv;

const hostname = args.h;
const port = args.p;
const path = Path.resolve(__dirname, `../../${args.d}`);

nconf.set('log:level', 'debug');
const Log = require('../../lib/logger').attach(nconf);

const awsConfig = {
  s3ForcePathStyle: true,
  endpoint: new AWS.Endpoint(`http://${hostname}:${port}`)
};
const awsClient = new AWS.S3(awsConfig);

function getBucketName(p) {
  return `propsd-${Path.basename(p)}`;
}

/**
 * Removes the "bucket" created in the temp folder
 */
function cleanupTempDir() {
  const tmpDirBucketPath = Path.resolve(os.tmpdir(), `propsd-temp-s3-server`);

  try {
    rmdir.sync(tmpDirBucketPath);
  } catch (err) {
    throw err;
  }
}

/**
 * Creates a directory for the "bucket" in the temp folder
 * @returns {String}
 */
function createTempDirForBucket() {
  const tmpDirBucketPath = Path.resolve(os.tmpdir(), `propsd-temp-s3-server`);

  try {
    cleanupTempDir();
    fs.mkdirSync(tmpDirBucketPath);
  } catch (err) {
    throw err;
  }
  return tmpDirBucketPath;
}

/**
 * Copies files from the watched folder the the "bucket"
 * @param {String} bucket
 */
function copyFiles(bucket) {
  const walker = walk.walk(path);

  walker.on('file', (root, fileStats, next) => {
    const pathToFile = `${Path.relative(path, root)}/${fileStats.name}`;
    const stream = fs.createReadStream(Path.join(path, pathToFile));

    awsClient.putObject({
      Bucket: bucket,
      Key: pathToFile,
      Body: stream
    }, (err) => {
      if (err) {
        Log.info(err, err.stack);
      }
      next();
    });
  });
}

/**
 * Deletes the contents of the "bucket"
 * @param {String} bucket
 */
function emptyBucket(bucket) {
  awsClient.listObjects({Bucket: bucket}, (listErr, listData) => {
    if (listErr) {
      throw listErr;
    }
    listData.Contents.forEach((el) => {
      awsClient.deleteObject({Bucket: bucket, Key: el.Key}, (deleteErr) => {
        if (deleteErr) {
          throw deleteErr;
        }
      });
    });
  });
}

/**
 * Handles cleanup on app exit
 */
function exitHandler(err) {
  let code = 0;

  if (err) {
    Log.error(err);
    code = 1;
  }
  Log.info('Cleaning up temp directory');
  cleanupTempDir();
  process.exit(code);
}

function createBucket(bucket) {
  awsClient.createBucket({Bucket: bucket}, (createBucketErr) => {
    if (createBucketErr) {
      throw createBucketErr;
    }
    copyFiles(bucket);

    chokidar.watch(path, {persistent: true}).on('change', () => {
      emptyBucket(bucket);
      copyFiles(bucket);
    });
  });
}

function init() {
  fs.stat(path, (err, stats) => {
    if (err || !stats.isDirectory()) {
      process.exit(1);
    }
    const bucket = getBucketName(path);
    const tmpDir = createTempDirForBucket();

    const client = new S3rver({
      port,
      hostname,
      silent: false,
      directory: tmpDir
    });

    client.run((serverErr, host, p) => {
      if (serverErr) {
        process.exit(1);
      }

      createBucket(bucket);

      Log.info(`listening for S3 requests at http://${host}:${p}`);
    });
  });
}

init();

// do something when app is closing
process.on('exit', exitHandler);

// catches ctrl+c event
process.on('SIGINT', exitHandler);

// catches uncaught exceptions
process.on('uncaughtException', (err) => {
  exitHandler(err);
});
<commit_msg>Added some better logging around errors that can occur.<commit_after>#!/usr/bin/env node

/* global Config, Log */
'use strict';
const fs = require('fs');
const S3rver = require('s3rver');
const os = require('os');
const Path = require('path');
const AWS = require('aws-sdk');
const rmdir = require('rimraf');
const walk = require('walk');
const chokidar = require('chokidar');
const nconf = require('nconf');

const DEFAULT_HTTP_PORT = 4569;

const args = require('yargs')
  .usage('Usage: $0 [args]')
  .option('d', {
    alias: 'data',
    describe: 'Load data directory from filesystem',
    type: 'string',
    required: true
  })
  .option('h', {
    alias: 'hostname',
    describe: 'The hostname or ip for the server',
    default: '127.0.0.1',
    type: 'string'
  })
  .option('p', {
    alias: 'port',
    describe: 'The port number of the http server',
    default: DEFAULT_HTTP_PORT,
    type: 'number'
  })
  .help('help')
  .argv;

const hostname = args.h;
const port = args.p;
const path = Path.resolve(__dirname, `../../${args.d}`);

nconf.set('log:level', 'debug');
const Log = require('../../lib/logger').attach(nconf);

const awsConfig = {
  s3ForcePathStyle: true,
  endpoint: new AWS.Endpoint(`http://${hostname}:${port}`)
};
const awsClient = new AWS.S3(awsConfig);

function getBucketName(p) {
  return `propsd-${Path.basename(p)}`;
}

/**
 * Removes the "bucket" created in the temp folder
 */
function cleanupTempDir() {
  const tmpDirBucketPath = Path.resolve(os.tmpdir(), `propsd-temp-s3-server`);

  try {
    rmdir.sync(tmpDirBucketPath);
  } catch (err) {
    throw err;
  }
}

/**
 * Creates a directory for the "bucket" in the temp folder
 * @returns {String}
 */
function createTempDirForBucket() {
  const tmpDirBucketPath = Path.resolve(os.tmpdir(), `propsd-temp-s3-server`);

  try {
    cleanupTempDir();
    fs.mkdirSync(tmpDirBucketPath);
  } catch (err) {
    throw err;
  }
  return tmpDirBucketPath;
}

/**
 * Copies files from the watched folder the the "bucket"
 * @param {String} bucket
 */
function copyFiles(bucket) {
  const walker = walk.walk(path);

  walker.on('file', (root, fileStats, next) => {
    const pathToFile = `${Path.relative(path, root)}/${fileStats.name}`;
    const stream = fs.createReadStream(Path.join(path, pathToFile));

    awsClient.putObject({
      Bucket: bucket,
      Key: pathToFile,
      Body: stream
    }, (err) => {
      if (err) {
        Log.info(err, err.stack);
      }
      next();
    });
  });
}

/**
 * Deletes the contents of the "bucket"
 * @param {String} bucket
 */
function emptyBucket(bucket) {
  awsClient.listObjects({Bucket: bucket}, (listErr, listData) => {
    if (listErr) {
      throw listErr;
    }
    listData.Contents.forEach((el) => {
      awsClient.deleteObject({Bucket: bucket, Key: el.Key}, (deleteErr) => {
        if (deleteErr) {
          throw deleteErr;
        }
      });
    });
  });
}

/**
 * Handles cleanup on app exit
 */
function exitHandler(err) {
  let code = 0;

  if (err) {
    Log.error(err);
    code = 1;
  }
  Log.info('Cleaning up temp directory');
  cleanupTempDir();
  process.exit(code);
}

function createBucket(bucket) {
  awsClient.createBucket({Bucket: bucket}, (createBucketErr) => {
    if (createBucketErr) {
      throw createBucketErr;
    }
    copyFiles(bucket);

    chokidar.watch(path, {persistent: true}).on('change', () => {
      emptyBucket(bucket);
      copyFiles(bucket);
    });
  });
}

function init() {
  fs.stat(path, (err, stats) => {
    if (err || !stats.isDirectory()) {
      Log.error('Path is not a directory');
      process.exit(1);
    }
    const bucket = getBucketName(path);
    const tmpDir = createTempDirForBucket();

    const client = new S3rver({
      port,
      hostname,
      silent: false,
      directory: tmpDir
    });

    client.run((serverErr, host, p) => {
      if (serverErr) {
        Log.error(serverErr);
        process.exit(1);
      }

      createBucket(bucket);

      Log.info(`listening for S3 requests at http://${host}:${p}`);
    });
  });
}

init();

// do something when app is closing
process.on('exit', exitHandler);

// catches ctrl+c event
process.on('SIGINT', exitHandler);

// catches uncaught exceptions
process.on('uncaughtException', (err) => {
  exitHandler(err);
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */
CLASS({
  package: 'foam.browser',
  name: 'BrowserConfig',
  documentation: 'Configuration object for FOAM\'s browser. ' +
      'Create an instance of this model and load it in a BrowserView.',

  requires: [
    'foam.dao.EasyDAO',
    'foam.dao.NullDAO',
    'foam.mlang.CannedQuery',
    'foam.ui.DAOListView',
    'foam.ui.md.CannedQueryCitationView',
    'foam.ui.md.CitationView',
  ],

  exports: [
    'dao'
  ],

  properties: [
    {
      name: 'title',
      defaultValueFn: function() {
        return this.dao.model.name + ' Browser';
      }
    },
    {
      model_: 'foam.core.types.DAOProperty',
      name: 'dao',
      documentation: 'The master DAO to browse. Will default to an MDAO if not provided.',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.model,
          daoType: 'MDAO',
          seqNo: true,
        });
      }
    },
    {
      model_: 'ModelProperty',
      name: 'model',
      defaultValueFn: function() { return this.dao.model; }
    },
    {
      name: 'filteredDAO',
      documentation: 'The filtered version of $$DOC{ref:".dao"} that\'s ' +
          'being viewed right now.',
      dynamicValue: function() {
        this.dao; this.query;
        var query = this.query || (this.showAllWithNoQuery ? TRUE : FALSE);
        return this.dao.where(this.query);
      },
      view: 'foam.ui.DAOListView',
    },
    {
      name: 'query',
      documentation: 'The authoritative mlang query. Formed from $$DOC{ref:".search"} ' +
          'AND $$DOC{ref:".cannedQuery"}. Dynamic value, should not need to ' +
          'be set directly.',
      dynamicValue: function() {
        // TODO(braden): Is AND the right semantics here? Maybe searches should
        // span the whole DAO without limiting by canned query.
        this.search; this.cannedQuery; this.searchWithinCannedQuery;
        var canned = this.cannedQuery.expression;
        if (this.search !== '') {
          var query = this.queryParser(this.search);
          return this.searchWithinCannedQuery ? AND(query, canned) : query;
        } else {
          return canned;
        }
      },
    },
    {
      name: 'queryParser',
      documentation: 'Parser for user-entered search strings. Should return ' +
          'an mlang suitable for searching $$DOC{ref:".dao"}. Defaults to KEYWORD.',
      defaultValue: KEYWORD,
    },
    {
      model_: 'foam.core.types.DAOProperty',
      name: 'cannedQueryDAO',
      documentation: 'A DAO of $$DOC{ref:"foam.mlang.CannedQuery"} objects.',
      required: true,
      postSet: function(old, nu) {
        if (nu && !this.query) {
          nu.limit(1).select([])(function(arr) { this.cannedQuery = arr[0]; }.bind(this));
        }
      },
      view: {
        factory_: 'foam.ui.DAOListView',
        rowView: 'foam.ui.md.CannedQueryCitationView',
      },
    },
    {
      name: 'cannedQuery',
      documentation: 'Currently selected canned query. Combined into $$DOC{ref:".query"} ' +
          'by ANDing its expression with $$DOC{ref:".search"}.',
    },
    {
      model_: 'StringProperty',
      name: 'search',
      view: {
        factory_: 'foam.ui.md.TextFieldView',
        label: 'Search',
        floatingLabel: false,
        placeholder: 'Search',
        onKeyMode: true,
      },
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'listView',
      defaultValue: {
        factory_: 'foam.ui.DAOListView',
        rowView: 'foam.ui.md.CitationView'
      }
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'detailView',
      documentation: 'A ViewFactory for the main detail view. You usually ' +
          'will want to override $$DOC{ref:".innerDetailView"} rather than ' +
          'this property.',
      defaultValue: 'foam.ui.md.UpdateDetailView',
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'innerDetailView',
      defaultValue: 'foam.ui.md.DetailView'
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'createView',
      documentation: 'The view for creating a new item. Defaults to creating ' +
          'a new empty instance of $$DOC{ref:".model"} and passing it to ' +
          '$$DOC{ref:".detailView"}.',
      defaultValue: function(args, X) {
        var newObj = this.model.create();
        return this.detailView({
          data: newObj,
          innerView: this.innerDetailView
        }, X);
      }
    },
    {
      name: 'menuFactory',
      documentation: 'The menuFactory returns a View for the left-side menu. ' +
          'By default, it returns the view for $$DOC{ref:".cannedQueryDAO"}.',
      defaultValue: function() {
        var view = this.DAOListView.create({
          data$: this.cannedQueryDAO$,
          rowView: 'foam.ui.md.CannedQueryCitationView',
        }, this.Y.sub({
          selection$: this.cannedQuery$
        }));

        // Listen for the DAOListView's ROW_CLICK event, and emit our own
        // MENU_CLOSE event when it fires. This was originally a postSet on
        // cannedQuery, which is bound to the selection, but that only updates
        // when the value actually changes.
        view.subscribe(view.ROW_CLICK, function() {
          this.publish(this.MENU_CLOSE);
        }.bind(this));

        return view;
      }
    },
    {
      name: 'busyStatus',
      documentation: 'Optional busyStatus. If defined, a spinner will be ' +
          'shown when the BusyStatus is set to true.'
    },
    {
      name: 'showAllWithNoQuery',
      documentation: 'When there is no query set, should the browser ' +
          'render all the data, or none of it? Defaults to all, set false if ' +
          'you have a lot of data and don\'t want to render it all.',
      defaultValue: true,
    },
    {
      model_: 'BooleanProperty',
      name: 'showAdd',
      documentation: 'An action to create a new value in $$DOC{ref:".dao"} ' +
          'will be rendered when this is set to true (the default).',
      defaultValue: true,
    },
    {
      model_: 'BooleanProperty',
      name: 'searchWithinCannedQuery',
      documentation: 'By default, searches are against all of the DAO. To ' +
          'restrict searches to the currently selected canned query, set ' +
          'this to true.',
      defaultValue: false,
    },
  ],

  constants: {
    MENU_CLOSE: ['menu-close']
  },
});
<commit_msg>Browser: More robust default for title property<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */
CLASS({
  package: 'foam.browser',
  name: 'BrowserConfig',
  documentation: 'Configuration object for FOAM\'s browser. ' +
      'Create an instance of this model and load it in a BrowserView.',

  requires: [
    'foam.dao.EasyDAO',
    'foam.dao.NullDAO',
    'foam.mlang.CannedQuery',
    'foam.ui.DAOListView',
    'foam.ui.md.CannedQueryCitationView',
    'foam.ui.md.CitationView',
  ],

  exports: [
    'dao'
  ],

  properties: [
    {
      name: 'title',
      defaultValueFn: function() {
        return this.model && this.model.name ? this.model.name + ' Browser' :
            'Browser';
      }
    },
    {
      model_: 'foam.core.types.DAOProperty',
      name: 'dao',
      documentation: 'The master DAO to browse. Will default to an MDAO if not provided.',
      lazyFactory: function() {
        return this.EasyDAO.create({
          model: this.model,
          daoType: 'MDAO',
          seqNo: true,
        });
      }
    },
    {
      model_: 'ModelProperty',
      name: 'model',
      required: true,
      factory: function() { return this.dao.model; }
    },
    {
      name: 'filteredDAO',
      documentation: 'The filtered version of $$DOC{ref:".dao"} that\'s ' +
          'being viewed right now.',
      dynamicValue: function() {
        this.dao; this.query;
        var query = this.query || (this.showAllWithNoQuery ? TRUE : FALSE);
        return this.dao.where(this.query);
      },
      view: 'foam.ui.DAOListView',
    },
    {
      name: 'query',
      documentation: 'The authoritative mlang query. Formed from $$DOC{ref:".search"} ' +
          'AND $$DOC{ref:".cannedQuery"}. Dynamic value, should not need to ' +
          'be set directly.',
      dynamicValue: function() {
        // TODO(braden): Is AND the right semantics here? Maybe searches should
        // span the whole DAO without limiting by canned query.
        this.search; this.cannedQuery; this.searchWithinCannedQuery;
        var canned = this.cannedQuery.expression;
        if (this.search !== '') {
          var query = this.queryParser(this.search);
          return this.searchWithinCannedQuery ? AND(query, canned) : query;
        } else {
          return canned;
        }
      },
    },
    {
      name: 'queryParser',
      documentation: 'Parser for user-entered search strings. Should return ' +
          'an mlang suitable for searching $$DOC{ref:".dao"}. Defaults to KEYWORD.',
      defaultValue: KEYWORD,
    },
    {
      model_: 'foam.core.types.DAOProperty',
      name: 'cannedQueryDAO',
      documentation: 'A DAO of $$DOC{ref:"foam.mlang.CannedQuery"} objects.',
      required: true,
      postSet: function(old, nu) {
        if (nu && !this.query) {
          nu.limit(1).select([])(function(arr) { this.cannedQuery = arr[0]; }.bind(this));
        }
      },
      view: {
        factory_: 'foam.ui.DAOListView',
        rowView: 'foam.ui.md.CannedQueryCitationView',
      },
    },
    {
      name: 'cannedQuery',
      documentation: 'Currently selected canned query. Combined into $$DOC{ref:".query"} ' +
          'by ANDing its expression with $$DOC{ref:".search"}.',
    },
    {
      model_: 'StringProperty',
      name: 'search',
      view: {
        factory_: 'foam.ui.md.TextFieldView',
        label: 'Search',
        floatingLabel: false,
        placeholder: 'Search',
        onKeyMode: true,
      },
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'listView',
      defaultValue: {
        factory_: 'foam.ui.DAOListView',
        rowView: 'foam.ui.md.CitationView'
      }
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'detailView',
      documentation: 'A ViewFactory for the main detail view. You usually ' +
          'will want to override $$DOC{ref:".innerDetailView"} rather than ' +
          'this property.',
      defaultValue: 'foam.ui.md.UpdateDetailView',
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'innerDetailView',
      defaultValue: 'foam.ui.md.DetailView'
    },
    {
      model_: 'ViewFactoryProperty',
      name: 'createView',
      documentation: 'The view for creating a new item. Defaults to creating ' +
          'a new empty instance of $$DOC{ref:".model"} and passing it to ' +
          '$$DOC{ref:".detailView"}.',
      defaultValue: function(args, X) {
        var newObj = this.model.create();
        return this.detailView({
          data: newObj,
          innerView: this.innerDetailView
        }, X);
      }
    },
    {
      name: 'menuFactory',
      documentation: 'The menuFactory returns a View for the left-side menu. ' +
          'By default, it returns the view for $$DOC{ref:".cannedQueryDAO"}.',
      defaultValue: function() {
        var view = this.DAOListView.create({
          data$: this.cannedQueryDAO$,
          rowView: 'foam.ui.md.CannedQueryCitationView',
        }, this.Y.sub({
          selection$: this.cannedQuery$
        }));

        // Listen for the DAOListView's ROW_CLICK event, and emit our own
        // MENU_CLOSE event when it fires. This was originally a postSet on
        // cannedQuery, which is bound to the selection, but that only updates
        // when the value actually changes.
        view.subscribe(view.ROW_CLICK, function() {
          this.publish(this.MENU_CLOSE);
        }.bind(this));

        return view;
      }
    },
    {
      name: 'busyStatus',
      documentation: 'Optional busyStatus. If defined, a spinner will be ' +
          'shown when the BusyStatus is set to true.'
    },
    {
      name: 'showAllWithNoQuery',
      documentation: 'When there is no query set, should the browser ' +
          'render all the data, or none of it? Defaults to all, set false if ' +
          'you have a lot of data and don\'t want to render it all.',
      defaultValue: true,
    },
    {
      model_: 'BooleanProperty',
      name: 'showAdd',
      documentation: 'An action to create a new value in $$DOC{ref:".dao"} ' +
          'will be rendered when this is set to true (the default).',
      defaultValue: true,
    },
    {
      model_: 'BooleanProperty',
      name: 'searchWithinCannedQuery',
      documentation: 'By default, searches are against all of the DAO. To ' +
          'restrict searches to the currently selected canned query, set ' +
          'this to true.',
      defaultValue: false,
    },
  ],

  constants: {
    MENU_CLOSE: ['menu-close']
  },
});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013-2014, Paul Fischer, Intel Corporation. All rights reserved.
 * Please see http://software.intel.com/html5/license/samples for license terms and conditions.
 */

/*
 * The functions in this file are designed to reliably detect various "ready" events
 * within a variety of containers (Intel XDK "legacy" container, Cordova 3.x container,
 * standard browser, App Preview, Crosswalk, etc.). It "unifies" the commonly used
 * ready events and is very helpful for moving a "web app" to a "hybrid app" scenario.
 *
 * This file has no dependencies. It will generate a custom "app.Ready" event
 * that you should use once to start your application, rather than waiting on a
 * "device ready" or "document ready" or "window load" or similar events.
 *
 * You should not have to modify anything in this file to use it. See the example
 * index.html file that accompanies this file (in its sample repo location) for
 * recommendations on the best placement of this file relative to other files and
 * for recommendations regarding the loading of other JavaScript files.
 *
 * There are a large number of console.log messages contained within this file.
 * They can be used to debug initialization problems and understand how it works.
 * It is highly recommended that you leave them in your app, they will not unduly
 * slow down or burden your application.
 *
 * There are many comments in this file and the accompanying index.html file.
 * Please read the comments within for details and further documentation.
 *
 * BTW: "dev" means "device" in this context, not "develop," because it grew out
 * a desire to build a more reliable and flexible "device ready" detector.
 */

/*jslint browser:true, devel:true, white:true, vars:true */
/*global $:false, intel:false, dev:false, performance:false */

window.dev = window.dev || {} ;         // there should only be one of these, but...

// Use performance counter if it is available, otherwise, use milliseconds since 1970

if( window.performance && performance.now ) {
    dev.timeStamp = function() { return performance.now().toFixed(3) ; } ;
} else {
    dev.timeStart = Date.now() ;        // feeble zero ref for relative time in ms
    dev.timeStamp = function() { return (Date.now() - dev.timeStart) ; } ;
}

// Defines some delays constants used throughout for ready detections.
// Each should be smaller than the next; most cases should work as is.
// Lowering dev.BROWSER will speed up detection of browser scenario...
// ...at expense of possible false detects of browser environment...
// ...probably okay to go as low as 3000ms, depends on external libraries, etc.

dev.INSURANCE = 250 ;                   // ms, insurance on registering ready events detected
dev.WINDOW_LOAD = 500 ;                 // ms, for combating premature window load events
dev.BROWSER = 7000 ;                    // ms, detecting in a browser (probably best at >5 seconds)
dev.FAIL_SAFE = 10000 ;                 // ms, if all else fails, this saves our bacon :-)

// Used to keep track of time when each of these items was triggered.
// Sorry for the weird names in the isDeviceReady structure, it's done for
// easier debugging and comparison of numbers when displayed in console.log messages.

dev.isDeviceReady = {                   // listed in approximate order expected
    a_startTime______:dev.timeStamp(),  // when we started execution of this module
    b_fnDocumentReady:false,            // detected document.readyState == "complete"
    c_cordova_ready__:false,            // detected cordova device ready event
    d_xdk_ready______:false,            // detected xdk device ready event
    e_fnDeviceReady__:false,            // entered onDeviceReady()
    f_browser_ready__:false             // detected browser container
} ;

// Where the device ready event ultimately ends up, regardless of environment.
// Runs after underlying device native code and browser is initialized.
// Usually not much needed here, just additional "device init" code.
// See initDeviceReady() below for code that kicks off this function.
// This function works with Cordova and XDK webview or in a browser.

// NOTE: Customize this function, if necessary, for low-level init of your app.
// NOTE: In most cases, you can leave this code alone and use it as is.

dev.onDeviceReady = function() {
    var fName = "dev.onDeviceReady():" ;
    console.log(fName, "entry") ;

    // Useful for debug and understanding initialization flow.
    if( dev.isDeviceReady.e_fnDeviceReady__ ) {
        console.log(fName, "function terminated") ;
        return ;
    } else {
        dev.isDeviceReady.e_fnDeviceReady__ = dev.timeStamp() ;
    }

    // TODO: change this to use new custom events if I confirm it works in all webviews.
    // All device initialization is done; create and issue custom event named "app.Ready".
    // Using deprecated custom events until I can confirm new method works in all webviews...

    var evt = document.createEvent("Event") ;
    evt.initEvent("app.Ready", false, false) ;
    document.dispatchEvent(evt) ;

    console.log(fName, dev.isDeviceReady) ;
    console.log(fName, "exit") ;
} ;

/*
 * The following is an excerpt from the 2.9.0 cordova.js file and is useful for understanding
 * Cordova events. The order of events during page load and Cordova startup is as follows:
 *
 * onDOMContentLoaded*         Internal event that is received when the web page is loaded and parsed.
 * onNativeReady*              Internal event that indicates the Cordova native side is ready.
 * onCordovaReady*             Internal event fired when all Cordova JavaScript objects have been created.
 * onCordovaInfoReady*         Internal event fired when device properties are available.
 * onCordovaConnectionReady*   Internal event fired when the connection property has been set.
 * onDeviceReady*              User event fired to indicate that Cordova is ready
 * onResume                    User event fired to indicate a start/resume lifecycle event
 * onPause                     User event fired to indicate a pause lifecycle event
 * onDestroy*                  Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
 *
 * The events marked with an * are sticky. Once they have fired, they will stay in the fired state.
 * Listeners that subscribe to a sticky (*) event, after the event is fired, will execute right away.
 *
 * The only Cordova events that user code should register for are:
 *      deviceready           Cordova native code is initialized and Cordova APIs can be called from JavaScript
 *      pause                 App has moved to background
 *      resume                App has returned to foreground
 *
 * Listeners can be registered as follows:
 *      document.addEventListener("deviceready", myDeviceReadyListener, false);
 *      document.addEventListener("resume", myResumeListener, false);
 *      document.addEventListener("pause", myPauseListener, false);
 *
 * The following DOM lifecycle events should be used for saving and restoring state:
 *      window.onload
 *      window.onunload
 */

// The following is not fool-proof, we're mostly interested in detecting one
// or both events to insure device init is finished, detecting either will do.
// Even though the timing should indicate which container, it does not always work.

// NOTE: In most cases, you can leave these functions alone and use them as is.

// If this event is called first, we should be in the Cordova container.

dev.onDeviceReadyCordova = function() {
    dev.isDeviceReady.c_cordova_ready__ = dev.timeStamp() ;
    var fName = "dev.onDeviceReadyCordova():" ;
    console.log(fName, dev.isDeviceReady.c_cordova_ready__) ;
    window.setTimeout(dev.onDeviceReady, dev.INSURANCE) ;
} ;

// If this event is called first, we should be in the legacy XDK container.

dev.onDeviceReadyXDK = function() {
    dev.isDeviceReady.d_xdk_ready______ = dev.timeStamp() ;
    var fName = "dev.onDeviceReadyXDK():" ;
    console.log(fName, dev.isDeviceReady.d_xdk_ready______) ;
    window.setTimeout(dev.onDeviceReady, dev.INSURANCE) ;
} ;

// This is a faux onDeviceReady for browser scenario, mostly for code symmetry and fail-safe.

dev.onDeviceReadyBrowser = function() {
    dev.isDeviceReady.f_browser_ready__ = dev.timeStamp() ;
    var fName = "dev.onDeviceReadyBrowser():" ;
    console.log(fName, dev.isDeviceReady.f_browser_ready__) ;
    window.setTimeout(dev.onDeviceReady, dev.INSURANCE) ;
} ;

// Runs after document is loaded, and sets up wait for native (device) init to finish.
// If we're running in a browser we're ready to go when document is loaded, but...
// if we're running on a device we need to wait for native code to finish its init.

// NOTE: In most cases, you can leave this code alone and use it as is.

dev.initDeviceReady = function() {
    var fName = "dev.initDeviceReady():" ;
    console.log(fName, "entry") ;

    // Useful for debug and understanding initialization flow.
    if( dev.isDeviceReady.b_fnDocumentReady ) {
        console.log(fName, "function terminated") ;
        return ;
    } else {
        dev.isDeviceReady.b_fnDocumentReady = dev.timeStamp() ;
    }

    document.addEventListener("intel.xdk.device.ready", dev.onDeviceReadyXDK, false) ;
    document.addEventListener("deviceready", dev.onDeviceReadyCordova, false) ;
    window.setTimeout(dev.onDeviceReadyBrowser, dev.BROWSER) ;

    // Last one, above, is fail-safe, in case we got no device ready event from Cordova or XDK.
    // Cordova will timeout after five seconds, so we use a longer timeout to be conservative.
    // Very end of this file includes a "fail-safe, fail-safe" in case all else fails!

    // TODO: might want to double-check for Cordova deviceready, shouldn't be required...
    // "if" logic (below) needs further investigation in Cordova, legacy and debug containers
    // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
    // if( window.channel && channel.onCordovaReady && (channel.onCordovaReady.state === 2) )
    //     dev.onDeviceReadyCordova() ;

    console.log(fName, "navigator.vendor:", navigator.vendor) ;
    console.log(fName, "navigator.platform:", navigator.platform) ;
    console.log(fName, "navigator.userAgent:", navigator.userAgent) ;

    console.log(fName, "exit") ;
} ;

// Wait for document ready before looking for device ready.
// This insures the app does not start running until DOM is ready and...
// ...makes it easier to deal with both in-browser and on-device scenarios and...
// ...makes it easier to init device-dependent and device-independent code in one place.

// NOTE: In most cases, you can leave this code alone and use it as is.
// NOTE: document.readyState seems to be more reliable, but seems not to be omnipresent.
// NOTE: Delay after "load" event is added because some webviews appear to trigger prematurely.

if( document.readyState ) {                     // some devices don't support this, why???
    console.log("document.readyState:", document.readyState) ;
    document.onreadystatechange = function () {
        console.log("document.readyState:", document.readyState) ;
        if (document.readyState === "complete") {
            dev.initDeviceReady() ;             // call when document is "ready ready" :)
        }
    } ;
}
console.log("addEventListener:", dev.timeStamp()) ;
window.addEventListener("load", window.setTimeout(dev.initDeviceReady,dev.WINDOW_LOAD), false) ;
window.setTimeout(dev.initDeviceReady, dev.FAIL_SAFE) ;     // fail-safe fail-safe, just in case we miss all events!
<commit_msg>broken addEventListener in Windows 8 builds<commit_after>/*
 * Copyright (c) 2013-2014, Paul Fischer, Intel Corporation. All rights reserved.
 * Please see http://software.intel.com/html5/license/samples for license terms and conditions.
 */

/*
 * The functions in this file are designed to reliably detect various "ready" events
 * within a variety of containers (Intel XDK "legacy" container, Cordova 3.x container,
 * standard browser, App Preview, Crosswalk, etc.). It "unifies" the commonly used
 * ready events and is very helpful for moving a "web app" to a "hybrid app" scenario.
 *
 * This file has no dependencies. It will generate a custom "app.Ready" event
 * that you should use once to start your application, rather than waiting on a
 * "device ready" or "document ready" or "window load" or similar events.
 *
 * You should not have to modify anything in this file to use it. See the example
 * index.html file that accompanies this file (in its sample repo location) for
 * recommendations on the best placement of this file relative to other files and
 * for recommendations regarding the loading of other JavaScript files.
 *
 * There are a large number of console.log messages contained within this file.
 * They can be used to debug initialization problems and understand how it works.
 * It is highly recommended that you leave them in your app, they will not unduly
 * slow down or burden your application.
 *
 * There are many comments in this file and the accompanying index.html file.
 * Please read the comments within for details and further documentation.
 *
 * BTW: "dev" means "device" in this context, not "develop," because it grew out
 * a desire to build a more reliable and flexible "device ready" detector.
 */

/*jslint browser:true, devel:true, white:true, vars:true */
/*global $:false, intel:false, dev:false, performance:false */

window.dev = window.dev || {} ;         // there should only be one of these, but...

// Use performance counter if it is available, otherwise, use milliseconds since 1970

if( window.performance && performance.now ) {
    dev.timeStamp = function() { return performance.now().toFixed(3) ; } ;
} else {
    dev.timeStart = Date.now() ;        // feeble zero ref for relative time in ms
    dev.timeStamp = function() { return (Date.now() - dev.timeStart) ; } ;
}

// Defines some delays constants used throughout for ready detections.
// Each should be smaller than the next; most cases should work as is.
// Lowering dev.BROWSER will speed up detection of browser scenario...
// ...at expense of possible false detects of browser environment...
// ...probably okay to go as low as 3000ms, depends on external libraries, etc.

dev.INSURANCE = 250 ;                   // ms, insurance on registering ready events detected
dev.WINDOW_LOAD = 500 ;                 // ms, for combating premature window load events
dev.BROWSER = 7000 ;                    // ms, detecting in a browser (probably best at >5 seconds)
dev.FAIL_SAFE = 10000 ;                 // ms, if all else fails, this saves our bacon :-)

// Used to keep track of time when each of these items was triggered.
// Sorry for the weird names in the isDeviceReady structure, it's done for
// easier debugging and comparison of numbers when displayed in console.log messages.

dev.isDeviceReady = {                   // listed in approximate order expected
    a_startTime______:dev.timeStamp(),  // when we started execution of this module
    b_fnDocumentReady:false,            // detected document.readyState == "complete"
    c_cordova_ready__:false,            // detected cordova device ready event
    d_xdk_ready______:false,            // detected xdk device ready event
    e_fnDeviceReady__:false,            // entered onDeviceReady()
    f_browser_ready__:false             // detected browser container
} ;

// Where the device ready event ultimately ends up, regardless of environment.
// Runs after underlying device native code and browser is initialized.
// Usually not much needed here, just additional "device init" code.
// See initDeviceReady() below for code that kicks off this function.
// This function works with Cordova and XDK webview or in a browser.

// NOTE: Customize this function, if necessary, for low-level init of your app.
// NOTE: In most cases, you can leave this code alone and use it as is.

dev.onDeviceReady = function() {
    var fName = "dev.onDeviceReady():" ;
    console.log(fName, "entry") ;

    // Useful for debug and understanding initialization flow.
    if( dev.isDeviceReady.e_fnDeviceReady__ ) {
        console.log(fName, "function terminated") ;
        return ;
    } else {
        dev.isDeviceReady.e_fnDeviceReady__ = dev.timeStamp() ;
    }

    // TODO: change this to use new custom events if I confirm it works in all webviews.
    // All device initialization is done; create and issue custom event named "app.Ready".
    // Using deprecated custom events until I can confirm new method works in all webviews...

    var evt = document.createEvent("Event") ;
    evt.initEvent("app.Ready", false, false) ;
    document.dispatchEvent(evt) ;

    console.log(fName, dev.isDeviceReady) ;
    console.log(fName, "exit") ;
} ;

/*
 * The following is an excerpt from the 2.9.0 cordova.js file and is useful for understanding
 * Cordova events. The order of events during page load and Cordova startup is as follows:
 *
 * onDOMContentLoaded*         Internal event that is received when the web page is loaded and parsed.
 * onNativeReady*              Internal event that indicates the Cordova native side is ready.
 * onCordovaReady*             Internal event fired when all Cordova JavaScript objects have been created.
 * onCordovaInfoReady*         Internal event fired when device properties are available.
 * onCordovaConnectionReady*   Internal event fired when the connection property has been set.
 * onDeviceReady*              User event fired to indicate that Cordova is ready
 * onResume                    User event fired to indicate a start/resume lifecycle event
 * onPause                     User event fired to indicate a pause lifecycle event
 * onDestroy*                  Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
 *
 * The events marked with an * are sticky. Once they have fired, they will stay in the fired state.
 * Listeners that subscribe to a sticky (*) event, after the event is fired, will execute right away.
 *
 * The only Cordova events that user code should register for are:
 *      deviceready           Cordova native code is initialized and Cordova APIs can be called from JavaScript
 *      pause                 App has moved to background
 *      resume                App has returned to foreground
 *
 * Listeners can be registered as follows:
 *      document.addEventListener("deviceready", myDeviceReadyListener, false);
 *      document.addEventListener("resume", myResumeListener, false);
 *      document.addEventListener("pause", myPauseListener, false);
 *
 * The following DOM lifecycle events should be used for saving and restoring state:
 *      window.onload
 *      window.onunload
 */

// The following is not fool-proof, we're mostly interested in detecting one
// or both events to insure device init is finished, detecting either will do.
// Even though the timing should indicate which container, it does not always work.

// NOTE: In most cases, you can leave these functions alone and use them as is.

// If this event is called first, we should be in the Cordova container.

dev.onDeviceReadyCordova = function() {
    dev.isDeviceReady.c_cordova_ready__ = dev.timeStamp() ;
    var fName = "dev.onDeviceReadyCordova():" ;
    console.log(fName, dev.isDeviceReady.c_cordova_ready__) ;
    window.setTimeout(dev.onDeviceReady, dev.INSURANCE) ;
} ;

// If this event is called first, we should be in the legacy XDK container.

dev.onDeviceReadyXDK = function() {
    dev.isDeviceReady.d_xdk_ready______ = dev.timeStamp() ;
    var fName = "dev.onDeviceReadyXDK():" ;
    console.log(fName, dev.isDeviceReady.d_xdk_ready______) ;
    window.setTimeout(dev.onDeviceReady, dev.INSURANCE) ;
} ;

// This is a faux onDeviceReady for browser scenario, mostly for code symmetry and fail-safe.

dev.onDeviceReadyBrowser = function() {
    dev.isDeviceReady.f_browser_ready__ = dev.timeStamp() ;
    var fName = "dev.onDeviceReadyBrowser():" ;
    console.log(fName, dev.isDeviceReady.f_browser_ready__) ;
    window.setTimeout(dev.onDeviceReady, dev.INSURANCE) ;
} ;

// Runs after document is loaded, and sets up wait for native (device) init to finish.
// If we're running in a browser we're ready to go when document is loaded, but...
// if we're running on a device we need to wait for native code to finish its init.

// NOTE: In most cases, you can leave this code alone and use it as is.

dev.initDeviceReady = function() {
    var fName = "dev.initDeviceReady():" ;
    console.log(fName, "entry") ;

    // Useful for debug and understanding initialization flow.
    if( dev.isDeviceReady.b_fnDocumentReady ) {
        console.log(fName, "function terminated") ;
        return ;
    } else {
        dev.isDeviceReady.b_fnDocumentReady = dev.timeStamp() ;
    }

    document.addEventListener("intel.xdk.device.ready", dev.onDeviceReadyXDK, false) ;
    document.addEventListener("deviceready", dev.onDeviceReadyCordova, false) ;
    window.setTimeout(dev.onDeviceReadyBrowser, dev.BROWSER) ;

    // Last one, above, is fail-safe, in case we got no device ready event from Cordova or XDK.
    // Cordova will timeout after five seconds, so we use a longer timeout to be conservative.
    // Very end of this file includes a "fail-safe, fail-safe" in case all else fails!

    // TODO: might want to double-check for Cordova deviceready, shouldn't be required...
    // "if" logic (below) needs further investigation in Cordova, legacy and debug containers
    // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
    // if( window.channel && channel.onCordovaReady && (channel.onCordovaReady.state === 2) )
    //     dev.onDeviceReadyCordova() ;

    console.log(fName, "navigator.vendor:", navigator.vendor) ;
    console.log(fName, "navigator.platform:", navigator.platform) ;
    console.log(fName, "navigator.userAgent:", navigator.userAgent) ;

    console.log(fName, "exit") ;
} ;

// Wait for document ready before looking for device ready.
// This insures the app does not start running until DOM is ready and...
// ...makes it easier to deal with both in-browser and on-device scenarios and...
// ...makes it easier to init device-dependent and device-independent code in one place.

// NOTE: In most cases, you can leave this code alone and use it as is.
// NOTE: document.readyState seems to be more reliable, but seems not to be omnipresent.
// NOTE: Delay after "load" event is added because some webviews appear to trigger prematurely.

if( document.readyState ) {                     // some devices don't support this, why???
    console.log("document.readyState:", document.readyState) ;
    document.onreadystatechange = function () {
        console.log("document.readyState:", document.readyState) ;
        if (document.readyState === "complete") {
            dev.initDeviceReady() ;             // call when document is "ready ready" :)
        }
    } ;
}
console.log("addEventListener:", dev.timeStamp()) ;
window.addEventListener("load", function(){window.setTimeout(dev.initDeviceReady,dev.WINDOW_LOAD);}.bind(dev), false) ;
window.setTimeout(dev.initDeviceReady, dev.FAIL_SAFE) ;     // fail-safe fail-safe, just in case we miss all events!
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */

MODEL({
  package: 'foam.demos.sevenguis',
  name: 'Timer',
  properties: [
    {
      name: 'elapsedTime',
      defaultValue: 0
    },
    {
      name: 'duration',
      units: 'ms',
      defaultValue: 10000,
    },
    {
      name: 'progress',
      mode: 'read-only'
    },
    {
      name: 'lastTick_',
      hidden: true,
      defaultValue: 0
    }
  ],
  methods: [
    function init() {
      this.SUPER();
      this.X.dynamic(function() { this.progress = Math.min(1, this.elapsedTime / this.duration);  }.bind(this));
      this.tick();
    }
  ],
  actions: [
    {
      name: 'reset',
      action: function() {
        this.elapsedTime = this.lastTick_ = 0;
        this.tick();
      }
    }
  ],
  listeners: [
    {
      name: 'tick',
      isFramed: true,
      code: function() {
        if ( this.elapsedTime >= this.duration ) return;
        var now = Date.now();
        if ( this.lastTick_ ) this.elapsedTime += now - this.lastTick_;
        this.lastTick_ = now;
        this.tick();
      }
    }
  ]
});
<commit_msg>Timer demo.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */

MODEL({
  package: 'foam.demos.sevenguis',
  name: 'Timer',
  properties: [
    {
      name: 'elapsedTime',
      defaultValue: 0
    },
    {
      name: 'duration',
      units: 'ms',
      defaultValue: 5000,
    },
    {
      name: 'progress',
      view: 'foam.ui.RangeView'
//      mode: 'read-only'
    },
    {
      name: 'lastTick_',
      hidden: true,
      defaultValue: 0
    }
  ],
  methods: [
    function init() {
      this.SUPER();
      this.X.dynamic(function() { this.progress = Math.min(1, this.elapsedTime / this.duration);  }.bind(this));
      this.tick();
    }
  ],
  actions: [
    {
      name: 'reset',
      action: function() {
        this.elapsedTime = this.lastTick_ = 0;
        this.tick();
      }
    }
  ],
  listeners: [
    {
      name: 'tick',
      isFramed: true,
      code: function() {
        if ( this.elapsedTime >= this.duration ) return;
        var now = Date.now();
        if ( this.lastTick_ ) this.elapsedTime += now - this.lastTick_;
        this.lastTick_ = now;
        this.tick();
      }
    }
  ]
});
<|endoftext|>


<commit_before>var timeout = 20000;

var visited = [];
var pending = [];

var casper = require('casper').create({
    /* verbose: true,
    logLevel: "debug" */
    viewportSize: {width: 1280, height: 768},
    waitTimeout: timeout
  });

var args = casper.cli.args;
if (args.length < 2) {
  casper.echo("Need a domain and a selector");
  casper.exit();
}

var domain = args[0];
var testSelector = args[1];
//casper.echo("Searching " + domain + " for " + testSelector);

// http://stackoverflow.com/questions/646628/how-to-check-if-a-string-startswith-another-string
if (typeof String.prototype.startsWith != 'function') {
  String.prototype.startsWith = function (str){
    return this.slice(0, str.length) == str;
  };
}
if (typeof String.prototype.endsWith != 'function') {
  String.prototype.endsWith = function (str){
    return this.slice(-str.length) == str;
  };
}

function usableLink(href) {
  // only relative links or those with the domain
  if (href.length < 1) {
    return false;
  }
  if (href.startsWith("mailto")) {
    return false;
  }
  if (href.startsWith("//")) {
    // protocol agnostic URL
    if (href.startsWith("//" + domain)) {
      return true;
    }
    else {
      return false;
    }
  }
  var pat = /^https?:\/\//i;
  if (pat.test(href)) {
    if ( href.startsWith("http://" + domain) ||
          href.startsWith("https://" + domain) ) {
      return true;
    }
    return false;
  }
  // hopefully, a relative url
  return true;
}

function getLinks() {
  return [].map.call(document.querySelectorAll('a[href]'), function(node) {
    return node.getAttribute('href');
  });
}

function checkSelector(selector) {
  return ([].map.call(document.querySelectorAll(selector), function(node) {
    return node.outerHTML;
  }));
}

function imagesLoading() {
  return true;
  //var images = document.getElementsByTagName('img');
  //return [].prototype.every.call(images, function(i) { return i.complete; });
}

function spider(url) {
  visited.push(url);

  casper.open(url).waitFor(function() {
      return this.evaluate(imagesLoading);
    }, function() {
    //this.echo(url);
    // do the test
    var test = this.evaluate(checkSelector, testSelector);
    if (test.length > 0) {
      //this.echo(url + "\t" + test.join(" "));
      this.echo(url);
    }
    var links = this.evaluate(getLinks);
    links = links.filter(usableLink);
    links.forEach(function(link) {
      if (!(link.startsWith("http") || link.startsWith("https") || link.startsWith("//"))) {
        // relative to current
        if (link.startsWith("/")) {
          link = "http://" + domain + link;
        }
        else {
          link = url + link;
        }
      }
      if (link.endsWith("/")) {
        link = link.slice(0, link.length-1);
      }
      if (visited.indexOf(link) === -1 && pending.indexOf(link) === -1) {
        pending.push(link);
      }
    });

    if (pending.length > 0) {
      var next = pending.shift();
      spider(next);
    }
    else {
      return;
    }
  }, function() { this.echo(url + ": timeout!"); });
}

var start_url = "http://" + domain;
casper.start(start_url, function() {
  spider(start_url);
});
casper.run();<commit_msg>a little attribution<commit_after>// spider a website looking for a selector
// with help from http://planzero.org/blog/2013/03/07/spidering_the_web_with_casperjs

var timeout = 20000;

var visited = [];
var pending = [];

var casper = require('casper').create({
    /* verbose: true,
    logLevel: "debug" */
    viewportSize: {width: 1280, height: 768},
    waitTimeout: timeout
  });

var args = casper.cli.args;
if (args.length < 2) {
  casper.echo("Need a domain and a selector");
  casper.exit();
}

var domain = args[0];
var testSelector = args[1];
//casper.echo("Searching " + domain + " for " + testSelector);

// http://stackoverflow.com/questions/646628/how-to-check-if-a-string-startswith-another-string
if (typeof String.prototype.startsWith != 'function') {
  String.prototype.startsWith = function (str){
    return this.slice(0, str.length) == str;
  };
}
if (typeof String.prototype.endsWith != 'function') {
  String.prototype.endsWith = function (str){
    return this.slice(-str.length) == str;
  };
}

function usableLink(href) {
  // only relative links or those with the domain
  if (href.length < 1) {
    return false;
  }
  if (href.startsWith("mailto")) {
    return false;
  }
  if (href.startsWith("//")) {
    // protocol agnostic URL
    if (href.startsWith("//" + domain)) {
      return true;
    }
    else {
      return false;
    }
  }
  var pat = /^https?:\/\//i;
  if (pat.test(href)) {
    if ( href.startsWith("http://" + domain) ||
          href.startsWith("https://" + domain) ) {
      return true;
    }
    return false;
  }
  // hopefully, a relative url
  return true;
}

function getLinks() {
  return [].map.call(document.querySelectorAll('a[href]'), function(node) {
    return node.getAttribute('href');
  });
}

function checkSelector(selector) {
  return ([].map.call(document.querySelectorAll(selector), function(node) {
    return node.outerHTML;
  }));
}

function imagesLoading() {
  return true;
  // from http://codeutopia.net/blog/2014/02/05/tips-for-taking-screenshots-with-phantomjs-casperjs/
  //var images = document.getElementsByTagName('img');
  //return [].prototype.every.call(images, function(i) { return i.complete; });
}

function spider(url) {
  visited.push(url);

  casper.open(url).waitFor(function() {
      return this.evaluate(imagesLoading);
    }, function() {
    //this.echo(url);
    // do the test
    var test = this.evaluate(checkSelector, testSelector);
    if (test.length > 0) {
      //this.echo(url + "\t" + test.join(" "));
      this.echo(url);
    }
    var links = this.evaluate(getLinks);
    links = links.filter(usableLink);
    links.forEach(function(link) {
      if (!(link.startsWith("http") || link.startsWith("https") || link.startsWith("//"))) {
        // relative to current
        if (link.startsWith("/")) {
          link = "http://" + domain + link;
        }
        else {
          link = url + link;
        }
      }
      if (link.endsWith("/")) {
        link = link.slice(0, link.length-1);
      }
      if (visited.indexOf(link) === -1 && pending.indexOf(link) === -1) {
        pending.push(link);
      }
    });

    if (pending.length > 0) {
      var next = pending.shift();
      spider(next);
    }
    else {
      return;
    }
  }, function() { this.echo(url + ": timeout!"); });
}

var start_url = "http://" + domain;
casper.start(start_url, function() {
  spider(start_url);
});
casper.run();<|endoftext|>