|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- // Copyright 2014 Joyent, Inc. All rights reserved.
-
- var EventEmitter = require('events').EventEmitter;
- var util = require('util');
-
- var assert = require('assert-plus');
-
- var dn = require('../dn');
- var messages = require('../messages/index');
- var Protocol = require('../protocol');
- var PagedControl = require('../controls/paged_results_control.js');
-
-
- ///--- API
-
-
- /**
- * Handler object for paged search operations.
- *
- * Provided to consumers in place of the normal search EventEmitter it adds the
- * following new events:
- * 1. page - Emitted whenever the end of a result page is encountered.
- * If this is the last page, 'end' will also be emitted.
- * The event passes two arguments:
- * 1. The result object (similar to 'end')
- * 2. A callback function optionally used to continue the search
- * operation if the pagePause option was specified during
- * initialization.
- * 2. pageError - Emitted if the server does not support paged search results
- * If there are no listeners for this event, the 'error' event
- * will be emitted (and 'end' will not be). By listening to
- * 'pageError', a successful search that lacks paging will be
- * able to emit 'end'.
- * 3. search - Emitted as an internal event to trigger another client search.
- */
- function SearchPager(opts) {
- assert.object(opts);
- assert.func(opts.callback);
- assert.number(opts.pageSize);
-
- EventEmitter.call(this, {});
-
- this.callback = opts.callback;
- this.controls = opts.controls;
- this.pageSize = opts.pageSize;
- this.pagePause = opts.pagePause;
-
- this.controls.forEach(function (control) {
- if (control.type === PagedControl.OID) {
- // The point of using SearchPager is not having to do this.
- // Toss an error if the pagedResultsControl is present
- throw new Error('redundant pagedResultControl');
- }
- });
-
- this.finished = false;
- this.started = false;
-
- var emitter = new EventEmitter();
- emitter.on('searchEntry', this.emit.bind(this, 'searchEntry'));
- emitter.on('end', this._onEnd.bind(this));
- emitter.on('error', this._onError.bind(this));
- this.childEmitter = emitter;
- }
- util.inherits(SearchPager, EventEmitter);
- module.exports = SearchPager;
-
- /**
- * Start the paged search.
- */
- SearchPager.prototype.begin = function begin() {
- // Starting first page
- this._nextPage(null);
- };
-
- SearchPager.prototype._onEnd = function _onEnd(res) {
- var self = this;
- var cookie = null;
- res.controls.forEach(function (control) {
- if (control.type === PagedControl.OID) {
- cookie = control.value.cookie;
- }
- });
- // Pass a noop callback by default for page events
- var nullCb = function () { };
-
- if (cookie === null) {
- // paged search not supported
- this.finished = true;
- this.emit('page', res, nullCb);
- var err = new Error('missing paged control');
- err.name = 'PagedError';
- if (this.listeners('pageError').length > 0) {
- this.emit('pageError', err);
- // If the consumer as subscribed to pageError, SearchPager is absolved
- // from deliverying the fault via the 'error' event. Emitting an 'end'
- // event after 'error' breaks the contract that the standard client
- // provides, so it's only a possibility if 'pageError' is used instead.
- this.emit('end', res);
- } else {
- this.emit('error', err);
- // No end event possible per explaination above.
- }
- return;
- }
-
- if (cookie.length === 0) {
- // end of paged results
- this.finished = true;
- this.emit('page', nullCb);
- this.emit('end', res);
- } else {
- if (this.pagePause) {
- // Wait to fetch next page until callback is invoked
- // Halt page fetching if called with error
- this.emit('page', res, function (err) {
- if (!err) {
- self._nextPage(cookie);
- } else {
- // the paged search has been canceled so emit an end
- self.emit('end', res);
- }
- });
- } else {
- this.emit('page', res, nullCb);
- this._nextPage(cookie);
- }
- }
- };
-
- SearchPager.prototype._onError = function _onError(err) {
- this.finished = true;
- this.emit('error', err);
- };
-
- /**
- * Initiate a search for the next page using the returned cookie value.
- */
- SearchPager.prototype._nextPage = function _nextPage(cookie) {
- var controls = this.controls.slice(0);
- controls.push(new PagedControl({
- value: {
- size: this.pageSize,
- cookie: cookie
- }
- }));
-
- this.emit('search', controls, this.childEmitter,
- this._sendCallback.bind(this));
- };
-
- /**
- * Callback provided to the client API for successful transmission.
- */
- SearchPager.prototype._sendCallback = function _sendCallback(err, res) {
- if (err) {
- this.finished = true;
- if (!this.started) {
- // EmitSend error during the first page, bail via callback
- this.callback(err, null);
- } else {
- this.emit('error', err);
- }
- } else {
- // search successfully send
- if (!this.started) {
- this.started = true;
- // send self as emitter as the client would
- this.callback(null, this);
- }
- }
- };
|