In eating my own dog food I have revised, corrected, improved and fixed the code I posted last week. The biggest changes are related to ensuring that all that cached data does not just accumulate and never gets cleared away.
Values set using the “GM_setValue” function are saved in the Firefox preferences back end and can be manually changed by typing “about:config” in the address bar and searching for the preference name “greasemonkey.scriptvals.namespace/name.foo” or the key that you set.
1 2 | var MaxCacheAge = 60 * 60 * 24 * 1000; // Max Age for the Cached data is 1 day in milliseconds var currentTime = new Date().valueOf(); // current datetime in milliseconds |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | function getIntersect(arr1, arr2){ /** * given 2 arrays returns an array with all elements in arr1 that are in arr2 * see http://www.falsepositives.com/index.php/2009/12/01/javascript-function-to-get-the-intersect-of-2-arrays/ for more details */ var r = [], o = {}, l = arr2.length, i, v; for (i = 0; i < l; i++) { o[arr2[i]] = true; } l = arr1.length; for (i = 0; i < l; i++) { v = arr1[i]; if (v in o) { r.push(v); } } return r; } function Array_diff(arr1, arr2){ /** * given 2 arrays returns an array with all elements in arr1 that are NOT in arr2 */ var r = [], o = {}, l = arr2.length, i, v; for (i = 0; i < l; i++) { o[arr2[i]] = true; } l = arr1.length; for (i = 0; i < l; i++) { v = arr1[i]; if (!(v in o)) { r.push(v); } } return r; } /** * add key to the array of values we are tracking (if it's not aready there) * @param {string} key */ function GM_setCachedDataTrackingValueKey(key){ var Tracking = eval(GM_getValue('CachedDataTracking_cache', "")); if (Tracking == "" || Tracking == undefined) { var Tracking = []; } var i, l = Tracking.length; for (i = 0; i < l; i++) { if (Tracking[i] === key) { return ""; // the key is already in the Tracking array so we can stop; } } Tracking.push(key); GM_setValue('CachedDataTracking_cache', uneval(Tracking)); } function GM_removeCachedDataTrackingValueKey(key){ console.log('GM_removeCachedDataTrackingValueKey,key=' + key); var Tracking = eval(GM_getValue('CachedDataTracking_cache', "")); if (Tracking == "" || Tracking == undefined) { return ""; } var i, l = Tracking.length, NewTracking = [], j = 0; for (i = 0; i < l; i++) { if (Tracking[i] != key) { NewTracking[j++] = Tracking[i]; } } GM_setValue('CachedDataTracking_cache', uneval(NewTracking)); GM_deleteValue(key); } /** * delete all cached values older than MaxCacheAge * @param {Number} MaxCacheAge 0=all. Required. */ function GM_flushCachedData(MaxCacheAge){ var Tracking = eval(GM_getValue('CachedDataTracking_cache', "")); if (!(Tracking == "" || Tracking == undefined)) { var l = Tracking.length, i, v, who, DeleteWhoFromTracking = []; for (i = 0; i < l; i++) { who = Tracking[i]; var DateofWho = GM_getValue(who + '_cacheDate', ""); // get the age of the cache for Who if (DateofWho != "") { if (MaxCacheAge == 0 || (DateofWho < (currentTime - MaxCacheAge))) { // if age is older that MaxCacheAge //the blank the date and the data and remove from CachedDataTracking_cache GM_deleteValue(who); GM_deleteValue(who + '_cacheDate', ""); DeleteWhoFromTracking.push(who) } } } if (DeleteWhoFromTracking.length > 0) { // if there is anyone to remove from tracking var NewTracking = Array_diff(Tracking, DeleteWhoFromTracking); //NewTracking is new array minus element from 2nd array if (NewTracking.length == 0) { GM_setValue('CachedDataTracking_cache', "") } else { GM_setValue('CachedDataTracking_cache', uneval(NewTracking)); } } } GM_setValue('CachedDataTracking_cacheDate', currentTime); } /** * GM_setCachedDataValue set the value, using the key name, and the cacheDate for the value based on the current datetime. * to see the values set use about:config in the firefox awsome bar and filter with greasemonkey.scriptvals.http://www.FollowRank.com/ * @param {String} key The key argument is a string of no fixed format. Required. * @param {Object} value The value argument can be a string, boolean, or integer. Required. * @returns void * */ function GM_setCachedDataValue(key, value){ // key is the name of the value stored // value is the value to be stored var currentTime = new Date().valueOf(); var raw = currentTime.valueOf(); GM_setValue(key, value); GM_setValue(key + '_cacheDate', raw.toString()); } /** * GM_getCachedDataValue returns the value indicated by the key if a) it exists, b) if it is less the maxDuration milliseconds old; otherwise return a blank. * @param {String} key key is the name of the value stored. key argument is a string of no fixed format. Required. * @param {Number} maxDuration is the Maximum Duration (or how old) in milliseconds the cached data can be. maxDuration is a number. Required. * @returns a Integer, String or Boolean */ function GM_getCachedDataValue(key, maxDuration){ // key is the name of the value stored // maxDuration is Maximum Duration (or how old) in milliseconds the cached data can be // note fillter for about:config is greasemonkey.scriptvals.http://www.FollowRank.com/ if (typeof maxDuration != "number") { console.error('maxDuration is NOT a number, but rather a ' + typeof maxDuration); return ""; } var currentTime = new Date(); var raw = GM_getValue(key + '_cacheDate', ""); // get the age of the cache if (raw != "") { var cache_dt = new Date(parseInt(raw)); var age = currentTime.getTime() - cache_dt.getTime(); var allowed_age = maxDuration; |
1 2 3 4 5 6 7 8 9 10 11 12 13 | if (age < = allowed_age) { // if the age is less than the max allowed age the get and return the cached value GM_setCachedDataTrackingValueKey(key); return GM_getValue(key, ""); } else {// if the age is greater than the max then blank out the cache date and value and return black GM_deleteValue(key); GM_deleteValue(key + '_cacheDate'); console.info('GM cahaced data was too old so I killed it! Age was = ' + age + ' and allowed aged was =' + allowed_age); } } return ""; } |
So there are several changes made to the code :
In rereading the Grease Monkey Wiki I spotted the GM_deleteValue function in it’s api wiki. ack!!
I moved 2 variables (MaxCacheAge and currentTime) outside the function themselves. They could almost be constants, but as long as they are only calcualted once and not hard coded! Also “currentTime” is stored in it’s milliseconds format since this is the only format I use it in.
I’ve included two utility functions : getIntersect (which returns an array with all elements in arr1 that are in arr2 as seen in a JavaScript Function to get the Intersect of 2 Arrays) and Array_diff (which returns an array with all elements in arr1 that are NOT in arr2)
The biggest change is the addition of 3 functions : GM_setCachedDataTrackingValueKey, GM_removeCachedDataTrackingValueKey and GM_flushCachedData which track the keys I have put in the Cache and ensure that nothing too old is ever stored there. I believe the first two are straight forward but it is flushCachedData that is more interesting / complicated. flushCachedData excepts a number which is the max age of all values in the cache which might be 48 hours (MaxCacheAge * 2) or 0 (Zero) in which case all values tracked in the cache are removed. I can see some further optimizations for these functions, but they solve the basic problem of all that cached data always accumulating and never getting cleared away.
The basic usage remains the same. The only thing I would add is that I have used it in the beginning of my GM script to do a cleanup
1 2 3 4 | var TrackingDate = eval(GM_getValue('CachedDataTracking_cacheDate', "")); if (TrackingDate < (currentTime - MaxCacheAge) ) { GM_flushCachedData(MaxCacheAge); //cleanup the cache by remove anything older than 24 hours } |
If you wanted to blow away all the values stored by a GM script just include (once only!!) this:
1 | var vals = GM_listValues().map(GM_deleteValue); |
Hope you find this useful. cache away…
If you wish t see and use it in practice then check out Follow Rank and Follows in Common for Twitter User Profiles