<commit_before>import Ember from 'ember';
import Configuration from '../configuration';
import TokenAuthenticator from './token';

/**
  JWT (JSON Web Token) Authenticator that supports automatic token refresh.

  Inspired by [ember-simple-auth-oauth2](https://github.com/simplabs/ember-simple-auth/tree/master/packages/ember-simple-auth-oauth2)

  The factory for this authenticator is registered as
  'authenticator:jwt` in Ember's container.

  @class JWT
  @namespace SimpleAuth.Authenticators
  @module ember-simple-auth-token/authenticators/jwt
  @extends TokenAuthenticator
*/
export default TokenAuthenticator.extend({
  /**
    The endpoint on the server for refreshing a token.
    @property serverTokenRefreshEndpoint
    @type String
    @default '/api/token-refresh/'
  */
  serverTokenRefreshEndpoint: '/api/token-refresh/',

  /**
    Sets whether the authenticator automatically refreshes access tokens.
    @property refreshAccessTokens
    @type Boolean
    @default true
  */
  refreshAccessTokens: true,

  /**
    The number of seconds to subtract from the token's time of expiration when
    scheduling the automatic token refresh call.
    @property refreshLeeway
    @type Integer
    @default 0 (seconds)
  */
  refreshLeeway: 0,

  /**
    The amount of time to wait before refreshing the token - set automatically.
    @property refreshTokenTimeout
    @private
  */
  refreshTokenTimeout: null,

  /**
    The name for which decoded token field represents the token expire time.
    @property tokenExpireName
    @type String
    @default 'exp'
  */
  tokenExpireName: 'exp',

  /**
    Default time unit.
    @property timeFactor
    @type Integer
    @default 1 (seconds)
  */
  timeFactor: 1,

  /**
    @method init
    @private
  */
  init() {
    this.serverTokenEndpoint = Configuration.serverTokenEndpoint;
    this.serverTokenRefreshEndpoint = Configuration.serverTokenRefreshEndpoint;
    this.identificationField = Configuration.identificationField;
    this.passwordField = Configuration.passwordField;
    this.tokenPropertyName = Configuration.tokenPropertyName;
    this.refreshAccessTokens = Configuration.refreshAccessTokens;
    this.refreshLeeway = Configuration.refreshLeeway;
    this.tokenExpireName = Configuration.tokenExpireName;
    this.timeFactor = Configuration.timeFactor;
    this.headers = Configuration.headers;
  },

  /**
    Restores the session from a set of session properties.

    It will return a resolving promise if one of two conditions is met:

    1) Both `data.token` and `data.expiresAt` are non-empty and `expiresAt`
       is greater than the calculated `now`.
    2) If `data.token` is non-empty and the decoded token has a key for
       `tokenExpireName`.

    If `refreshAccessTokens` is true, `scheduleAccessTokenRefresh` will
    be called and an automatic token refresh will be initiated.

    @method restore
    @param {Object} data The data to restore the session from
    @return {Ember.RSVP.Promise} A promise that when it resolves results
                                 in the session being authenticated
  */
  restore(data) {
    const dataObject = Ember.Object.create(data);

    return new Ember.RSVP.Promise((resolve, reject) => {
      const now = this.getCurrentTime();
      const token = dataObject.get(this.tokenPropertyName);
      let expiresAt = this.resolveTime(dataObject.get(this.tokenExpireName));

      if (Ember.isEmpty(token)) {
        return reject(new Error('empty token'));
      }
      if (Ember.isEmpty(expiresAt)) {
        // Fetch the expire time from the token data since `expiresAt`
        // wasn't included in the data object that was passed in.
        const tokenData = this.getTokenData(data[this.tokenPropertyName]);

        expiresAt = this.resolveTime(tokenData[this.tokenExpireName]);
        if (Ember.isEmpty(expiresAt)) {
          return resolve(data);
        }
      }

      if (expiresAt > now) {
        const wait = expiresAt - now - (this.refreshLeeway * 1000);

        if (wait > 0) {
          if (this.refreshAccessTokens) {
            this.scheduleAccessTokenRefresh(dataObject.get(this.tokenExpireName), token);
          }
          resolve(data);
        } else if (this.refreshAccessTokens) {
          resolve(this.refreshAccessToken(token));
        } else {
          reject(new Error('unable to refresh token'));
        }
      } else {
        reject(new Error('token is expired'));
      }
    });
  },

  /**
    Authenticates the session with the specified `credentials`.

    It will return a resolving promise if it successfully posts a request
    to the `JWT.serverTokenEndpoint` with the valid credentials.

    An automatic token refresh will be scheduled with the new expiration date
    from the returned refresh token. That expiration will be merged with the
    response and the promise resolved.

    @method authenticate
    @param {Object} credentials The credentials to authenticate the session with
    @param {Object} headers Additional headers to be sent to server
    @return {Ember.RSVP.Promise} A promise that resolves when an auth token is
                                 successfully acquired from the server and rejects
                                 otherwise
  */
  authenticate(credentials, headers) {

    return new Ember.RSVP.Promise((resolve, reject) => {
      const data = this.getAuthenticateData(credentials);

      this.makeRequest(this.serverTokenEndpoint, data, headers).then(response => {
        Ember.run(() => {
          const token = Ember.get(response, this.tokenPropertyName);
          const tokenData = this.getTokenData(token);
          const expiresAt = Ember.get(tokenData, this.tokenExpireName);
          const tokenExpireData = {};

          this.scheduleAccessTokenRefresh(expiresAt, token);

          tokenExpireData[this.tokenExpireName] = expiresAt;

          response = Ember.merge(response, tokenExpireData);

          resolve(this.getResponseData(response));
        });
      }, xhr => {
        Ember.run(() => { reject(xhr.responseJSON || xhr.responseText); });
      });
    });
  },

  /**
    Schedules a token refresh request to be sent to the backend after a calculated
    `wait` time has passed.

    If both `token` and `expiresAt` are non-empty, and `expiresAt` minus the optional
    refres leeway is greater than the calculated `now`, the token refresh will be scheduled
    through Ember.run.later.

    @method scheduleAccessTokenRefresh
    @private
  */
  scheduleAccessTokenRefresh(expiresAt, token) {
    if (this.refreshAccessTokens) {
      expiresAt = this.resolveTime(expiresAt);

      const now = this.getCurrentTime();
      const wait = expiresAt - now - (this.refreshLeeway * 1000);

      if (!Ember.isEmpty(token) && !Ember.isEmpty(expiresAt) && wait > 0) {
        Ember.run.cancel(this._refreshTokenTimeout);

        delete this._refreshTokenTimeout;

        this._refreshTokenTimeout = Ember.run.later(this, this.refreshAccessToken, token, wait);
      }
    }
  },

  /**
    Makes a refresh token request to grab a new authenticated JWT token from the server.

    It will return a resolving promise if a successful POST is made to the
    `JWT.serverTokenRefreshEndpoint`.

    After the new token is obtained it will schedule the next automatic token refresh
    based on the new `expiresAt` time.

    The session will be updated via the trigger `sessionDataUpdated`.

    @method refreshAccessToken
    @private
  */
  refreshAccessToken(token, headers) {
    let data = {
      [this.tokenPropertyName]: token
    };

    return new Ember.RSVP.Promise((resolve, reject) => {
      this.makeRequest(this.serverTokenRefreshEndpoint, data, headers).then(response => {
        Ember.run(() => {
          const resToken = Ember.get(response, this.tokenPropertyName);
          const tokenData = this.getTokenData(resToken);
          const expiresAt = Ember.get(tokenData, this.tokenExpireName);
          const tokenExpireData = {};

          tokenExpireData[this.tokenExpireName] = expiresAt;

          data = Ember.merge(response, tokenExpireData);

          this.scheduleAccessTokenRefresh(expiresAt, resToken);
          this.trigger('sessionDataUpdated', data);

          resolve(response);
        });
      }, (xhr, status, error) => {
        Ember.Logger.warn(`Access token could not be refreshed - server responded with ${error}.`);
        reject();
      });
    });
  },

  /**
    Returns the decoded token with accessible returned values.

    @method getTokenData
    @return {object} An object with properties for the session.
  */
  getTokenData(token) {
    const tokenData = atob(token.split('.')[1]);

    try {
      return JSON.parse(tokenData);
    } catch (e) {
      return tokenData;
    }
  },

  /**
    Accepts a `url` and `data` to be used in an ajax server request.

    @method makeRequest
    @private
  */
  makeRequest(url, data, headers) {
    return Ember.$.ajax({
      url: url,
      method: 'POST',
      data: JSON.stringify(data),
      dataType: 'json',
      contentType: 'application/json',
      headers: this.headers,
      beforeSend: (xhr, settings) => {
        xhr.setRequestHeader('Accept', settings.accepts.json);

        if (headers) {
          Object.keys(headers).forEach(key => {
            xhr.setRequestHeader(key, headers[key]);
          });
        }
      }
    });
  },

  /**
    Cancels any outstanding automatic token refreshes and returns a resolving
    promise.
    @method invalidate
    @param {Object} data The data of the session to be invalidated
    @return {Ember.RSVP.Promise} A resolving promise
  */
  invalidate() {
    Ember.run.cancel(this._refreshTokenTimeout);

    delete this._refreshTokenTimeout;

    return new Ember.RSVP.resolve();
  },

  getCurrentTime() {
    return (new Date()).getTime();
  },

  /**
    Handles converting between time units for data between different systems.
    Default: seconds(1)
    @method resolveTime
    @private
  */
  resolveTime(time) {
    if (Ember.isEmpty(time)) {
      return time;
    }
    return new Date(time * this.timeFactor).getTime();
  }
});
<commit_msg>Add fix for expiresAt<commit_after>import Ember from 'ember';
import Configuration from '../configuration';
import TokenAuthenticator from './token';

/**
  JWT (JSON Web Token) Authenticator that supports automatic token refresh.

  Inspired by [ember-simple-auth-oauth2](https://github.com/simplabs/ember-simple-auth/tree/master/packages/ember-simple-auth-oauth2)

  The factory for this authenticator is registered as
  'authenticator:jwt` in Ember's container.

  @class JWT
  @namespace SimpleAuth.Authenticators
  @module ember-simple-auth-token/authenticators/jwt
  @extends TokenAuthenticator
*/
export default TokenAuthenticator.extend({
  /**
    The endpoint on the server for refreshing a token.
    @property serverTokenRefreshEndpoint
    @type String
    @default '/api/token-refresh/'
  */
  serverTokenRefreshEndpoint: '/api/token-refresh/',

  /**
    Sets whether the authenticator automatically refreshes access tokens.
    @property refreshAccessTokens
    @type Boolean
    @default true
  */
  refreshAccessTokens: true,

  /**
    The number of seconds to subtract from the token's time of expiration when
    scheduling the automatic token refresh call.
    @property refreshLeeway
    @type Integer
    @default 0 (seconds)
  */
  refreshLeeway: 0,

  /**
    The amount of time to wait before refreshing the token - set automatically.
    @property refreshTokenTimeout
    @private
  */
  refreshTokenTimeout: null,

  /**
    The name for which decoded token field represents the token expire time.
    @property tokenExpireName
    @type String
    @default 'exp'
  */
  tokenExpireName: 'exp',

  /**
    Default time unit.
    @property timeFactor
    @type Integer
    @default 1 (seconds)
  */
  timeFactor: 1,

  /**
    @method init
    @private
  */
  init() {
    this.serverTokenEndpoint = Configuration.serverTokenEndpoint;
    this.serverTokenRefreshEndpoint = Configuration.serverTokenRefreshEndpoint;
    this.identificationField = Configuration.identificationField;
    this.passwordField = Configuration.passwordField;
    this.tokenPropertyName = Configuration.tokenPropertyName;
    this.refreshAccessTokens = Configuration.refreshAccessTokens;
    this.refreshLeeway = Configuration.refreshLeeway;
    this.tokenExpireName = Configuration.tokenExpireName;
    this.timeFactor = Configuration.timeFactor;
    this.headers = Configuration.headers;
  },

  /**
    Restores the session from a set of session properties.

    It will return a resolving promise if one of two conditions is met:

    1) Both `data.token` and `data.expiresAt` are non-empty and `expiresAt`
       is greater than the calculated `now`.
    2) If `data.token` is non-empty and the decoded token has a key for
       `tokenExpireName`.

    If `refreshAccessTokens` is true, `scheduleAccessTokenRefresh` will
    be called and an automatic token refresh will be initiated.

    @method restore
    @param {Object} data The data to restore the session from
    @return {Ember.RSVP.Promise} A promise that when it resolves results
                                 in the session being authenticated
  */
  restore(data) {
    const dataObject = Ember.Object.create(data);

    return new Ember.RSVP.Promise((resolve, reject) => {
      const now = this.getCurrentTime();
      const token = dataObject.get(this.tokenPropertyName);
      let expiresAt = this.resolveTime(dataObject.get(this.tokenExpireName));

      if (Ember.isEmpty(token)) {
        return reject(new Error('empty token'));
      }
      if (Ember.isEmpty(expiresAt)) {
        // Fetch the expire time from the token data since `expiresAt`
        // wasn't included in the data object that was passed in.
        const tokenData = this.getTokenData(data[this.tokenPropertyName]);

        expiresAt = this.resolveTime(tokenData[this.tokenExpireName]);
        if (Ember.isEmpty(expiresAt)) {
          return resolve(data);
        }
      }

      if (expiresAt > now) {
        const wait = expiresAt - now - (this.refreshLeeway * 1000);

        if (wait > 0) {
          if (this.refreshAccessTokens) {
            this.scheduleAccessTokenRefresh(dataObject.get(this.tokenExpireName), token);
          }
          resolve(data);
        } else if (this.refreshAccessTokens) {
          resolve(this.refreshAccessToken(token));
        } else {
          reject(new Error('unable to refresh token'));
        }
      } else {
        reject(new Error('token is expired'));
      }
    });
  },

  /**
    Authenticates the session with the specified `credentials`.

    It will return a resolving promise if it successfully posts a request
    to the `JWT.serverTokenEndpoint` with the valid credentials.

    An automatic token refresh will be scheduled with the new expiration date
    from the returned refresh token. That expiration will be merged with the
    response and the promise resolved.

    @method authenticate
    @param {Object} credentials The credentials to authenticate the session with
    @param {Object} headers Additional headers to be sent to server
    @return {Ember.RSVP.Promise} A promise that resolves when an auth token is
                                 successfully acquired from the server and rejects
                                 otherwise
  */
  authenticate(credentials, headers) {

    return new Ember.RSVP.Promise((resolve, reject) => {
      const data = this.getAuthenticateData(credentials);

      this.makeRequest(this.serverTokenEndpoint, data, headers).then(response => {
        Ember.run(() => {
          const token = Ember.get(response, this.tokenPropertyName);
          const tokenData = this.getTokenData(token);
          const expiresAt = Ember.get(tokenData, this.tokenExpireName);
          const tokenExpireData = {};

          this.scheduleAccessTokenRefresh(expiresAt, token);

          tokenExpireData[this.tokenExpireName] = expiresAt;

          response = Ember.merge(response, tokenExpireData);

          resolve(this.getResponseData(response));
        });
      }, xhr => {
        Ember.run(() => { reject(xhr.responseJSON || xhr.responseText); });
      });
    });
  },

  /**
    Schedules a token refresh request to be sent to the backend after a calculated
    `wait` time has passed.

    If both `token` and `expiresAt` are non-empty, and `expiresAt` minus the optional
    refres leeway is greater than the calculated `now`, the token refresh will be scheduled
    through Ember.run.later.

    @method scheduleAccessTokenRefresh
    @private
  */
  scheduleAccessTokenRefresh(expiresAt, token) {
    if (this.refreshAccessTokens) {
      expiresAt = this.resolveTime(expiresAt);

      const now = this.getCurrentTime();
      const wait = expiresAt - now - (this.refreshLeeway * 1000);

      if (!Ember.isEmpty(token) && !Ember.isEmpty(expiresAt) && wait > 0) {
        Ember.run.cancel(this._refreshTokenTimeout);

        delete this._refreshTokenTimeout;

        this._refreshTokenTimeout = Ember.run.later(this, this.refreshAccessToken, token, wait);
      }
    }
  },

  /**
    Makes a refresh token request to grab a new authenticated JWT token from the server.

    It will return a resolving promise if a successful POST is made to the
    `JWT.serverTokenRefreshEndpoint`.

    After the new token is obtained it will schedule the next automatic token refresh
    based on the new `expiresAt` time.

    The session will be updated via the trigger `sessionDataUpdated`.

    @method refreshAccessToken
    @private
  */
  refreshAccessToken(token, headers) {
    let data = {
      [this.tokenPropertyName]: token
    };

    return new Ember.RSVP.Promise((resolve, reject) => {
      this.makeRequest(this.serverTokenRefreshEndpoint, data, headers).then(response => {
        Ember.run(() => {
          const resToken = Ember.get(response, this.tokenPropertyName);
          const tokenData = this.getTokenData(resToken);
          const expiresAt = Ember.get(tokenData, this.tokenExpireName);
          const tokenExpireData = {};

          tokenExpireData[this.tokenExpireName] = expiresAt;

          data = Ember.merge(response, tokenExpireData);

          this.scheduleAccessTokenRefresh(expiresAt, resToken);
          this.trigger('sessionDataUpdated', data);

          resolve(response);
        });
      }, (xhr, status, error) => {
        Ember.Logger.warn(`Access token could not be refreshed - server responded with ${error}.`);
        reject();
      });
    });
  },

  /**
    Returns the decoded token with accessible returned values.

    @method getTokenData
    @return {object} An object with properties for the session.
  */
  getTokenData(token) {
    const tokenData = atob(token.split('.')[1]);

    try {
      return JSON.parse(tokenData);
    } catch (e) {
      return tokenData;
    }
  },

  /**
    Accepts a `url` and `data` to be used in an ajax server request.

    @method makeRequest
    @private
  */
  makeRequest(url, data, headers) {
    return Ember.$.ajax({
      url: url,
      method: 'POST',
      data: JSON.stringify(data),
      dataType: 'json',
      contentType: 'application/json',
      headers: this.headers,
      beforeSend: (xhr, settings) => {
        xhr.setRequestHeader('Accept', settings.accepts.json);

        if (headers) {
          Object.keys(headers).forEach(key => {
            xhr.setRequestHeader(key, headers[key]);
          });
        }
      }
    });
  },

  /**
    Cancels any outstanding automatic token refreshes and returns a resolving
    promise.
    @method invalidate
    @param {Object} data The data of the session to be invalidated
    @return {Ember.RSVP.Promise} A resolving promise
  */
  invalidate() {
    Ember.run.cancel(this._refreshTokenTimeout);

    delete this._refreshTokenTimeout;

    return new Ember.RSVP.resolve();
  },

  getCurrentTime() {
    return Math.floor((new Date()).getTime() / 1000);
  },

  /**
    Handles converting between time units for data between different systems.
    Default: seconds(1)
    @method resolveTime
    @private
  */
  resolveTime(time) {
    if (Ember.isEmpty(time)) {
      return time;
    }
    return new Date(time * this.timeFactor).getTime();
  }
});
<|endoftext|>


<commit_before>import { addCallback, runCallbacks, runCallbacksAsync } from 'meteor/vulcan:core';
import { Posts } from 'meteor/example-forum';
import Users from 'meteor/vulcan:users';

import { convertFromRaw } from 'draft-js';
import { draftToHTML } from '../../editor/utils.js';
import { preProcessLatex } from '../../editor/server/utils.js';
import Localgroups from '../localgroups/collection.js';

import marked from 'marked';
import TurndownService from 'turndown';
const turndownService = new TurndownService()

function PostsEditRunPostUndraftedSyncCallbacks (modifier, post) {
  if (modifier.$set && modifier.$set.draft === false && post.draft) {
    modifier = runCallbacks("posts.undraft.sync", modifier, post);
  }
  return modifier;
}
addCallback("posts.edit.sync", PostsEditRunPostUndraftedSyncCallbacks);

function PostsEditRunPostUndraftedAsyncCallbacks (newPost, oldPost) {
  if (!newPost.draft && oldPost.draft) {
    runCallbacksAsync("posts.undraft.async", newPost, oldPost)
  }
  return newPost
}

addCallback("posts.edit.async", PostsEditRunPostUndraftedAsyncCallbacks);

/**
 * @summary set postedAt when a post is moved out of drafts
 */
function PostsSetPostedAt (modifier, post) {
  modifier.$set.postedAt = new Date();
  if (modifier.$unset) {
    delete modifier.$unset.postedAt;
  }
  return modifier;
}
addCallback("posts.undraft.sync", PostsSetPostedAt);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsEditIncreaseFrontpagePostCount (post, oldPost) {
  if (post.frontpageDate && !oldPost.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.edit.async", postsEditIncreaseFrontpagePostCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsNewIncreaseFrontpageCount (post) {
  if (post.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.new.async", postsNewIncreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsRemoveDecreaseFrontpageCount (post) {
  if (post.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.remove.async", postsRemoveDecreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved out of frontpage
 */
function postsEditDecreaseFrontpagePostCount (post, oldPost) {
  if (!post.frontpageDate && oldPost.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.edit.async", postsEditDecreaseFrontpagePostCount);

Posts.convertFromContentAsync = async function(content) {
  const contentState = convertFromRaw(await preProcessLatex(content));
  return {htmlBody: draftToHTML(contentState)}
}

Posts.createHtmlHighlight = (body, id, slug, wordCount) => {
  const highlight = body.replace(/< refresh to render LaTeX >/g, "< LaTeX Equation >")
  if (body.length > 2400) {
    // drop the last paragraph
    const highlight2400Shortened = highlight.slice(0,2400).split("\n").slice(0,-1).join("\n")
    const highlightnewlineShortened = highlight.split("\n\n").slice(0,5).join("\n\n")
    if (highlightnewlineShortened.length > highlight2400Shortened.length) {
      return marked(highlight2400Shortened)
    } else {
      return marked(highlightnewlineShortened)
    }
  } else {
    return marked(highlight)
  }
}

Posts.createExcerpt = (body) => {
  const excerpt = body.slice(0,400).split("[").slice(0, -1).join('[')
  const excerptWithReadMore = excerpt + `... <span class="post-excerpt-read-more">(Read More)</span>`
  return marked(excerptWithReadMore)
}

/*ws
 * @summary Takes in a content field, returns object with {htmlBody, body, excerpt}
*/

Posts.convertFromContent = (content, id, slug) => {
  const contentState = convertFromRaw(content);
  const htmlBody = draftToHTML(contentState)
  const body = turndownService.turndown(htmlBody)
  const excerpt = Posts.createExcerpt(body)
  const wordCount = body.split(" ").length
  const htmlHighlight = Posts.createHtmlHighlight(body, id, slug, wordCount)
  return {
    htmlBody: htmlBody,
    body: body,
    excerpt: excerpt,
    htmlHighlight: htmlHighlight,
    wordCount: wordCount
  }
}

/*
 * @summary Input is html, returns object with {body, excerpt}
*/

Posts.convertFromHTML = (html, id, slug) => {
  const body = turndownService.turndown(html)
  const excerpt = Posts.createExcerpt(body)
  const wordCount = body.split(" ").length
  const htmlHighlight = Posts.createHtmlHighlight(body, id, slug, wordCount)
  return {
    body,
    excerpt,
    wordCount,
    htmlHighlight
  }
}

function PostsNewHTMLSerializeCallback (post) {
  if (post.content) {
    const newPostFields = Posts.convertFromContent(post.content, post._id, post.slug);
    post = {...post, ...newPostFields}
  } else if (post.htmlBody) {
    const newPostFields = Posts.convertFromHTML(post.htmlBody, post._id, post.slug);
    post = {...post, ...newPostFields}
  }
  return post
}

addCallback("posts.new.sync", PostsNewHTMLSerializeCallback);

function PostsEditHTMLSerializeCallback (modifier, post) {
  if (modifier.$set && modifier.$set.content) {
    const newPostFields = Posts.convertFromContent(modifier.$set.content, post._id, post.slug)
    modifier.$set = {...modifier.$set, ...newPostFields}
    delete modifier.$unset.htmlBody;
  } else if (modifier.$set && modifier.$set.htmlBody) {
    const newPostFields = Posts.convertFromHTML(modifier.$set.htmlBody, post._id, post.slug);
    modifier.$set = {...modifier.$set, ...newPostFields}
  }
  return modifier
}

addCallback("posts.edit.sync", PostsEditHTMLSerializeCallback);

async function PostsEditHTMLSerializeCallbackAsync (post) {
  if (post.content) {
    const newPostFields = await Posts.convertFromContentAsync(post.content);
    Posts.update({_id: post._id}, {$set: newPostFields})
  } else if (post.htmlBody) {
    const newPostFields = Posts.convertFromHTML(post.htmlBody, post._id, post.slug);
    Posts.update({_id: post._id}, {$set: newPostFields})
  }
}

addCallback("posts.edit.async", PostsEditHTMLSerializeCallbackAsync);
addCallback("posts.new.async", PostsEditHTMLSerializeCallbackAsync);

function increaseMaxBaseScore ({newDocument, vote}, collection, user, context) {
  if (vote.collectionName === "Posts" && newDocument.baseScore > (newDocument.maxBaseScore || 0)) {
    Posts.update({_id: newDocument._id}, {$set: {maxBaseScore: newDocument.baseScore}})
  }
}

addCallback("votes.upvote.async", increaseMaxBaseScore);

function PostsNewDefaultLocation (post) {
  if (post.isEvent && post.groupId && !post.location) {
    const { location, googleLocation, mongoLocation } = Localgroups.findOne(post.groupId)
    post = {...post, location, googleLocation, mongoLocation}
  }
  return post
}

addCallback("posts.new.sync", PostsNewDefaultLocation);

function PostsNewDefaultTypes (post) {
  if (post.isEvent && post.groupId && !post.types) {
    const { types } = Localgroups.findOne(post.groupId)
    post = {...post, types}
  }
  return post
}

addCallback("posts.new.sync", PostsNewDefaultTypes);
<commit_msg>Excerpt without links doesn't disappear<commit_after>import { addCallback, runCallbacks, runCallbacksAsync } from 'meteor/vulcan:core';
import { Posts } from 'meteor/example-forum';
import Users from 'meteor/vulcan:users';

import { convertFromRaw } from 'draft-js';
import { draftToHTML } from '../../editor/utils.js';
import { preProcessLatex } from '../../editor/server/utils.js';
import Localgroups from '../localgroups/collection.js';

import marked from 'marked';
import TurndownService from 'turndown';
const turndownService = new TurndownService()

function PostsEditRunPostUndraftedSyncCallbacks (modifier, post) {
  if (modifier.$set && modifier.$set.draft === false && post.draft) {
    modifier = runCallbacks("posts.undraft.sync", modifier, post);
  }
  return modifier;
}
addCallback("posts.edit.sync", PostsEditRunPostUndraftedSyncCallbacks);

function PostsEditRunPostUndraftedAsyncCallbacks (newPost, oldPost) {
  if (!newPost.draft && oldPost.draft) {
    runCallbacksAsync("posts.undraft.async", newPost, oldPost)
  }
  return newPost
}

addCallback("posts.edit.async", PostsEditRunPostUndraftedAsyncCallbacks);

/**
 * @summary set postedAt when a post is moved out of drafts
 */
function PostsSetPostedAt (modifier, post) {
  modifier.$set.postedAt = new Date();
  if (modifier.$unset) {
    delete modifier.$unset.postedAt;
  }
  return modifier;
}
addCallback("posts.undraft.sync", PostsSetPostedAt);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsEditIncreaseFrontpagePostCount (post, oldPost) {
  if (post.frontpageDate && !oldPost.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.edit.async", postsEditIncreaseFrontpagePostCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsNewIncreaseFrontpageCount (post) {
  if (post.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: 1}})
  }
}
addCallback("posts.new.async", postsNewIncreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved into frontpage
 */
function postsRemoveDecreaseFrontpageCount (post) {
  if (post.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.remove.async", postsRemoveDecreaseFrontpageCount);

/**
 * @summary update frontpagePostCount when post is moved out of frontpage
 */
function postsEditDecreaseFrontpagePostCount (post, oldPost) {
  if (!post.frontpageDate && oldPost.frontpageDate) {
    Users.update({_id: post.userId}, {$inc: {frontpagePostCount: -1}})
  }
}
addCallback("posts.edit.async", postsEditDecreaseFrontpagePostCount);

Posts.convertFromContentAsync = async function(content) {
  const contentState = convertFromRaw(await preProcessLatex(content));
  return {htmlBody: draftToHTML(contentState)}
}

Posts.createHtmlHighlight = (body, id, slug, wordCount) => {
  const highlight = body.replace(/< refresh to render LaTeX >/g, "< LaTeX Equation >")
  if (body.length > 2400) {
    // drop the last paragraph
    const highlight2400Shortened = highlight.slice(0,2400).split("\n").slice(0,-1).join("\n")
    const highlightnewlineShortened = highlight.split("\n\n").slice(0,5).join("\n\n")
    if (highlightnewlineShortened.length > highlight2400Shortened.length) {
      return marked(highlight2400Shortened)
    } else {
      return marked(highlightnewlineShortened)
    }
  } else {
    return marked(highlight)
  }
}

Posts.createExcerpt = (body) => {
  const excerpt = body.slice(0,400)
  if (excerpt.includes("[")) {
    const excerptTrimLink = excerpt.split("[").slice(0, -1).join('[')
    const excerptWithReadMore = excerptTrimLink + `... <span class="post-excerpt-read-more">(Read More)</span>`
    return marked(excerptWithReadMore)
  } else {
    const excerptWithReadMore = excerpt + `... <span class="post-excerpt-read-more">(Read More)</span>`
    return marked(excerptWithReadMore)
  }
}

/*ws
 * @summary Takes in a content field, returns object with {htmlBody, body, excerpt}
*/

Posts.convertFromContent = (content, id, slug) => {
  const contentState = convertFromRaw(content);
  const htmlBody = draftToHTML(contentState)
  const body = turndownService.turndown(htmlBody)
  const excerpt = Posts.createExcerpt(body)
  const wordCount = body.split(" ").length
  const htmlHighlight = Posts.createHtmlHighlight(body, id, slug, wordCount)
  return {
    htmlBody: htmlBody,
    body: body,
    excerpt: excerpt,
    htmlHighlight: htmlHighlight,
    wordCount: wordCount
  }
}

/*
 * @summary Input is html, returns object with {body, excerpt}
*/

Posts.convertFromHTML = (html, id, slug) => {
  const body = turndownService.turndown(html)
  const excerpt = Posts.createExcerpt(body)
  const wordCount = body.split(" ").length
  const htmlHighlight = Posts.createHtmlHighlight(body, id, slug, wordCount)
  return {
    body,
    excerpt,
    wordCount,
    htmlHighlight
  }
}

function PostsNewHTMLSerializeCallback (post) {
  if (post.content) {
    const newPostFields = Posts.convertFromContent(post.content, post._id, post.slug);
    post = {...post, ...newPostFields}
  } else if (post.htmlBody) {
    const newPostFields = Posts.convertFromHTML(post.htmlBody, post._id, post.slug);
    post = {...post, ...newPostFields}
  }
  return post
}

addCallback("posts.new.sync", PostsNewHTMLSerializeCallback);

function PostsEditHTMLSerializeCallback (modifier, post) {
  if (modifier.$set && modifier.$set.content) {
    const newPostFields = Posts.convertFromContent(modifier.$set.content, post._id, post.slug)
    modifier.$set = {...modifier.$set, ...newPostFields}
    delete modifier.$unset.htmlBody;
  } else if (modifier.$set && modifier.$set.htmlBody) {
    const newPostFields = Posts.convertFromHTML(modifier.$set.htmlBody, post._id, post.slug);
    modifier.$set = {...modifier.$set, ...newPostFields}
  }
  return modifier
}

addCallback("posts.edit.sync", PostsEditHTMLSerializeCallback);

async function PostsEditHTMLSerializeCallbackAsync (post) {
  if (post.content) {
    const newPostFields = await Posts.convertFromContentAsync(post.content);
    Posts.update({_id: post._id}, {$set: newPostFields})
  } else if (post.htmlBody) {
    const newPostFields = Posts.convertFromHTML(post.htmlBody, post._id, post.slug);
    Posts.update({_id: post._id}, {$set: newPostFields})
  }
}

addCallback("posts.edit.async", PostsEditHTMLSerializeCallbackAsync);
addCallback("posts.new.async", PostsEditHTMLSerializeCallbackAsync);

function increaseMaxBaseScore ({newDocument, vote}, collection, user, context) {
  if (vote.collectionName === "Posts" && newDocument.baseScore > (newDocument.maxBaseScore || 0)) {
    Posts.update({_id: newDocument._id}, {$set: {maxBaseScore: newDocument.baseScore}})
  }
}

addCallback("votes.upvote.async", increaseMaxBaseScore);

function PostsNewDefaultLocation (post) {
  if (post.isEvent && post.groupId && !post.location) {
    const { location, googleLocation, mongoLocation } = Localgroups.findOne(post.groupId)
    post = {...post, location, googleLocation, mongoLocation}
  }
  return post
}

addCallback("posts.new.sync", PostsNewDefaultLocation);

function PostsNewDefaultTypes (post) {
  if (post.isEvent && post.groupId && !post.types) {
    const { types } = Localgroups.findOne(post.groupId)
    post = {...post, types}
  }
  return post
}

addCallback("posts.new.sync", PostsNewDefaultTypes);
<|endoftext|>


<commit_before>//
// https://github.com/kogakure/gulp-tutorial/blob/master/gulp/config.js
//
var banner = [
    '/**',
    ' * <%= pkg.name %> - <%= pkg.description %>',
    ' * <%= pkg.author %>',
    ' * Version <%= pkg.version %>',
    ' * <%= pkg.license %> Licensed',
    ' */',
    ''].join('\n');

module.exports = {
    banner: banner,
    clean: {
        files: [
            'dist'
        ]
    },
    jshint: {
        src: [
            'index.js',
            'gulpfile.js',
            'src/**/*.js'
        ],
        options: require('../config/jshint')
    },
    uglify: {
        debug: {
            output: {
                beautify: true,
                comments: true
            },
            compress: false,
            mangle: false
        },
        dist: {
            compress: true,
            mangle: true
        }
    }
};
<commit_msg>Validate files under lib with jshint<commit_after>//
// https://github.com/kogakure/gulp-tutorial/blob/master/gulp/config.js
//
var banner = [
    '/**',
    ' * <%= pkg.name %> - <%= pkg.description %>',
    ' * <%= pkg.author %>',
    ' * Version <%= pkg.version %>',
    ' * <%= pkg.license %> Licensed',
    ' */',
    ''].join('\n');

module.exports = {
    banner: banner,
    clean: {
        files: [
            'dist'
        ]
    },
    jshint: {
        src: [
            'index.js',
            'gulpfile.js',
            'src/**/*.js',
            'lib/**/*.js'
        ],
        options: require('../config/jshint')
    },
    uglify: {
        debug: {
            output: {
                beautify: true,
                comments: true
            },
            compress: false,
            mangle: false
        },
        dist: {
            compress: true,
            mangle: true
        }
    }
};
<|endoftext|>


<commit_before>import _ from 'underscore';
import util from 'util';
import path from 'path';
import assert from 'assert';
import chalk from 'chalk';

import isopackets from '../tool-env/isopackets.js'
import files from '../fs/files.js';
import utils from '../utils/utils.js';
import { Console } from '../console/console.js';
import buildmessage from '../utils/buildmessage.js';
import main from '../cli/main.js';
import httpHelpers from '../utils/http-helpers.js';

import { cordova as cordova_lib, events as cordova_events, CordovaError }
  from 'cordova-lib';
import cordova_util from 'cordova-lib/src/cordova/util.js';
import superspawn from 'cordova-lib/src/cordova/superspawn.js';
import PluginInfoProvider from 'cordova-lib/src/PluginInfoProvider.js';

import { AVAILABLE_PLATFORMS, displayNameForPlatform } from './index.js';
import { CordovaBuilder } from './builder.js';

cordova_events.on('results', logIfVerbose);
cordova_events.on('log', logIfVerbose);
cordova_events.on('warn', log);

function logIfVerbose(...args) {
  if (Console.verbose) {
    log(...args);
  }
};

function log(...args) {
  Console.rawInfo(`%% ${util.format.apply(null, args)}\n`);
}

export class CordovaProject {
  constructor(projectContext,
    appName = files.pathBasename(projectContext.projectDir)) {
    this.projectContext = projectContext;

    this.projectRoot = projectContext.getProjectLocalDirectory('cordova-build');
    this.appName = appName;

    this.pluginsDir = files.pathJoin(this.projectRoot, 'plugins');

    this.createIfNeeded();
  }

  createIfNeeded() {
    if (!files.exists(this.projectRoot)) {
      files.mkdir_p(files.pathDirname(this.projectRoot));
      // Cordova app identifiers have to look like Java namespaces.
      // Change weird characters (especially hyphens) into underscores.
      const appId = 'com.meteor.userapps.' +
        this.appName.replace(/[^a-zA-Z\d_$.]/g, '_');

      const templatePath = files.convertToOSPath(files.pathJoin(
        files.getCurrentToolsDir(),
        'tools/node_modules/cordova-app-hello-world'));

      const config = { lib: { www: { url: templatePath } } };

      // Don't set cwd to project root in runCommands because it doesn't exist yet
      this.runCommands('creating Cordova project', async () => {
        await cordova_lib.raw.create(files.convertToOSPath(this.projectRoot),
          appId, this.appName, config);
      }, undefined, null);
    }
  }

  // Preparing

  prepareFromAppBundle(bundlePath, plugins, options = {}) {
    assert(bundlePath);
    assert(plugins);

    buildmessage.assertInCapture();

    Console.debug('Preparing Cordova project from app bundle');

    buildmessage.enterJob({ title: `preparing Cordova project` }, () => {
      const builder = new CordovaBuilder(this, bundlePath, options);

      builder.processControlFile();

      if (buildmessage.jobHasMessages()) return;

      builder.writeConfigXmlAndCopyResources();
      builder.copyWWW();
      builder.copyBuildOverride();

      this.ensurePlatformsAreSynchronized();
      this.ensurePluginsAreSynchronized(plugins,
        builder.pluginsConfiguration);
    })
  }

  prepareForPlatform(platform) {
    assert(platform);

    const commandOptions = _.extend(this.defaultOptions,
      { platforms: [platform] });

    this.runCommands(`preparing Cordova project for platform \
${displayNameForPlatform(platform)}`, async () => {
      await cordova_lib.raw.prepare(commandOptions);
    });
  }

  // Building (includes prepare)

  buildForPlatform(platform, options = [], extraPaths) {
    assert(platform);

    const commandOptions = _.extend(this.defaultOptions,
      { platforms: [platform], options: options });

    this.runCommands(`building Cordova project for platform \
${displayNameForPlatform(platform)}`, async () => {
      await cordova_lib.raw.build(commandOptions);
    });
  }

  // Running (incudes build)

  async run(platform, isDevice, options = [], extraPaths) {
    options.push(isDevice ? '--device' : '--emulator');

    const env = this.defaultEnvWithPathsAdded(...extraPaths);
    const commandOptions = _.extend(this.defaultOptions,
      { platforms: [platform], options: options });

    this.runCommands(`running Cordova project for platform \
${displayNameForPlatform(platform)} with options ${options}`, async () => {
      await cordova_lib.raw.run(commandOptions);
    }, env);
  }

  // Platforms

  checkPlatformRequirements(platform) {
    if (platform === 'ios' && process.platform !== 'darwin') {
      Console.warn("Currently, it is only possible to build iOS apps \
on an OS X system.");
      return false;
    }

    const installedPlatforms = this.listInstalledPlatforms();
    const inProject = _.contains(installedPlatforms, platform);
    if (!inProject) {
      Console.warn(`Please add the ${displayNameForPlatform(platform)} \
platform to your project first.`);
      Console.info(`Run: ${Console.command(`meteor add-platform ${platform}`)}`);
      return false;
    }

    const allRequirements = this.runCommands(`checking Cordova \
requirements for platform ${displayNameForPlatform(platform)}`,
      async () => {
        return await cordova_lib.raw.requirements([platform],
          this.defaultOptions);
      });
    let requirements = allRequirements && allRequirements[platform];
    if (!requirements) {
      Console.error(`Failed to check requirements for platform \
${displayNameForPlatform(platform)}`);
      return false;
    } else if (requirements instanceof CordovaError) {
      Console.error(`cordova: ${requirements.message}`);
      return false;
    }

    // We don't use ios-deploy, but open Xcode to run on a device instead
    requirements = _.reject(requirements,
      requirement => requirement.id === 'ios-deploy');

    const satisfied = _.every(requirements,
      requirement => requirement.installed);

    if (!satisfied) {
      Console.info();
      Console.info(`Make sure all installation requirements are satisfied \
before running or building for ${displayNameForPlatform(platform)}:`);
      for (requirement of requirements) {
        const name = requirement.name;
        if (requirement.installed) {
          Console.success(name);
        } else {
          const reason = requirement.metadata && requirement.metadata.reason;
          if (reason) {
            Console.failInfo(`${name}: ${reason}`);
          } else {
            Console.failInfo(name);
          }
        }
      }
    }
    return satisfied;
  }

  listInstalledPlatforms() {
    return cordova_util.listPlatforms(files.convertToOSPath(this.projectRoot));
  }

  updatePlatforms(platforms = this.listInstalledPlatforms()) {
    this.runCommands(`updating Cordova project for platforms \
${displayNamesForPlatforms(platform)}`, async () => {
      await cordova_lib.raw.platform('update', platforms, this.defaultOptions);
    });
  }

  addPlatform(platform) {
    this.runCommands(`adding platform ${displayNameForPlatform(platform)} \
to Cordova project`, async () => {
      await cordova_lib.raw.platform('add', platform, this.defaultOptions);
    });
  }

  removePlatform(platform) {
    this.runCommands(`removing platform ${displayNameForPlatform(platform)} \
from Cordova project`, async () => {
      await cordova_lib.raw.platform('rm', platform, this.defaultOptions);
    });
  }

  get cordovaPlatformsInApp() {
    return this.projectContext.platformList.getCordovaPlatforms();
  }

  // Ensures that the Cordova platforms are synchronized with the app-level
  // platforms.
  ensurePlatformsAreSynchronized(platforms = this.cordovaPlatformsInApp) {
    buildmessage.assertInCapture();

    const installedPlatforms = this.listInstalledPlatforms();

    for (platform of platforms) {
      if (_.contains(installedPlatforms, platform)) continue;

      this.addPlatform(platform);
    }

    for (platform of installedPlatforms) {
      if (!_.contains(platforms, platform) &&
        _.contains(AVAILABLE_PLATFORMS, platform)) {
        this.removePlatform(platform);
      }
    }
  }

  // Plugins

  listInstalledPlugins() {
    const pluginInfoProvider = new PluginInfoProvider();
    const installedPlugins = pluginInfoProvider.getAllWithinSearchPath(
      files.convertToOSPath(this.pluginsDir));
    const fetchedPlugins = this.listFetchedPlugins();
    return _.object(installedPlugins.map(plugin => {
      const id = plugin.id;
      const version = fetchedPlugins[id] || plugin.version;
      return [id, version];
    }));
  }

  listFetchedPlugins() {
    const fetchedPluginsMetadata = JSON.parse(files.readFile(
      files.pathJoin(this.pluginsDir, 'fetch.json'), 'utf8'));
    return _.object(_.map(fetchedPluginsMetadata, (metadata, name) => {
      const source = metadata.source;
      let version;
      if (source.type === 'registry') {
        version = source.id.split('@')[1];
      } else if (source.type === 'git') {
        version = `${source.url}#${source.ref}`;
      } else if (source.type === 'local') {
        version = source.path;
      }
      return [name, version];
    }));
  }

  targetForPlugin(name, version) {
    if (!version) {
      return name;
    }

    if (utils.isUrlWithSha(version)) {
      return this.convertToGitUrl(version);
    } else if (utils.isUrlWithFileScheme(version)) {
      // Strip file:// and resolve the path relative to the cordova-build
      // directory
      const pluginPath = this.resolveLocalPluginPath(version);
      // We need to check if the directory exists ourselves because Cordova
      // will try to install from npm (and fail with an unhelpful error message)
      // if the directory is not found
      if (!cordova_util.isDirectory(pluginPath)) {
        buildmessage.error(`Couldn't find local directory \
'${files.convertToOSPath(pluginPath)}'. \
(Attempting to install plugin ${name}).`);
        return null;
      }
      return files.convertToOSPath(pluginPath);
    } else {
      return `${name}@${version}`;
    }
  }

  convertToGitUrl(url) {
    // Matches GitHub tarball URLs, like:
    // https://github.com/meteor/com.meteor.cordova-update/tarball/92fe99b7248075318f6446b288995d4381d24cd2
    const match =
      url.match(/^https?:\/\/github.com\/(.+?)\/(.+?)\/tarball\/([0-9a-f]{40})/);
    if (match) {
        const [,organization,repository,sha] = match;
      // Convert them to a Git URL
      return `https://github.com/${organization}/${repository}.git#${sha}`;
    } else if (/\.git#[0-9a-f]{40}/.test(url)) {
      return url;
    } else {
      buildmessage.error(`Meteor no longer supports installing Cordova plugins \
from arbitrary tarball URLs. You can either add a plugin from a Git URL with \
a SHA reference, or from a local path. (Attempting to install from ${url}.)`);
      return null;
    }
  }

  // Strips file:// and resolves the path relative to the cordova-build
  // directory
  resolveLocalPluginPath(pluginPath) {
    pluginPath = pluginPath.substr("file://".length);
    if (utils.isPathRelative(pluginPath)) {
      return path.resolve(this.projectContext.projectDir, pluginPath);
    } else {
      return pluginPath;
    }
  }

  addPlugin(name, version, config = {}) {
    const target = this.targetForPlugin(name, version);
    if (target) {
      const commandOptions = _.extend(this.defaultOptions,
        { cli_variables: config });

      this.runCommands(`adding plugin ${target} \
to Cordova project`, async () => {
        await cordova_lib.raw.plugin('add', [target], commandOptions);
      });
    }
  }

  removePlugins(plugins) {
    if (_.isEmpty(plugins)) return;

    this.runCommands(`removing plugins ${plugins} \
from Cordova project`, async () => {
      await cordova_lib.raw.plugin('rm', plugins, this.defaultOptions);
    });
  }

  // Ensures that the Cordova plugins are synchronized with the app-level
  // plugins.
  ensurePluginsAreSynchronized(plugins, pluginsConfiguration = {}) {
    assert(plugins);

    buildmessage.assertInCapture();

    buildmessage.enterJob({ title: "installing Cordova plugins"}, () => {
      const installedPlugins = this.listInstalledPlugins();

      // Due to the dependency structure of Cordova plugins, it is impossible to
      // upgrade the version on an individual Cordova plugin. Instead, whenever a
      // new Cordova plugin is added or removed, or its version is changed,
      // we just reinstall all of the plugins.
      let shouldReinstallAllPlugins = false;

      // Iterate through all of the plugins and find if any of them have a new
      // version. Additionally check if we have plugins installed from local path.
      const pluginsFromLocalPath = {};
      _.each(plugins, (version, name) => {
        // Check if plugin is installed from local path
        const isPluginFromLocalPath = utils.isUrlWithFileScheme(version);

        if (isPluginFromLocalPath) {
          pluginsFromLocalPath[name] = version;
        } else {
          if (utils.isUrlWithSha(version)) {
            version = this.convertToGitUrl(version);
          }

          if (!_.has(installedPlugins, name) ||
            installedPlugins[name] !== version) {
            // We do not have the plugin installed or the version has changed.
            shouldReinstallAllPlugins = true;
          }
        }
      });

      if (!_.isEmpty(pluginsFromLocalPath)) {
        Console.debug('Reinstalling Cordova plugins added from the local path');
      }

      // Check to see if we have any installed plugins that are not in the current
      // set of plugins.
      _.each(installedPlugins, (version, name) => {
        if (!_.has(plugins, name)) {
          shouldReinstallAllPlugins = true;
        }
      });

      if (shouldReinstallAllPlugins || !_.isEmpty(pluginsFromLocalPath)) {
        let pluginsToRemove;
        if (shouldReinstallAllPlugins) {
          pluginsToRemove = Object.keys(installedPlugins);
        } else {
          // Only try to remove plugins that are currently installed
          pluginsToRemove = _.intersection(
            Object.keys(pluginsFromLocalPath),
            Object.keys(installedPlugins));
        }

        this.removePlugins(pluginsToRemove);

        // Now install necessary plugins.

        if (shouldReinstallAllPlugins) {
          pluginsToInstall = plugins;
        } else {
          pluginsToInstall = pluginsFromLocalPath;
        }

        const pluginsCount = _.size(pluginsToInstall);
        let pluginsInstalled = 0;

        buildmessage.reportProgress({ current: 0, end: pluginsCount });
        _.each(pluginsToInstall, (version, name) => {
          this.addPlugin(name, version, pluginsConfiguration[name]);

          if (buildmessage.jobHasMessages()) return;

          buildmessage.reportProgress({
            current: ++pluginsInstalled,
            end: pluginsCount
          });
        });
      }
    });
  }

  // Cordova commands support

  get defaultOptions() {
    return { silent: !Console.verbose, verbose: Console.verbose };
  }

  defaultEnvWithPathsAdded(...extraPaths) {
    let paths = (this.defaultPaths || []);
    paths.unshift(...extraPaths);
    const env = files.currentEnvWithPathsAdded(...paths);
    return env;
  }

  get defaultPaths() {
    const nodeBinDir = files.getCurrentNodeBinDir();

    // Add the ios-sim bin path so Cordova can find it
    const iosSimBinPath =
      files.pathJoin(files.getDevBundle(),
      'lib/node_modules/ios-sim/bin');

    return [nodeBinDir, iosSimBinPath];
  }

  runCommands(title, asyncFunc, env = this.defaultEnvWithPathsAdded(),
    cwd = this.projectRoot) {
    // Capitalize title for debug output
    Console.debug(title[0].toUpperCase() + title.slice(1));

    const oldCwd = process.cwd();
    if (cwd) {
      process.chdir(files.convertToOSPath(cwd));
    }

    const oldEnv = process.env;
    if (env) {
      process.env = env;
    }

    try {
      return Promise.await(asyncFunc());
    } catch (error) {
      if (error instanceof CordovaError) {
        Console.arrowError('Errors executing Cordova commands:');
        Console.error();
        const consoleOptions = Console.options({ indent: 3 });
        Console.error(`While ${title}:`, consoleOptions);
        const errorMessage = Console.verbose ? (error.stack || error.message) :
          error.message;
        Console.error(errorMessage, consoleOptions);
        Console.error(chalk.green("(If the error message contains suggestions \
for a fix, note that this may not apply to the Meteor integration. You can try \
running again with the --verbose option to help diagnose the issue.)"),
          consoleOptions);
        throw new main.ExitWithCode(1);
      } else {
        throw error;
      }
    } finally {
      if (oldCwd) {
        process.chdir(oldCwd);
      }
      if (oldEnv) {
        process.env = oldEnv;
      }
    }
  }
}
<commit_msg>Fix Cordova project template path<commit_after>import _ from 'underscore';
import util from 'util';
import path from 'path';
import assert from 'assert';
import chalk from 'chalk';

import isopackets from '../tool-env/isopackets.js'
import files from '../fs/files.js';
import utils from '../utils/utils.js';
import { Console } from '../console/console.js';
import buildmessage from '../utils/buildmessage.js';
import main from '../cli/main.js';
import httpHelpers from '../utils/http-helpers.js';

import { cordova as cordova_lib, events as cordova_events, CordovaError }
  from 'cordova-lib';
import cordova_util from 'cordova-lib/src/cordova/util.js';
import superspawn from 'cordova-lib/src/cordova/superspawn.js';
import PluginInfoProvider from 'cordova-lib/src/PluginInfoProvider.js';

import { AVAILABLE_PLATFORMS, displayNameForPlatform } from './index.js';
import { CordovaBuilder } from './builder.js';

cordova_events.on('results', logIfVerbose);
cordova_events.on('log', logIfVerbose);
cordova_events.on('warn', log);

function logIfVerbose(...args) {
  if (Console.verbose) {
    log(...args);
  }
};

function log(...args) {
  Console.rawInfo(`%% ${util.format.apply(null, args)}\n`);
}

export class CordovaProject {
  constructor(projectContext,
    appName = files.pathBasename(projectContext.projectDir)) {
    this.projectContext = projectContext;

    this.projectRoot = projectContext.getProjectLocalDirectory('cordova-build');
    this.appName = appName;

    this.pluginsDir = files.pathJoin(this.projectRoot, 'plugins');

    this.createIfNeeded();
  }

  createIfNeeded() {
    if (!files.exists(this.projectRoot)) {
      files.mkdir_p(files.pathDirname(this.projectRoot));
      // Cordova app identifiers have to look like Java namespaces.
      // Change weird characters (especially hyphens) into underscores.
      const appId = 'com.meteor.userapps.' +
        this.appName.replace(/[^a-zA-Z\d_$.]/g, '_');

      const templatePath = files.convertToOSPath(files.pathJoin(
        files.getDevBundle(),
        'lib/node_modules/cordova-app-hello-world'));

      const config = { lib: { www: { url: templatePath } } };

      // Don't set cwd to project root in runCommands because it doesn't exist yet
      this.runCommands('creating Cordova project', async () => {
        await cordova_lib.raw.create(files.convertToOSPath(this.projectRoot),
          appId, this.appName, config);
      }, undefined, null);
    }
  }

  // Preparing

  prepareFromAppBundle(bundlePath, plugins, options = {}) {
    assert(bundlePath);
    assert(plugins);

    buildmessage.assertInCapture();

    Console.debug('Preparing Cordova project from app bundle');

    buildmessage.enterJob({ title: `preparing Cordova project` }, () => {
      const builder = new CordovaBuilder(this, bundlePath, options);

      builder.processControlFile();

      if (buildmessage.jobHasMessages()) return;

      builder.writeConfigXmlAndCopyResources();
      builder.copyWWW();
      builder.copyBuildOverride();

      this.ensurePlatformsAreSynchronized();
      this.ensurePluginsAreSynchronized(plugins,
        builder.pluginsConfiguration);
    })
  }

  prepareForPlatform(platform) {
    assert(platform);

    const commandOptions = _.extend(this.defaultOptions,
      { platforms: [platform] });

    this.runCommands(`preparing Cordova project for platform \
${displayNameForPlatform(platform)}`, async () => {
      await cordova_lib.raw.prepare(commandOptions);
    });
  }

  // Building (includes prepare)

  buildForPlatform(platform, options = [], extraPaths) {
    assert(platform);

    const commandOptions = _.extend(this.defaultOptions,
      { platforms: [platform], options: options });

    this.runCommands(`building Cordova project for platform \
${displayNameForPlatform(platform)}`, async () => {
      await cordova_lib.raw.build(commandOptions);
    });
  }

  // Running (incudes build)

  async run(platform, isDevice, options = [], extraPaths) {
    options.push(isDevice ? '--device' : '--emulator');

    const env = this.defaultEnvWithPathsAdded(...extraPaths);
    const commandOptions = _.extend(this.defaultOptions,
      { platforms: [platform], options: options });

    this.runCommands(`running Cordova project for platform \
${displayNameForPlatform(platform)} with options ${options}`, async () => {
      await cordova_lib.raw.run(commandOptions);
    }, env);
  }

  // Platforms

  checkPlatformRequirements(platform) {
    if (platform === 'ios' && process.platform !== 'darwin') {
      Console.warn("Currently, it is only possible to build iOS apps \
on an OS X system.");
      return false;
    }

    const installedPlatforms = this.listInstalledPlatforms();
    const inProject = _.contains(installedPlatforms, platform);
    if (!inProject) {
      Console.warn(`Please add the ${displayNameForPlatform(platform)} \
platform to your project first.`);
      Console.info(`Run: ${Console.command(`meteor add-platform ${platform}`)}`);
      return false;
    }

    const allRequirements = this.runCommands(`checking Cordova \
requirements for platform ${displayNameForPlatform(platform)}`,
      async () => {
        return await cordova_lib.raw.requirements([platform],
          this.defaultOptions);
      });
    let requirements = allRequirements && allRequirements[platform];
    if (!requirements) {
      Console.error(`Failed to check requirements for platform \
${displayNameForPlatform(platform)}`);
      return false;
    } else if (requirements instanceof CordovaError) {
      Console.error(`cordova: ${requirements.message}`);
      return false;
    }

    // We don't use ios-deploy, but open Xcode to run on a device instead
    requirements = _.reject(requirements,
      requirement => requirement.id === 'ios-deploy');

    const satisfied = _.every(requirements,
      requirement => requirement.installed);

    if (!satisfied) {
      Console.info();
      Console.info(`Make sure all installation requirements are satisfied \
before running or building for ${displayNameForPlatform(platform)}:`);
      for (requirement of requirements) {
        const name = requirement.name;
        if (requirement.installed) {
          Console.success(name);
        } else {
          const reason = requirement.metadata && requirement.metadata.reason;
          if (reason) {
            Console.failInfo(`${name}: ${reason}`);
          } else {
            Console.failInfo(name);
          }
        }
      }
    }
    return satisfied;
  }

  listInstalledPlatforms() {
    return cordova_util.listPlatforms(files.convertToOSPath(this.projectRoot));
  }

  updatePlatforms(platforms = this.listInstalledPlatforms()) {
    this.runCommands(`updating Cordova project for platforms \
${displayNamesForPlatforms(platform)}`, async () => {
      await cordova_lib.raw.platform('update', platforms, this.defaultOptions);
    });
  }

  addPlatform(platform) {
    this.runCommands(`adding platform ${displayNameForPlatform(platform)} \
to Cordova project`, async () => {
      await cordova_lib.raw.platform('add', platform, this.defaultOptions);
    });
  }

  removePlatform(platform) {
    this.runCommands(`removing platform ${displayNameForPlatform(platform)} \
from Cordova project`, async () => {
      await cordova_lib.raw.platform('rm', platform, this.defaultOptions);
    });
  }

  get cordovaPlatformsInApp() {
    return this.projectContext.platformList.getCordovaPlatforms();
  }

  // Ensures that the Cordova platforms are synchronized with the app-level
  // platforms.
  ensurePlatformsAreSynchronized(platforms = this.cordovaPlatformsInApp) {
    buildmessage.assertInCapture();

    const installedPlatforms = this.listInstalledPlatforms();

    for (platform of platforms) {
      if (_.contains(installedPlatforms, platform)) continue;

      this.addPlatform(platform);
    }

    for (platform of installedPlatforms) {
      if (!_.contains(platforms, platform) &&
        _.contains(AVAILABLE_PLATFORMS, platform)) {
        this.removePlatform(platform);
      }
    }
  }

  // Plugins

  listInstalledPlugins() {
    const pluginInfoProvider = new PluginInfoProvider();
    const installedPlugins = pluginInfoProvider.getAllWithinSearchPath(
      files.convertToOSPath(this.pluginsDir));
    const fetchedPlugins = this.listFetchedPlugins();
    return _.object(installedPlugins.map(plugin => {
      const id = plugin.id;
      const version = fetchedPlugins[id] || plugin.version;
      return [id, version];
    }));
  }

  listFetchedPlugins() {
    const fetchedPluginsMetadata = JSON.parse(files.readFile(
      files.pathJoin(this.pluginsDir, 'fetch.json'), 'utf8'));
    return _.object(_.map(fetchedPluginsMetadata, (metadata, name) => {
      const source = metadata.source;
      let version;
      if (source.type === 'registry') {
        version = source.id.split('@')[1];
      } else if (source.type === 'git') {
        version = `${source.url}#${source.ref}`;
      } else if (source.type === 'local') {
        version = source.path;
      }
      return [name, version];
    }));
  }

  targetForPlugin(name, version) {
    if (!version) {
      return name;
    }

    if (utils.isUrlWithSha(version)) {
      return this.convertToGitUrl(version);
    } else if (utils.isUrlWithFileScheme(version)) {
      // Strip file:// and resolve the path relative to the cordova-build
      // directory
      const pluginPath = this.resolveLocalPluginPath(version);
      // We need to check if the directory exists ourselves because Cordova
      // will try to install from npm (and fail with an unhelpful error message)
      // if the directory is not found
      if (!cordova_util.isDirectory(pluginPath)) {
        buildmessage.error(`Couldn't find local directory \
'${files.convertToOSPath(pluginPath)}'. \
(Attempting to install plugin ${name}).`);
        return null;
      }
      return files.convertToOSPath(pluginPath);
    } else {
      return `${name}@${version}`;
    }
  }

  convertToGitUrl(url) {
    // Matches GitHub tarball URLs, like:
    // https://github.com/meteor/com.meteor.cordova-update/tarball/92fe99b7248075318f6446b288995d4381d24cd2
    const match =
      url.match(/^https?:\/\/github.com\/(.+?)\/(.+?)\/tarball\/([0-9a-f]{40})/);
    if (match) {
        const [,organization,repository,sha] = match;
      // Convert them to a Git URL
      return `https://github.com/${organization}/${repository}.git#${sha}`;
    } else if (/\.git#[0-9a-f]{40}/.test(url)) {
      return url;
    } else {
      buildmessage.error(`Meteor no longer supports installing Cordova plugins \
from arbitrary tarball URLs. You can either add a plugin from a Git URL with \
a SHA reference, or from a local path. (Attempting to install from ${url}.)`);
      return null;
    }
  }

  // Strips file:// and resolves the path relative to the cordova-build
  // directory
  resolveLocalPluginPath(pluginPath) {
    pluginPath = pluginPath.substr("file://".length);
    if (utils.isPathRelative(pluginPath)) {
      return path.resolve(this.projectContext.projectDir, pluginPath);
    } else {
      return pluginPath;
    }
  }

  addPlugin(name, version, config = {}) {
    const target = this.targetForPlugin(name, version);
    if (target) {
      const commandOptions = _.extend(this.defaultOptions,
        { cli_variables: config });

      this.runCommands(`adding plugin ${target} \
to Cordova project`, async () => {
        await cordova_lib.raw.plugin('add', [target], commandOptions);
      });
    }
  }

  removePlugins(plugins) {
    if (_.isEmpty(plugins)) return;

    this.runCommands(`removing plugins ${plugins} \
from Cordova project`, async () => {
      await cordova_lib.raw.plugin('rm', plugins, this.defaultOptions);
    });
  }

  // Ensures that the Cordova plugins are synchronized with the app-level
  // plugins.
  ensurePluginsAreSynchronized(plugins, pluginsConfiguration = {}) {
    assert(plugins);

    buildmessage.assertInCapture();

    buildmessage.enterJob({ title: "installing Cordova plugins"}, () => {
      const installedPlugins = this.listInstalledPlugins();

      // Due to the dependency structure of Cordova plugins, it is impossible to
      // upgrade the version on an individual Cordova plugin. Instead, whenever a
      // new Cordova plugin is added or removed, or its version is changed,
      // we just reinstall all of the plugins.
      let shouldReinstallAllPlugins = false;

      // Iterate through all of the plugins and find if any of them have a new
      // version. Additionally check if we have plugins installed from local path.
      const pluginsFromLocalPath = {};
      _.each(plugins, (version, name) => {
        // Check if plugin is installed from local path
        const isPluginFromLocalPath = utils.isUrlWithFileScheme(version);

        if (isPluginFromLocalPath) {
          pluginsFromLocalPath[name] = version;
        } else {
          if (utils.isUrlWithSha(version)) {
            version = this.convertToGitUrl(version);
          }

          if (!_.has(installedPlugins, name) ||
            installedPlugins[name] !== version) {
            // We do not have the plugin installed or the version has changed.
            shouldReinstallAllPlugins = true;
          }
        }
      });

      if (!_.isEmpty(pluginsFromLocalPath)) {
        Console.debug('Reinstalling Cordova plugins added from the local path');
      }

      // Check to see if we have any installed plugins that are not in the current
      // set of plugins.
      _.each(installedPlugins, (version, name) => {
        if (!_.has(plugins, name)) {
          shouldReinstallAllPlugins = true;
        }
      });

      if (shouldReinstallAllPlugins || !_.isEmpty(pluginsFromLocalPath)) {
        let pluginsToRemove;
        if (shouldReinstallAllPlugins) {
          pluginsToRemove = Object.keys(installedPlugins);
        } else {
          // Only try to remove plugins that are currently installed
          pluginsToRemove = _.intersection(
            Object.keys(pluginsFromLocalPath),
            Object.keys(installedPlugins));
        }

        this.removePlugins(pluginsToRemove);

        // Now install necessary plugins.

        if (shouldReinstallAllPlugins) {
          pluginsToInstall = plugins;
        } else {
          pluginsToInstall = pluginsFromLocalPath;
        }

        const pluginsCount = _.size(pluginsToInstall);
        let pluginsInstalled = 0;

        buildmessage.reportProgress({ current: 0, end: pluginsCount });
        _.each(pluginsToInstall, (version, name) => {
          this.addPlugin(name, version, pluginsConfiguration[name]);

          if (buildmessage.jobHasMessages()) return;

          buildmessage.reportProgress({
            current: ++pluginsInstalled,
            end: pluginsCount
          });
        });
      }
    });
  }

  // Cordova commands support

  get defaultOptions() {
    return { silent: !Console.verbose, verbose: Console.verbose };
  }

  defaultEnvWithPathsAdded(...extraPaths) {
    let paths = (this.defaultPaths || []);
    paths.unshift(...extraPaths);
    const env = files.currentEnvWithPathsAdded(...paths);
    return env;
  }

  get defaultPaths() {
    const nodeBinDir = files.getCurrentNodeBinDir();

    // Add the ios-sim bin path so Cordova can find it
    const iosSimBinPath =
      files.pathJoin(files.getDevBundle(),
      'lib/node_modules/ios-sim/bin');

    return [nodeBinDir, iosSimBinPath];
  }

  runCommands(title, asyncFunc, env = this.defaultEnvWithPathsAdded(),
    cwd = this.projectRoot) {
    // Capitalize title for debug output
    Console.debug(title[0].toUpperCase() + title.slice(1));

    const oldCwd = process.cwd();
    if (cwd) {
      process.chdir(files.convertToOSPath(cwd));
    }

    const oldEnv = process.env;
    if (env) {
      process.env = env;
    }

    try {
      return Promise.await(asyncFunc());
    } catch (error) {
      if (error instanceof CordovaError) {
        Console.arrowError('Errors executing Cordova commands:');
        Console.error();
        const consoleOptions = Console.options({ indent: 3 });
        Console.error(`While ${title}:`, consoleOptions);
        const errorMessage = Console.verbose ? (error.stack || error.message) :
          error.message;
        Console.error(errorMessage, consoleOptions);
        Console.error(chalk.green("(If the error message contains suggestions \
for a fix, note that this may not apply to the Meteor integration. You can try \
running again with the --verbose option to help diagnose the issue.)"),
          consoleOptions);
        throw new main.ExitWithCode(1);
      } else {
        throw error;
      }
    } finally {
      if (oldCwd) {
        process.chdir(oldCwd);
      }
      if (oldEnv) {
        process.env = oldEnv;
      }
    }
  }
}
<|endoftext|>


<commit_before>var as = require('../build/Release/aerospike.node')
var LargeList = require('./llist.js');
var LargeMap = require('./lmap.js');
var stream = require('stream');
var inherits = require('util').inherits;

/******************************************************************************
 * RecordStream - used for Scan and Query operations
 *****************************************************************************/

function RecordStream() {}

inherits(RecordStream, stream);

RecordStream.prototype.writable = false;
RecordStream.prototype.readable = true;
RecordStream.prototype._read = function(){};

/******************************************************************************
 * Streaming Scan Operation
 *****************************************************************************/

// wrapper function around query_foreach function.
// This wrapper receives results from V8 layer and emits 'data' event
// to node.js layer.
// On error, emits 'error' event.
// When all the results are consumed it emits 'end' event.
execute = function() {

	var rs = new RecordStream();

    var self = this;

	var onResult = function( res, key) {
		rs.emit('data', res);
	}

	var onError = function(error) {
		rs.emit('error', error);
	};

	var onEnd = function(end) {
		if( !self.isQuery && self.hasUDF ) {
			self.scanId = end;
			rs.emit('end', end);
		}
		else
		{
			rs.emit('end');
		}
	};

	if(self.isQuery) // it is a query request.
	{
		if(self.hasUDF)
		{
			// query UDF is not supported currently.
			return null;
		}
		else
		{
			// normal query and query aggregation is invoked here.
			self.foreach(onResult, onError, onEnd);
		}

	}
	else // it is a scan request
	{
		if(self.hasUDF) // scan with a UDF - so background scan.
		{
			// background scan does not return records. So callback for record is NULL.
			self.foreach(null, onError, onEnd);
		}
		else // it is a foreground scan or scan aggregation.
		{
			self.foreach(onResult, onError, onEnd);
		}

	}
	return rs;

}

Info = function(scanId, callback) {
	var self = this;
	self.queryInfo(scanId, callback);
}

query = function(ns, set, options) {

  if(typeof(set) != "string")
  {
	  set = '';
  }
  var queryObj = this.createQuery(ns, set);

  queryObj.isQuery = false; // true implies it is query instance.
							// false implies it is an instance of scan.
  queryObj.hasUDF = false;
  queryObj.hasAggregation = false;

  if( options && options.select) {
	  queryObj.select(options.select);
  }

  if( options && options.filters) {
	  queryObj.where(options.filters)
	  queryObj.isQuery = true;
  }

  if(options && 'recordQsize' in options) {
	  queryObj.setRecordQsize(options.recordQsize)
  }

  if ( options && 'aggregationUDF' in options) {
    queryObj.apply(options.aggregationUDF);
	queryObj.hasAggregation = true;
  }

  if ( options && 'UDF' in options) {
    queryObj.apply(options.UDF);
	queryObj.hasUDF = true;
  }

  if ( options && 'priority' in options) {
	  queryObj.setPriority(options.priority);
  }

  if( options && 'percent' in options) {
	  queryObj.setPercent(options.percent);
  }

  if( options && 'nobins' in options) {
	  queryObj.setNobins(options.nobins);
  }

  if(options && 'concurrent' in options) {
	  queryObj.setConcurrent(options.concurrent);
  }

  if(queryObj.isQuery)
  { // it is a query instance.
	  if(queryObj.hasUDF)
	  {
		  // query and has a UDF, implies query UDF - this is currently not supported.
		  queryObj.setQueryType(as.queryType.QUERYUDF);
	  }
	  else if(queryObj.hasAggregation)
	  {
		  // query and has aggregationUDF, this is query Aggregation
		  queryObj.setQueryType(as.queryType.QUERYAGGREGATION);
	  }
	  else
	  {
		  // query and does not have UDF or aggregationUDF.
		  // It is a normal query.
		  queryObj.setQueryType(as.queryType.QUERY);
	  }
  }
  else
  {
	  if(queryObj.hasUDF)
	  {
		  queryObj.setQueryType(as.queryType.SCANUDF);
	  }
	  else if(queryObj.hasAggregation)
	  {
		  queryObj.setQueryType(as.queryType.SCANAGGREGATION);
	  }
	  else
	  {
		  queryObj.setQueryType(as.queryType.SCAN);
	  }
  }

  var queryProto = Object.getPrototypeOf(queryObj);

  if( !queryProto.execute)
  {
	  queryProto.execute = execute;
  }

  if( !queryProto.Info)
  {
	  queryProto.Info = Info;
  }

  return queryObj;
}

createIntegerIndex = function(options, callback ){
	var policy = undefined;
	var set = undefined;
	if(options && options.policy){
		policy = options.policy;
	}
	if(options && options.set) {
		set = options.set;
	}
	this.indexCreate(options.ns, set, options.bin, options.index, as.indexType.NUMERIC, policy, callback)
}

createStringIndex = function(options, callback ){
	var policy = undefined;
	var set = undefined;
	if(options && options.policy){
		policy = options.policy;
	}
	if(options && options.set) {
		set = options.set;
	}
	this.indexCreate(options.ns, set, options.bin, options.index, as.indexType.STRING, policy, callback)

}
/******************************************************************************
 * `aerospike` shim
 *****************************************************************************/

var aerospike = {
  // client:   as.client,
  key:      as.key,
  status:   as.status,
  policy:   as.policy,
  operator: as.operator,
  language: as.language,
  log:      as.log,
  scanPriority: as.scanPriority,
  filter:	as.filter,
  indexType : as.indexType,
  scanStatus: as.scanStatus
};

aerospike.client = function(config) {
  var client = as.client(config);
  var proto = Object.getPrototypeOf(client);

  if( !proto.createQuery ) {
	  proto.createQuery = proto.query;
	  proto.query = query;
  }

  if( !proto.createIntegerIndex ) {
	  proto.createIntegerIndex = createIntegerIndex;
  }

  if( !proto.createStringIndex ) {
	  proto.createStringIndex = createStringIndex;
  }

  if( !proto.LargeList) {
	  proto.LargeList = LargeList;
  }
  return client;
};

module.exports = aerospike;
<commit_msg>Remove the lmap file checked in earlier<commit_after>var as = require('../build/Release/aerospike.node')
var LargeList = require('./llist.js');
var stream = require('stream');
var inherits = require('util').inherits;

/******************************************************************************
 * RecordStream - used for Scan and Query operations
 *****************************************************************************/

function RecordStream() {}

inherits(RecordStream, stream);

RecordStream.prototype.writable = false;
RecordStream.prototype.readable = true;
RecordStream.prototype._read = function(){};

/******************************************************************************
 * Streaming Scan Operation
 *****************************************************************************/

// wrapper function around query_foreach function.
// This wrapper receives results from V8 layer and emits 'data' event
// to node.js layer.
// On error, emits 'error' event.
// When all the results are consumed it emits 'end' event.
execute = function() {

	var rs = new RecordStream();

    var self = this;

	var onResult = function( res, key) {
		rs.emit('data', res);
	}

	var onError = function(error) {
		rs.emit('error', error);
	};

	var onEnd = function(end) {
		if( !self.isQuery && self.hasUDF ) {
			self.scanId = end;
			rs.emit('end', end);
		}
		else
		{
			rs.emit('end');
		}
	};

	if(self.isQuery) // it is a query request.
	{
		if(self.hasUDF)
		{
			// query UDF is not supported currently.
			return null;
		}
		else
		{
			// normal query and query aggregation is invoked here.
			self.foreach(onResult, onError, onEnd);
		}

	}
	else // it is a scan request
	{
		if(self.hasUDF) // scan with a UDF - so background scan.
		{
			// background scan does not return records. So callback for record is NULL.
			self.foreach(null, onError, onEnd);
		}
		else // it is a foreground scan or scan aggregation.
		{
			self.foreach(onResult, onError, onEnd);
		}

	}
	return rs;

}

Info = function(scanId, callback) {
	var self = this;
	self.queryInfo(scanId, callback);
}

query = function(ns, set, options) {

  if(typeof(set) != "string")
  {
	  set = '';
  }
  var queryObj = this.createQuery(ns, set);

  queryObj.isQuery = false; // true implies it is query instance.
							// false implies it is an instance of scan.
  queryObj.hasUDF = false;
  queryObj.hasAggregation = false;

  if( options && options.select) {
	  queryObj.select(options.select);
  }

  if( options && options.filters) {
	  queryObj.where(options.filters)
	  queryObj.isQuery = true;
  }

  if(options && 'recordQsize' in options) {
	  queryObj.setRecordQsize(options.recordQsize)
  }

  if ( options && 'aggregationUDF' in options) {
    queryObj.apply(options.aggregationUDF);
	queryObj.hasAggregation = true;
  }

  if ( options && 'UDF' in options) {
    queryObj.apply(options.UDF);
	queryObj.hasUDF = true;
  }

  if ( options && 'priority' in options) {
	  queryObj.setPriority(options.priority);
  }

  if( options && 'percent' in options) {
	  queryObj.setPercent(options.percent);
  }

  if( options && 'nobins' in options) {
	  queryObj.setNobins(options.nobins);
  }

  if(options && 'concurrent' in options) {
	  queryObj.setConcurrent(options.concurrent);
  }

  if(queryObj.isQuery)
  { // it is a query instance.
	  if(queryObj.hasUDF)
	  {
		  // query and has a UDF, implies query UDF - this is currently not supported.
		  queryObj.setQueryType(as.queryType.QUERYUDF);
	  }
	  else if(queryObj.hasAggregation)
	  {
		  // query and has aggregationUDF, this is query Aggregation
		  queryObj.setQueryType(as.queryType.QUERYAGGREGATION);
	  }
	  else
	  {
		  // query and does not have UDF or aggregationUDF.
		  // It is a normal query.
		  queryObj.setQueryType(as.queryType.QUERY);
	  }
  }
  else
  {
	  if(queryObj.hasUDF)
	  {
		  queryObj.setQueryType(as.queryType.SCANUDF);
	  }
	  else if(queryObj.hasAggregation)
	  {
		  queryObj.setQueryType(as.queryType.SCANAGGREGATION);
	  }
	  else
	  {
		  queryObj.setQueryType(as.queryType.SCAN);
	  }
  }

  var queryProto = Object.getPrototypeOf(queryObj);

  if( !queryProto.execute)
  {
	  queryProto.execute = execute;
  }

  if( !queryProto.Info)
  {
	  queryProto.Info = Info;
  }

  return queryObj;
}

createIntegerIndex = function(options, callback ){
	var policy = undefined;
	var set = undefined;
	if(options && options.policy){
		policy = options.policy;
	}
	if(options && options.set) {
		set = options.set;
	}
	this.indexCreate(options.ns, set, options.bin, options.index, as.indexType.NUMERIC, policy, callback)
}

createStringIndex = function(options, callback ){
	var policy = undefined;
	var set = undefined;
	if(options && options.policy){
		policy = options.policy;
	}
	if(options && options.set) {
		set = options.set;
	}
	this.indexCreate(options.ns, set, options.bin, options.index, as.indexType.STRING, policy, callback)

}
/******************************************************************************
 * `aerospike` shim
 *****************************************************************************/

var aerospike = {
  // client:   as.client,
  key:      as.key,
  status:   as.status,
  policy:   as.policy,
  operator: as.operator,
  language: as.language,
  log:      as.log,
  scanPriority: as.scanPriority,
  filter:	as.filter,
  indexType : as.indexType,
  scanStatus: as.scanStatus
};

aerospike.client = function(config) {
  var client = as.client(config);
  var proto = Object.getPrototypeOf(client);

  if( !proto.createQuery ) {
	  proto.createQuery = proto.query;
	  proto.query = query;
  }

  if( !proto.createIntegerIndex ) {
	  proto.createIntegerIndex = createIntegerIndex;
  }

  if( !proto.createStringIndex ) {
	  proto.createStringIndex = createStringIndex;
  }

  if( !proto.LargeList) {
	  proto.LargeList = LargeList;
  }
  return client;
};

module.exports = aerospike;
<|endoftext|>