nEDM Interface

nEDM Experiment

nEDM Interface

Repository

Tutorials


Modules


Classes


Externals


/**
 * @module lib/update_db
 *
 * @requires module:events
 * @requires module:toastr
 * @requires module:db
 * @requires module:lib/nedm
 */

/**
 * @callback module:lib/update_db.DBRequestCallback
 * @param {Object} err - object containing error, null/undefined if no error
 * @param {Object} result - object containing result, generally JSON format
 */

/**
 * Returns status of view
 * @callback module:lib/update_db.ViewStatusCallback
 * @param {Object} returned object, { error : msg } if error, otherwise information about the view status
 */

/**
 * Returns a list of variables
 * @callback module:lib/update_db.VariableNamesCallback
 * @param {Array} listOfVariables
 */

/**
 * Returns an object with all commands
 * @callback module:lib/update_db.CommandsCallback
 * @param {Array} rowsOfCommands
 */


/**
 * Information from changes feed
 *
 * @callback module:lib/update_db.ChangesFeedCallback
 * @param {Object} object - JSON information from changes feed
 */

/**
 * Information from changes feed
 * @callback module:lib/update_db.OnChangesCallback
 * @param {Object} object - JSON information from changes feed
 */

/**
 * Information from changes feed
 *
 * @callback module:lib/update_db.OnLatestCallback
 * @param {Object} object - dictionary of latest variables and values
 * (does not always include *all* variables!)
 */



var events = require("events");
var toastr = require("toastr");
var on_cloudant;
function BuildURL(options) {
    var url = "";

    var first = true;
    for(var key in options) {
        if (!first) url += "&";
        else url = "?";
        first = false;
        url += key + "=";
        if (typeof options[key] === 'string') url += options[key];
        else url += JSON.stringify(options[key]);
    }
    return encodeURI(url);
}

/**
 * A Kan.so DB object, from:
 * @external DB
 * @see {@link http://caolan.github.io/kanso/api/db.html}
 */

var old_db = require("db");
var old_use = old_db.use;

/**
 * Returns a function for handling ajax responses from jquery and calls
 * the callback with the data or appropriate error.
 *
 * @param {Function} callback(err,response)
 * @api private
 */

function onComplete(options, callback) {
    return function (req) {
        var resp;
        var ctype = req.getResponseHeader('Content-Type');
        if (ctype === 'application/json' || ctype === 'text/json') {
            try {
                resp = req.responseJSON;
            }
            catch (e) {
                return callback(e);
            }
        }
        else {
            if (options.expect_json) {
                try {
                    resp = req.responseJSON;
                }
                catch (ex) {
                    return callback(
                        new Error('Expected JSON response, got ' + ctype)
                    );
                }
            }
            else {
                resp = req.responseText;
            }
        }
        if (req.status === 401) {
            exports.emit('unauthorized', req);
        }
        if (req.status === 200 || req.status === 201 || req.status === 202) {
            callback(null, resp);
        }
        else if (resp && (resp.error || resp.reason)) {
            var err = new Error(resp.reason || resp.error);
            err.error = resp.error;
            err.reason = resp.reason;
            err.code = resp.code;
            err.status = req.status;
            callback(err);
        }
        else {
            // TODO: map status code to meaningful error message
            var err2 = new Error('Returned status code: ' + req.status);
            err2.status = req.status;
            callback(err2);
        }
    };
}



/**
 * Extension of db.DB
 * Adds/updates several functions to the DB object
 * @constructs nEDMDB
 * @augments external:DB
 * @requires module:db
 * @param {String} url - url to database
 */
function nEDMDB(url) {
    /* Force leading slash; make absolute path. */

    // From https://gist.github.com/jlong/2428561
    // We use the DOM to get us info about the url
    var parse = document.createElement('a');
    parse.href = url;

    // First check if it's already absolute:
    if (parse.href != url) {
        // We are on the same host
        // Now ensure we have a relative root-path
        if (url[0] != '/') parse.href = '/' + url;
    }

    // Make absolute path
    url = parse.href;
    var myDB = old_use(url);

    // hack to fix version 0.13 of db
    if (myDB.url[0] =='/') myDB.url = myDB.url.substr(1);

    (function (db) {
        var tthis = db;
        var _called_views = {};
        var __base = require('lib/nedm');
        var nedm = new __base.nEDMDatabase();
        function OpenChangesFeed(url) {
          var hasOpened = false;
          var hasSeenError = false;
          var myURL = url;
          var myToastr;
          var use_event_source = !on_cloudant;
          function OnOpen(e) {
              hasOpened = true;
              if (hasSeenError) {
                toastr.success("Connection reestablished to: " + myURL, "Connection established");
                myToastr.remove();
              }
              hasSeenError = false;
          }
          function OnError(e) {
              if (hasSeenError) return;
              var msg = "";
              if (hasOpened) {
                msg = "Connection lost to: " + myURL;
              } else {
                msg = "Error making connection to: " + myURL;
              }
              hasSeenError = true;
              myToastr = toastr.error(msg, "Changes Feed Error", { timeOut : 0, extendedTimeOut : 0 });
          }
          var myEvents = new events.EventEmitter();

          var processed_length = 0;
          function EmitChangesFeedEvents(ev) {
            if (!ev.currentTarget || !ev.currentTarget.responseText) return;
            var rT = ev.currentTarget.responseText.slice(processed_length);
            if (rT.length === 0) return;
            var last_index = rT.lastIndexOf('\n');
            if (last_index === -1) return;
            var reqs = rT.split('\n');
            var goto_length = reqs.length;
            if (last_index !== rT.length - 1) {
              goto_length -= 1;
            }
            for (var i = 0;i<goto_length;i++) {
              if (reqs[i] === '') continue;
              myEvents.emit("message", { data : reqs[i] });
            }
            processed_length += last_index;
          }

          if (use_event_source) {
            var listener = new EventSource(url);
            listener.onerror = OnError;
            listener.onopen = OnOpen;
            this.addListener = function(callback) {
              this.removeListener(callback);
              listener.addEventListener("message", callback, false);
            };

            this.removeListener = function(callback) {
              listener.removeEventListener("message", callback, false);
            };
            this.close = function() {
              listener.close();
            };

          } else {
            // Much more limited interface!
            // here we can't use EventSource
            var req = {
                type: 'GET',
                url: url
            };
            var re = tthis.request(req, {
              progress : EmitChangesFeedEvents
            });
            this.removeListener = function(callback) {
              myEvents.removeListener("message", callback);
            };
            this.addListener = function(callback) {
              myEvents.addListener("message", callback);
            };
            this.close = function() {
              re.close();
            };
          }

        }

        var open_changes_feeds = { };
        var callback_functions = { urls : [], callbacks : []};


        /**
          * Get most recent value of a particular variable
          * @function get_most_recent_value
          * @memberof nEDMDB#
          * @param {String} var_name - name of variable
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */
        db.get_most_recent_value = function(var_name, callback) {
          return this.getView('slow_control_time', 'slow_control_time',
          { opts : {
              endkey : [var_name],
            startkey : [var_name, {}],
          descending : true,
              reduce : false,
               limit : 1}}, callback);
        };

        /**
          * Guess current database (most of the time this shouldn't be used)
          * @function guessCurrent
          * @memberof nEDMDB#
          * @param {Object} location - URL
          * @public
          */
        db.guessCurrent = function (loc1) {
            var loc = loc1 || window.location;

            /**
             * A database must be named with all lowercase letters (a-z), digits (0-9),
             * or any of the _$()+-/ characters and must end with a slash in the URL.
             * The name has to start with a lowercase letter (a-z).
             *
             * http://wiki.apache.org/couchdb/HTTP_database_API
             */

            var re = /\/([a-z][a-z0-9_(%2F)\$\(\)\+-\/]*)\/_design\/([^\/]+)\//;
            var match = re.exec(loc.pathname);

            if (match) {
                return {
                    db: match[1],
                    design_doc: match[2],
                    root: '/nedm_head/_design/nedm_head/_rewrite/_couchdb/'
                };
            }
            return null;
        };

        /**
          * get certain list function
          * @function getList
          * @memberof nEDMDB#
          * @param {String} name - name of design document
          * @param {String} list - name of list function
          * @param {String} view - name of view function
          * @param {String} [other_ddoc] - name of other design document
          * @param {Object} [q] - query parameters for list
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */
        db.getList = function (name, list, view, /*optional*/ other_ddoc, /*optional*/q, callback) {
          if (!callback) {
            if (!q) {
              // (name, list, view, callback)
              callback = other_ddoc;
              q = {};
              other_ddoc = undefined;
            } else {
              callback = q;
              if(typeof other_ddoc === "object") {
                // (name, list, view, q, callback)
                q = other_ddoc;
                other_ddoc = undefined;
              } else {
                // (name, list, view, other_ddoc, callback)
                q = {};
              }
            }
          }
          var listname = this.encode(list);
          var viewname = this.encode(view);
          if (other_ddoc) {
            viewname = this.encode(other_ddoc) + '/' + viewname;
          }
          try {
              var data = this.stringifyQuery(q);
          }
          catch (e) {
              return callback(e);
          }
          var req = {
              url: this.url + '/_design/' + this.encode(name) +
                  '/_list/' + listname + '/' + viewname,
              data: data
          };
          this.request(req, callback);
        };

        /**
          * Call update function on design document
          * @function updateDoc
          * @memberof nEDMDB#
          * @param {Object} doc - document to insert
          * @param {String} designname - name of design document
          * @param {String} updatename - name of update function
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */
        db.updateDoc = function (doc, designname, updatename, callback) {
            var method, url = this.url;
            url += '/_design/' + designname + '/_update/' + updatename;
            if (doc._id === undefined) {
                method = "POST";
            }
            else {
                method = "PUT";
                url += '/' + doc._id;
            }
            var data;
            try {
                data = JSON.stringify(doc);
            }
            catch (e) {
                return callback(e);
            }
            var req = {
                type: method,
                url: url,
                data: data,
                processData: false,
                contentType: 'application/json',
                expect_json: true
            };
            this.request(req, callback);
        };

        /**
          * handle requests, enables dealing with progress functions
          * @function request
          * @memberof nEDMDB#
          * @param {Object} req - request
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */
        db.request = function(req, callback) {
          req.dataType = 'json';
          if (typeof callback !== 'object') {
            req.complete = onComplete(req, callback);
            return $.ajax(req);
          }
          var cbck = callback;
          if (cbck.progress) {
            req.xhr = function() {
              var xhr = new XMLHttpRequest();
              if (cbck.progress) {
                xhr.upload.addEventListener("progress", cbck.progress, false);
                xhr.addEventListener("progress", cbck.progress, false);
              }
              cbck.xhr = xhr;
              return xhr;
            };
          }
          req.complete = onComplete(req, callback.success);
          return $.ajax(req);
        };

        /**
          * Remove an attachment from a document
          * @function removeAttachment
          * @memberof nEDMDB#
          * @param {Object} doc - document from which attachment should be removed (at least { _id : "..." })
          * @param {String} file_name - attachment to remove
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */

        db.removeAttachment = function(doc, file_name, callback) {
          var tthis = this;
          var req = {
            type : "DELETE",
            url : this.url + "/" + doc._id + "/" + file_name,
          };

          var exec_rem = function(rev) {
            req.url += "?rev=" + rev;
            tthis.request(req, callback);
          };
          if (doc._rev) {
            return exec_rem(doc._rev);
          } else {
            return tthis.getDoc(doc._id, function(err, obj) {
              if (err) return;
              exec_rem(obj._rev);
            });
          }
        };

        /**
          * Add an attachment to a document
          * @function addAttachment
          * @memberof nEDMDB#
          * @param {Object} doc - document from which attachment should be removed (at least { _id : "..." })
          * @param {Object} file_object - attachment to remove
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */
        db.addAttachment = function(doc, file_object, callback) {
          var tthis = this;
          var req = {
            type : "PUT",
            url : this.url + "/" + doc._id + "/" + file_object.name,
            processData : false,
            data : file_object,
            contentType : file_object.type || "application/octet-stream"
          };

          var exec_upload = function(rev) {
            req.url += "?rev=" + rev;
            tthis.request(req, callback);
          };
          if (doc._rev) {
            return exec_upload(doc._rev);
          } else {
            return tthis.getDoc(doc._id, function(err, obj) {
              if (err) return;
              exec_upload(obj._rev);
            });
          }
        };

        /**
          * Query the changes feed of a database
          * @function changes
          * @memberof nEDMDB#
          * @param {Object} options - request options
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @private
          */
        db.changes = function(options, callback) {
            if (!callback) {
                callback = options;
                options = {};
            }
            var req = {
                url: this.url +
                    '/_changes' + BuildURL(options),
                type: "GET",
                expect_json: true
            };

            return this.request(req, callback);
        };

        /**
          * Query a view
          * @function getView
          * @memberof nEDMDB#
          * @param {String} name - name of design document
          * @param {String} view - name of view function
          * @param {Object} [options] - query parameters for view
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */
        db.getView = function(name, view, options, callback) {
            if (!callback) {
                callback = options;
                options = {};
            }
            if (!options.keys) options.keys = {};
            if (!options.opts) options.opts = {};
            var viewname = this.encode(view);
            var theType = "POST";
            var data;
            try {
                data = JSON.stringify(options.keys);
                if ($.isEmptyObject(options.keys)) {
                    data = "";
                    theType = "GET";
                }
            }
            catch (e) {
                return callback(e);
            }
            var req = {
                url: this.url +
                    '/_design/' + this.encode(name) +
                    '/_view/' + viewname + BuildURL(options.opts),
                data: data,
                type: theType,
                expect_json: true
            };

            var thisdb = this;
            // Inform the user if the view takes a while to load
            if (!(name in _called_views)) {
                _called_views[name] = {
                    timer_notify : function() {
                                       var base_text = "View (design doc " + name + ") is currently building: ";
                                       var tthis = this;
                                       thisdb.checkViewStatus(name, function(o) {
                                           if (o.done) {
                                              tthis.cancel();
                                              return;
                                           }
                                           if (!tthis.mytoastr) {
                                               tthis.initial_value = o.view_update_seq;
                                               tthis.total_diff = o.db_update_seq - tthis.initial_value;
                                               tthis.mytoastr = toastr.info(base_text + " ? of ?",
                                                   "View building",
                                                   { timeOut : -1,
                                            extendedTimeOut  : -1,
                                               positionClass : "toast-top-right",
                                                 closeButton : false
                                                   } );
                                           }
                                           var perc = (o.view_update_seq - tthis.initial_value)*100/tthis.total_diff;
                                           if (perc > 100) perc = 100;
                                           $(".toast-message", tthis.mytoastr).text(
                                             base_text + (o.view_update_seq - tthis.initial_value).toString() + " of " +
                                             tthis.total_diff.toString() + " (" + perc.toFixed(2) +
                                             "%)");
                                       });
                                   },
                    timeout : function() {
                        if (this.notify_view_building === undefined) {
                            this.notify_view_building = setTimeout(function(o) { return function() { o.timer_notify();}; }(this) , 3000);
                        }
                    },
                    cancel : function() {
                        if (this.notify_view_building) {
                          clearTimeout(this.notify_view_building);
                        }
                        if (this.mytoastr) this.mytoastr.remove();
                        this.notify_view_building = undefined;
                        this.mytoastr = undefined;
                    },
                    notify_view_building : undefined,
                    mytoastr : undefined
                };
            }
            var cur_view = _called_views[name];
            cur_view.timeout();
            var callback_wrapper = function(cbck) {
              return function(e, o) {
                // Clear the informational notice.
                cur_view.cancel();
                if (cbck) cbck(e,o);
              };
            };

            if (typeof callback === 'object') {
              var old_success = callback.success;
              callback.success = callback_wrapper(old_success);
            } else {
              callback = callback_wrapper(callback);
            }
            return this.request(req, callback);
        };

         /**
          * Get information from a view
          * @function getViewInfo
          * @memberof nEDMDB#
          * @param {String} view - name of design + view function
          * @param {module:lib/update_db.DBRequestCallback} callback
          * @public
          */
        db.getViewInfo = function(view, callback) {
            var req = {
                url: this.url + "/_design/" + view + "/_info",
                expect_json: true,
            };
            return this.request(req, callback);
        };


         /**
          * Check status of view
          * @function getViewInfo
          * @memberof nEDMDB#
          * @param {String} view - name of design + view function
          * @param {module:lib/update_db.ViewStatusCallback} callback
          * @public
          */
        db.checkViewStatus = function(view, callback) {
            var obj = this;
            return this.info( function(e, o) {
                if (e) callback({ error: e });
                else {
                    var cur_seq = o.update_seq;
                    var view_status = function(e, o) {
                        if (e) callback({ error: e });
                        else {
                            var done = (o.view_index.update_seq >= cur_seq) || !o.view_index.updater_running;
                            callback( { done: done,
                                       view : o.name,
                             view_update_seq: o.view_index.update_seq,
                               db_update_seq: cur_seq } );
                            if (!done) {
                              setTimeout(function() { obj.getViewInfo(view, view_status); }, 2000);
                            }
                        }
                    };
                    obj.getViewInfo(view, view_status);
                }
            });
        };

         /**
          * Get all variable names (data) in a particular database
          * @function getVariableNames
          * @memberof nEDMDB#
          * @param {module:lib/update_db.VariableNamesCallback} callback
          * @public
          */
        var _varNameCache;
        db.getVariableNames = function(callback) {
          if (_varNameCache) {
            return callback(_varNameCache);
          }
          this.getView('slow_control_time', 'slow_control_time',
            { opts : { stale : 'ok',
                 group_level : 1
                     }
            },
            function (e, o) {
                  if (e !== null ) {
                    _varNameCache = [];
                  } else {
                    _varNameCache = o.rows.map(function(x) { return x.key[0]; });
                  }
                  db.getVariableNames(callback);
            }
          );
        };
         /**
          * Get all commands in a particular database
          * @function getCommands
          * @memberof nEDMDB#
          * @param {module:lib/update_db.CommandsCallback} callback
          * @public
          */
        var _commandCache;
        db.getCommands = function(callback) {
          if (_commandCache) {
            return callback(_commandCache);
          }
          this.getView('execute_commands', 'export_commands',
            { opts : { reduce : false } },
            function (e, o) {
                  if (e !== null ) {
                    _commandCache = [];
                  } else {
                    _commandCache = o.rows;
                  }
                  db.getCommands(callback);
            }
          );
        };





        /**
         * Listen to changes feed of a particular database.  If feed is already open,
         * then add a listener to that feed.  *This should be used sparingly!*  Chances
         * are, the same functionality can be gained by listening to aggregate changes:
         *
         * @see lib/nedm.on_db_updates
         *
         * @function listen_to_changes_feed
         * @memberof nEDMDB#
         *
         * @param {module:lib/update_db.ChangesFeedCallback} callback - gets message from EventSource object
         * @options {Object} options - Options to pass to changes feed
         * @public
         */
        db.listen_to_changes_feed = function(callback, options) {
            if (on_cloudant) {
              options.feed = "continuous";
            } else {
              options.feed = "eventsource";
            }
            if (!options.heartbeat) {
                options.heartbeat = 7000;
            }
            var url = this.url + "/_changes" + BuildURL(options);

            if (!(url in open_changes_feeds)) {
                // start a new listener
                open_changes_feeds[url] = new OpenChangesFeed(url);
            }
            open_changes_feeds[url].addListener(callback);
            callback_functions.urls.push(url);
            callback_functions.callbacks.push(callback);
        };

        /**
         * Cancel previous changes feed opened by nedm.listen_to_changes_feed
         *
         * @function cancel_changes_feed
         * @memberof nEDMDB#
         *
         * @param {module:lib/update_db.ChangesFeedCallback} callback - gets message from EventSource object
         * @public
         */
        db.cancel_changes_feed = function(callback) {
            var index = callback_functions.callbacks.indexOf(callback);
            if ( index === -1 ) return;

            var url = callback_functions.urls[index];
            open_changes_feeds[url].removeListener(callback);
            callback_functions.callbacks.splice(index, 1);
            callback_functions.urls.splice(index, 1);
            var delete_urls = [];
            for (url in open_changes_feeds) {
                if ( callback_functions.urls.indexOf( url ) === -1 ) {
                  delete_urls.push(url);
                }
            }
            delete_urls.forEach( function(url) {
                open_changes_feeds[url].close();
                delete open_changes_feeds[url];
            });
        };

        /**
         * Object sent to command function
         * @typedef {Object} module:lib/update_db.CommandObject
         * @property {String} cmd_name - name of command
         * @property {Array} [arguments] - list of arguments (must be JSON-parseable!)
         * @property {module:lib/update_db.DBRequestCallback} [callback] - callback function
         * @property {number} [timeout] - time (in seconds) to wait until timeout.  If not sent, no timeout
         * @property {boolean} [quiet] - don't give any toastr messages
         */

        /**
         * Object returned from command function
         * @typedef {Object} module:lib/update_db.CommandReturnObject
         * @property {object} promise - promise object
         * @property {function} abort - call to request an abort
         */

        /**
         * Send command
         *
         * @function send_command
         * @memberof nEDMDB#
         *
         * @param {module:lib/update_db.CommandObject} o - information for command
         * @return {module:lib/update_db.CommandReturnObject}
         * @public
         */
        db.send_command = function(o) {
          var adoc = { type : 'command', execute : o.cmd_name };
          if ('arguments' in o) { adoc['arguments'] = o['arguments']; }
          var callback;
          var quiet;
          var timeout = 0;
          var that = this;
          if ('timeout' in o) timeout = o.timeout;
          if (timeout < 0) timeout = 0;
          if ('callback' in o) callback = o.callback;
          if ('quiet' in o) quiet = o.quiet;
          var abort_requested = false;
          var current_req = null;
          var ret_function = function(err, resp) {
            if (err) {
              if (callback) callback(err);
              if (!quiet) toastr.error(err.reason, err.error);
            } else {
              if (callback) callback(null, resp);
              if (!quiet) toastr.success(resp.toastr.msg, resp.toastr.title);
            }
          };
          function SubmitUpdateDoc() {
            var d = $.Deferred();
            current_req = that.updateDoc(adoc,
                'nedm_default', 'insert_with_timestamp', function(err, obj) {
                   if (err) {
                     d.reject(err);
                   } else {
                     d.resolve(obj);
                   }
                 });
            d.fail(ret_function);
            return d.promise();
          }
          function ResolveUpdateSubmission(obj) {
            var cmd_str = "Command submitted: " + o.cmd_name;
            var id = obj.id;
            if ('arguments' in adoc) {
                cmd_str += ", with args: " + JSON.stringify(adoc['arguments']);
            }
            if (!quiet) toastr.info(cmd_str, "");
            // Check for the return of the function...
            var total_timeout = timeout;
            var changes_opts = { doc_ids : [ id ],
                                  filter : "_doc_ids",
                                    feed : "longpoll" };
            if (timeout === 0) {
                changes_opts.heartbeat = 5000;
            } else {
                changes_opts.timeout = timeout;
            }
            var d = $.Deferred();
            var has_cycled = false;
            function HandleChanges(err, o) {
               if (err) {
                 d.reject(err);
               } else {
                 // We first need to check if the revision is only the first
                 var new_rev = false;
                 if (o.results.length > 0) {
                   var c = o.results[0].changes;
                   for (var i=0;i<c.length;i++) {
                     if (c[i].rev.slice(0,2) !== "1-") {
                       new_rev = true;
                       break;
                     }
                   }
                 }
                 if (!new_rev) {
                   if (!has_cycled) {
                     // Try cyclying again
                     has_cycled = true;
                     changes_opts.filter = '_view';
                     changes_opts.view = 'execute_commands/complete_commands';
                     changes_opts.since = o.last_seq;
                     current_req = that.changes( changes_opts, HandleChanges );
                   } else {
                     d.reject({ error  : "Timeout on reaction for command: " + adoc.execute,
                                reason :"Timeout" });
                   }
                 } else {
                   d.resolve(id);
                 }
               }
            }

            d.fail(ret_function);
            current_req = that.changes( changes_opts, HandleChanges );
            return d.promise();
          }

          function GetResults(anid) {
            current_req = that.getDoc(anid, function (err, obj) {
              current_req = null;
              if (err) return ret_function(err);
              var resp = obj.response;
              var resp_str = "Response for (" + o.cmd_name + "): " + resp.content + "\n" +
                             "    return value: " + JSON.stringify(resp['return']);
              if (!("ok" in resp)) {
                  ret_function( { error : resp_str, reason : "Error" } );
              } else {
                  resp.toastr = { msg : resp_str, title : "Success" };
                  ret_function(null, resp);
              }
            });
          }
          return {
             promise : SubmitUpdateDoc().then(ResolveUpdateSubmission).then(GetResults),
               abort : function() {
                 if (current_req) current_req.abort();
                 abort_requested = true;
             }
          };
        };
        /**
         * Get db name
         *
         * @function db_name
         * @memberof nEDMDB#
         *
         * @return {String} name of database
         * @public
         */
        db.db_name = function() {
          var arr = this.url.split('/');
          return arr[arr.length-1].split('%2F')[1];
        };


        /**
         * register for changes in the database
         *
         * @function on
         * @memberof nEDMDB#
         *
         * @param {string} [type="both"] - type of changes, "data", "heartbeat", "both" or "latest"
         * @param {module:lib/update_db.OnChangesCallback|module:lib/update_db.OnLatestCallback} callback - when type = "latest", uses OnLatestCallback
         * @public
         */
        db.on = function(type, callback) {
          if (!callback) {
            callback = type;
            type = "both";
          }
          if (type === "latest") {
            this.on("data", CallbackLatestValue);
            // Force the call
            CallbackLatestValue();
          }
          _callback_emitters.removeListener(type, callback);
          _callback_emitters.addListener(type, callback);
          nedm.on_db_updates(GenerateCallback);
        };

        /**
         * cancel callback for changes in the database
         *
         * @function off
         * @memberof nEDMDB#
         *
         * @param {string} [type] - type of changes, "data",
         * @public
         */
        db.off = function(type, callback) {
          if (!callback) {
            callback = type;
            type = "both";
          }
          _callback_emitters.removeListener(type, callback);
          if (_callback_emitters.listeners("latest").length === 0) {
            _callback_emitters.removeListener("data", CallbackLatestValue);
          }
          if (_callback_emitters._events && Object.keys(_callback_emitters._events).length === 0) nedm.remove_db_updates(GenerateCallback);
        };

        var _callback_emitters = new events.EventEmitter();
        var db_name = db.db_name();

        var latest_callback_called = false;
        var cached_variables;
        var latest_time;
        function CallbackLatestValue() {
          // check for latest values
          if (latest_callback_called) return;
          latest_callback_called = true;
          if (!cached_variables ) {
            db.getVariableNames(function(arr) {
              cached_variables = arr;
              latest_callback_called = false;
              CallbackLatestValue();
            });
          } else {
            var opts = {
                    limit : cached_variables.length,
               descending : true
            };
            if (latest_time) {
              opts.endkey = latest_time;
            }

            db.getList("extremes", "extremes", "slow_control_time_label", "slow_control_time_label",
              opts,
              function(e, o) {
                if (e === null && o.extreme_time) {
                  latest_time = o.extreme_time;
                  latest_time.push({});
                  _callback_emitters.emit("latest", o.keys);
                }
                latest_callback_called = false;
              }
            );
          }

        }

      function GenerateCallback(msg) {
        if (db_name !== msg.db) return;
        _callback_emitters.emit(msg.type, msg);
        _callback_emitters.emit("both", msg);
      }

    })(myDB);


    return myDB;
}



exports.UseDB = nEDMDB
old_db.use = exports.UseDB;
exports.on_cloudant = function(avar) {
  on_cloudant = avar;
};