updated by GasGit automation
This commit is contained in:
parent
3557f3eda9
commit
efee2cf532
1 changed files with 305 additions and 0 deletions
305
scripts/ClientWatcher.js
Normal file
305
scripts/ClientWatcher.js
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* simulate Watcher with apps script
|
||||||
|
* various changes server side can be watched for server side
|
||||||
|
* and resolved client side
|
||||||
|
* @constructor ClientWatcher
|
||||||
|
*/
|
||||||
|
var ClientWatcher = (function (ns) {
|
||||||
|
|
||||||
|
var watchers_ = {},startTime_=0;;
|
||||||
|
|
||||||
|
// now clean it
|
||||||
|
function cleanTheCamel_ (cleanThis) {
|
||||||
|
return typeof cleanThis === "string" ? cleanThis.slice(0,1).toUpperCase() + cleanThis.slice(1) : cleanThis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return {object} all current Watchers, the id is the key
|
||||||
|
*/
|
||||||
|
ns.getWatchers = function () {
|
||||||
|
return watchers_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a Watcher
|
||||||
|
* @param {object} options what to watch
|
||||||
|
* @param {string} [sheet] the sheet to watch if missing, watch the active sheet
|
||||||
|
* @param {string} [range] the range to watch - if missing, watch the whole sheet
|
||||||
|
* @param {string} [property=Data] matches getData, getBackground
|
||||||
|
* @param {TYPES} [type=SHEET] the type of Watcher
|
||||||
|
* @param {number} pollFrequency in ms, how often to poll
|
||||||
|
* @return {ClientWatcher.Watcher} the Watcher
|
||||||
|
*/
|
||||||
|
ns.addWatcher = function (options) {
|
||||||
|
|
||||||
|
// default settings for a Watcher request
|
||||||
|
var watch = Utils.vanMerge ([{
|
||||||
|
pollFrequency:2500,
|
||||||
|
id: '' , // Watcher id
|
||||||
|
watch: {
|
||||||
|
active: true, // whether to watch for changes to active
|
||||||
|
data: true, // whether to watch for data content changes
|
||||||
|
sheets:true // watch for changes in number/names of sheets
|
||||||
|
},
|
||||||
|
checksum:{
|
||||||
|
active:"", // the active checksum last time polled
|
||||||
|
data:"", // the data checksum last time polled
|
||||||
|
sheets:"" // the sheets in the workbook last time polled
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
app: "Sheets", // for now only Sheets are supported
|
||||||
|
scope: "Sheet", // Sheet, Active or Range - sheet will watch the datarange
|
||||||
|
range: "", // if range, specifiy a range to watch
|
||||||
|
sheet: "", // a sheet name - if not given, the active sheet will be used
|
||||||
|
property:"Values", // Values,Backgrounds etc...
|
||||||
|
fiddler:true // whether to create a fiddler to mnipulate data (ignored for nondata property)
|
||||||
|
}
|
||||||
|
},options || {}]);
|
||||||
|
|
||||||
|
// tidy up the parameter cases
|
||||||
|
Object.keys(watch.domain).forEach(function(k) {
|
||||||
|
watch.domain[k] = cleanTheCamel_ (watch.domain[k]);
|
||||||
|
});
|
||||||
|
watch.id = watch.id || ('w' + Object.keys(watchers_).length);
|
||||||
|
|
||||||
|
// add to the registry
|
||||||
|
return (watchers_[watch.id] = ns.newWatcher(watch));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove a Watcher
|
||||||
|
* @param {string||object} id the id or object
|
||||||
|
* @return {ClientWatcher} self
|
||||||
|
*/
|
||||||
|
ns.removeWatcher = function (watcher) {
|
||||||
|
var id = Utils.isVanObject(watcher) ? watcher.id : watcher;
|
||||||
|
if (!id || watchers_[id]) {
|
||||||
|
throw 'Watcher ' + id + ' doesnt exists - cannot remove';
|
||||||
|
}
|
||||||
|
watchers_[id].stop();
|
||||||
|
watchers_[id] = null;
|
||||||
|
return ns;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* return a specifc Watcher
|
||||||
|
* @param {string} id the Watcher
|
||||||
|
* @return {ClientWatcher.watcher} the Watcher
|
||||||
|
*/
|
||||||
|
ns.getWatcher = function (id) {
|
||||||
|
return watchers_[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used to create a new Watcher object
|
||||||
|
* @return {ClientWatcher.Watcher}
|
||||||
|
*/
|
||||||
|
ns.newWatcher = function (watch) {
|
||||||
|
return new ns.Watcher(watch);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* this is a Watcher object
|
||||||
|
* @param {object} watch the Watcher resource
|
||||||
|
* @return {ClientWatcher.Watcher}
|
||||||
|
*/
|
||||||
|
ns.Watcher = function (watch) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var current_ = {
|
||||||
|
active:null,
|
||||||
|
data:null
|
||||||
|
} ;
|
||||||
|
var watch_ = watch, stopped_ = false;
|
||||||
|
|
||||||
|
// this monitors requests
|
||||||
|
var status_ = {
|
||||||
|
serial:0, // the serial number of the poll
|
||||||
|
requested:0, // time requested
|
||||||
|
responded:0, // time responded
|
||||||
|
errors:0 , // number of errors
|
||||||
|
hits:0, // how many times a change was detected
|
||||||
|
totalWaiting:0 // time spent waiting for server response
|
||||||
|
};
|
||||||
|
|
||||||
|
self.start = function () {
|
||||||
|
// get started .. first time it will run immediately
|
||||||
|
return nextPolling_(!status_.serial);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.restart = function () {
|
||||||
|
stopped_ = false;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stop = function () {
|
||||||
|
stopped_ = true;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// force a redo
|
||||||
|
self.poke = function () {
|
||||||
|
Object.keys(watch_.checksum).forEach(function(d) {
|
||||||
|
watch_.checksum[d] = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.getWatching = function () {
|
||||||
|
return watch_;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* if you want the current data
|
||||||
|
* @return {object} the current data
|
||||||
|
*/
|
||||||
|
self.getCurrent = function () {
|
||||||
|
return current_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if you want the latest status
|
||||||
|
* @return {object} status
|
||||||
|
*/
|
||||||
|
self.getStatus = function () {
|
||||||
|
return status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* do the next polling after waiting some time
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
function nextPolling_ (immediate) {
|
||||||
|
return new Promise(function (resolve,reject) {
|
||||||
|
setTimeout ( function () {
|
||||||
|
self.poll()
|
||||||
|
.then(function(pack) {
|
||||||
|
resolve(pack);
|
||||||
|
})
|
||||||
|
['catch'](function(pack) {
|
||||||
|
reject(pack);
|
||||||
|
})
|
||||||
|
}, immediate ? tweakWaitTime(25): tweakWaitTime(watch_.pollFrequency));
|
||||||
|
});
|
||||||
|
|
||||||
|
// just to avoid everybody always polling at the same time
|
||||||
|
function tweakWaitTime(t) {
|
||||||
|
t += (t*Math.random()/5*(Math.random()>.5 ? 1 : -1));
|
||||||
|
// now we need to tweak the start time to avoid a timing problem in htmlservice
|
||||||
|
// .. never start one within 750ms of the last one.
|
||||||
|
var now = new Date().getTime();
|
||||||
|
startTime_ = Math.max(now + t, startTime_+750);
|
||||||
|
return startTime_ - now;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// convenience function to endlessly poll and callback on any changes
|
||||||
|
self.watch = function (callback) {
|
||||||
|
if (typeof callback !== "function") {
|
||||||
|
throw 'callback to .watch() must be a function';
|
||||||
|
}
|
||||||
|
self.start()
|
||||||
|
.then (function(pack) {
|
||||||
|
|
||||||
|
if (pack.changed.active || pack.changed.data || pack.changed.sheets) {
|
||||||
|
callback(current_, pack, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// just keep going round recusrsively
|
||||||
|
if (!stopped_) {
|
||||||
|
self.watch(callback);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
['catch'](function(err) {
|
||||||
|
// this will have been dealt with further up, but we still need to repoll
|
||||||
|
if (!stopped_) {
|
||||||
|
self.watch(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this returns a promise
|
||||||
|
* which will be resolved when the server sends back changed data
|
||||||
|
* and rejected when there is no change
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
self.poll = function () {
|
||||||
|
|
||||||
|
status_.requested = new Date().getTime();
|
||||||
|
status_.serial ++;
|
||||||
|
|
||||||
|
// promises dont have finally() yet.
|
||||||
|
function finallyActions () {
|
||||||
|
status_.responded = new Date().getTime();
|
||||||
|
status_.totalWaiting += (status_.responded - status_.requested);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can get rejected from a few paces, so just pul this out
|
||||||
|
function rejectActions (reject,err) {
|
||||||
|
console.log (err);
|
||||||
|
status_.errors++;
|
||||||
|
finallyActions();
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pollWork();
|
||||||
|
|
||||||
|
// call the co-operating server function
|
||||||
|
function pollWork () {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
|
||||||
|
Provoke.run ("ServerWatcher", "poll", watch_)
|
||||||
|
.then (
|
||||||
|
function (pack) {
|
||||||
|
|
||||||
|
// if there's been some changes to data then store it
|
||||||
|
if (pack.data) {
|
||||||
|
|
||||||
|
if (watch_.domain.fiddler && watch_.domain.property === "Values") {
|
||||||
|
// it may fail because data is in midde of being updated
|
||||||
|
// but that's - it'll get it next time.
|
||||||
|
try {
|
||||||
|
current_.fiddler = new Fiddler().setValues(pack.data);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// dont want to count this as a valid piece of data yet
|
||||||
|
// so we'll pass on this poll result and treat it as a reject
|
||||||
|
|
||||||
|
return rejectActions(reject,err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watch_.checksum.data = pack.checksum.data;
|
||||||
|
current_.data = pack.data;
|
||||||
|
|
||||||
|
// if there's been some changes to active positions
|
||||||
|
if(pack.active) {
|
||||||
|
current_.active = pack.active;
|
||||||
|
watch_.checksum.active = pack.checksum.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's been some changes to sheets then store it
|
||||||
|
if (pack.sheets) {
|
||||||
|
current_.sheets = pack.sheets;
|
||||||
|
watch_.checksum.sheets = pack.checksum.sheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pack.data || pack.active || pack.sheets) {
|
||||||
|
status_.hits++;
|
||||||
|
}
|
||||||
|
finallyActions();
|
||||||
|
resolve (pack);
|
||||||
|
})
|
||||||
|
['catch'](function (err) {
|
||||||
|
// sometimes there will be network errors which can generally be ignored..
|
||||||
|
rejectActions (reject, err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return ns;
|
||||||
|
})(ClientWatcher || {});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue