Using GreaseMonkey’s persistent data functions as a aged Cache, revised.

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

Leave a Reply