User:Erutuon/scripts/storageUtils.js

Note – after saving, you may have to bypass your browser’s cache to see the changes.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.

// {{documentation}}
// implicit dependencies : mediawiki.cookie,mw.storage
/* jshint maxerr:1048576, strict:true, undef:true, latedef:true, es5:true, sub:true */

/* global mw, $ */

(function StorageUtilsIIFE() {

// Lowest level wrapper that wraps `localStorage` or `$.cookie` and
// `$.removeCookie`.
var StorageWrapper = function() {
	this.cookiePreferences = {
		expires: 30, path: '/'
	};
};

StorageWrapper.prototype = {
	constructor: StorageWrapper,
	
	localStorageAvailable: mw.storage.get("localStorageTest") !== false,
	cookieAvailable: navigator.cookieEnabled,
	get storageAvailable () {
		return this.localStorageAvailable || this.cookieAvailable;
	},
	
	set: function(name, value) {
		if (this.localStorageAvailable) {
			localStorage[name] = value;
		} else {
			mw.cookie.set(name, value, {});
		}
	},
	
	get: function(name) {
		if (this.localStorageAvailable) {
			return localStorage[name];
		} else {
			return mw.cookie.get(name);
		}
	},
	
	remove: function(name) {
		if (this.localStorageAvailable) {
			delete localStorage[name];
		} else {
			return $.removeCookie(name);
		}
	},
};

var StorageByAddress = function (path, version) {
	console.log(arguments);
	if (typeof path !== "string")
		throw new TypeError("Expected string");
	
	// Call constructor for StorageWrapper object.
	StorageWrapper.call(this);
	
	this.path = path;
	this.version = version || 1;
};

StorageByAddress.prototype = Object.create(StorageWrapper.prototype, {
	constructor: { value: StorageByAddress },
	itemAddressPrefix: {
		get: function () {
			return "enwikt/" + this.path + "?v=" + this.version;
		},
	},
	makeAddress: {
		value: function(type) {
			return this.itemAddressPrefix + "&Type=" + type;
		},
	},
	getByType: {
		value: function(type) {
			return StorageWrapper.prototype.get.call(this, this.makeAddress(type));
		},
	},
	setByType: {
		value: function(type, value) {
			return StorageWrapper.prototype.set.call(this, this.makeAddress(type), value);
		},
	},
	removeByType: {
		value: function(type) {
			return StorageWrapper.prototype.remove.call(this, this.makeAddress(type));
		},
	}
});

// Expirable, lazy object storage. Inherits from `StorageByAddress` and
// `StorageWrapper`, but has its own `get` and `remove` methods and a `set` method
// that does nothing.
var CacheableStorage = function(productName, getProductCallback, expireInDays, version) {
	StorageByAddress.call(this, "CacheableStorage/" + productName, version);
	
	this.expireInDays = expireInDays;
	this.productName = productName;
	this.getProductCallback = getProductCallback;
	this._cachedData = null;
};

var secondsPerDay = 24 * 3600;

// callback function that must be bound using `Function.prototype.bind` so that
// `this` refers to the correct object
CacheableStorage.refreshDataHelper = function (data) {
	this.setByType("Type", typeof data);
	this.setByType("Expiration", new Date().toISOString());
	
	if (typeof(data) != "string")
		data = JSON.stringify(data);
	this.setByType("Data", data);
};

CacheableStorage.prototype = Object.create(StorageByAddress.prototype, {
	constructor: { value: CacheableStorage },
	refreshDataHelper: {
		get: function () {
			if (!this._refreshDataHelper)
				this._refreshDataHelper = CacheableStorage.refreshDataHelper.bind(this);
			return this._refreshDataHelper;
		},
	},
	refreshData: {
		value: function() {
			this.getProductCallback().then(this.refreshDataHelper);
		},
	},
	get: {
		value: function() {
			if (this._cachedData)
				return this._cachedData;
			
			var data = this.getByType("Data");
			var type = this.getByType("Type");
			var expiration = this.getByType("Expiration");
			if (data) {
				if (type != "string") data = JSON.parse(data);
				if ((new Date() - new Date(expiration)) / 1000 > this.expireInDays * secondsPerDay) {
					this.refreshData();
					return data;
				} else {
					this._cachedData = data;
					return this._cachedData;
				}
			} else {
				this.refreshData();
			}
			return null;
		},
	},
	// just to override `StorageWrapper.protoype.set`
	set: { value: function doesNothing() {}, },
	remove: {
		value: function () {
			this.removeByType("Type");
			this.removeByType("Expiration");
			this.removeByType("Data");
			this._cachedData = null;
		}
	},
	
	// For compatibility with old version, which had a different name for
	// the equivalent method:
	GetItem: {
		get: function() {
			return this.get;
		},
	},
});

/*
 * Not expirable. Inherits from `StorageByAddress` and `StorageWrapper`, but has its
 * own `get`, `set`, and `remove` methods.
 */
var ObjectStorage = function(contextName, version) {
	StorageByAddress.call(this, "CacheableStorage/" + contextName, version);
	
	this.contextName = contextName;
	this._cachedData = null;
};

ObjectStorage.prototype = Object.create(StorageByAddress.prototype, {
	constructor: { value: ObjectStorage },
	
	set: {
		value: function(obj) {
			var s = typeof(obj) == "string" ? obj : JSON.stringify(obj);
			this.setByType("Data", s);
			this.setByType("Type", typeof(obj));
			this._cachedData = obj;
		},
	},
	get: {
		value: function() {
			if (this._cachedData)
				return this._cachedData;
			
			var data = this.getByType("Data");
			var type = this.getByType("Type");
			if (data) {
				if (type != "string")
					data = JSON.parse(data);
				this._cachedData = data;
				return this._cachedData;
			}
			return null;
		},
	},
	remove: {
		value: function () {
			this.removeByType("Data");
			this.removeByType("Type");
			this._cachedData = null;
		},
	},
	
	// For compatibility with old version, which had different names for
	// equivalent methods:
	Get: {
		get: function () {
			return this.get;
		},
	},
	Set: {
		get: function () {
			return this.set;
		},
	},
});

window.MyStorage = {
	StorageWrapper: StorageWrapper,
	StorageByAddress: StorageByAddress,
	CacheableStorage: CacheableStorage,
	ObjectStorage: ObjectStorage,
};

})();