Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persistence WIP #53

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions lib/_Storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require('es6-shim');
var fs = require('fs');
var pathlib = require('path');
var crypto = require('crypto')
var Promise = require('rsvp').Promise;

module.exports = _Storage;

_Storage.cache = {};

_Storage.heapPrefix = 'heap' + String.fromCharCode(0);

/**
* Generate a heap id. Just a sha of the hrtime.
*/
_Storage.id = function () {
var tuple = process.hrtime();
var time = tuple[0] * 1e9 + tuple[1];
var shasum = crypto.createHash('sha1');
shasum.update(''+time);
return shasum.digest('hex');
};

/**
* _Storage
* Promise-y persistence layer.
* Note: non-heap objects cannot reference each other.
* TODO: save to files. Currently it persists via the cache above.
*/

function _Storage(namespace) {
this.heap = !namespace;
this.namespace = namespace || _Storage.id();
this.readyPromise = Promise.resolve();
if (this.heap) {
_Storage.cache[this.reference({ _storage: this })] = this;
}
}

/**
* Setup storage with some default data.
* TODO: This should ignore the defaults if it can retrieve itself from storage.
*/
_Storage.prototype.init = function(data) {
this.readyPromise = Promise.resolve(data);
return this.readyPromise;
};

/**
* Saves passed state to disk, and resets the ready promise to match.
* TODO: obviously, doesn't persist
*/
_Storage.prototype.persist = function (data) {
this.readyPromise = Promise.resolve(data);
return this.readyPromise;
};

/**
* Produce a heap reference for an object, if possible.
*/
_Storage.prototype.reference = function (storedObject) {
if (!storedObject._storage || !storedObject._storage.heap) {
return storedObject;
}
return _Storage.heapPrefix + storedObject._storage.namespace;
};

/**
* Retrieve some data from the heap using a heap reference
*/
_Storage.prototype.dereference = function (storedObjectReference) {
// TODO: guard this better
if (!storedObjectReference.startsWith(_Storage.heapPrefix)) {
throw Error('Reference is not valid: ' + storedObjectReference)
}
// TODO: get from storage
if (_Storage.cache[namespace]) {
return Promise.resolve(_Storage.cache[namespace]);
} else {
return Promise.reject(new Error('NullPointerException: could not find that thing.'));
}
};
7 changes: 4 additions & 3 deletions lib/_Worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ var Request = require('../spec/Request');
util.inherits(_Worker, EventEmitter);
module.exports = _Worker;

function _Worker(url, body) {
function _Worker(url, glob, body) {
EventEmitter.call(this);

var worker = this;
var loadScripts = createScriptLoader(url);
var scope = new ServiceWorker(url);
var scope = new ServiceWorker(url, glob);

this.url = url;
this.glob = glob;
this.body = body;
this.scope = scope;

Expand All @@ -46,7 +47,7 @@ function _Worker(url, body) {
console.log(chalk.red('Install failed for worker version:'), chalk.yellow(worker.scope.version));
throw err;
});

// these promises are resolved in install()
this._activateCalled = false;
this._activateResolver = null;
Expand Down
6 changes: 4 additions & 2 deletions lib/_WorkerRegistration.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var fs = require('fs');
var pathlib = require('path');
var URL = require('dom-urls');
var chalk = require('chalk');

Expand All @@ -18,7 +20,7 @@ function _WorkerRegistration(url, glob) {

_WorkerRegistration.prototype.update = function() {
var workerRegistration = this;

console.log(chalk.blue('Fetching worker from', this.url.toString()));

// TODO: we have a race condition here
Expand All @@ -36,7 +38,7 @@ _WorkerRegistration.prototype.update = function() {
return;
}

var nextWorker = new _Worker(workerRegistration.url, body);
var nextWorker = new _Worker(workerRegistration.url, workerRegistration.glob, body);

workerRegistration.nextWorker = nextWorker;

Expand Down
2 changes: 2 additions & 0 deletions spec/Cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var AsyncMap = require('../spec/AsyncMap');
var Request = require('../spec/Request');
var fetch = require('../spec/fetch');
var URL = require('dom-urls');
var _Storage = require('../lib/_Storage');

module.exports = Cache;

Expand Down Expand Up @@ -40,6 +41,7 @@ Cache.persistValue = function (key, value) {

function Cache() {
this.items = new AsyncMap();
hide(this, '_storage', new _Storage());
var args = [].slice.call(arguments);
args.forEach(function (url) {
this.add(new URL(url));
Expand Down
46 changes: 43 additions & 3 deletions spec/CacheList.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,61 @@
var util = require('util');
var hide = require('hide-key');
var Cache = require('../spec/Cache');
var URL = require('dom-urls');
var AsyncMap = require('../spec/AsyncMap');
var Promise = require('rsvp').Promise;
var _Storage = require('../lib/_Storage');

util.inherits(CacheList, AsyncMap);
module.exports = CacheList;

function CacheList() {
CacheList.makeNamespace = function (origin) {
return origin
.replace('//', '')
.replace(/[^a-z0-9]/ig, '-')
.replace(/\-{2,}/g, '');
};

/**
* CacheList
*/

function CacheList(url) {
AsyncMap.apply(this, arguments);
this.origin = 'none://none';
url = new URL(url);
this.origin = url.protocol + '//' + url.host;
hide(this, '_namespace', CacheList.makeNamespace(this.origin));
hide(this, '_storage', new _Storage(this._namespace));
this._storage.init({
caches: []
});
console.log('this._namespace', this._namespace);
console.log('this._storage', this._storage);
}

/**
* Add a cache to the CacheList. This adds a reference to the cache on the heap, not the cache
* itself. We then wrap get to dereference it.
*/
CacheList.prototype.set = function (key, cache) {
if (!(cache instanceof Cache)) {
throw Error('CacheList only accepts Caches.');
}
return AsyncMap.prototype.set.call(this, key, cache);
console.log('== CacheList#set ========================');
return this._storage.readyPromise
.then(function (data) {
data.caches.push({
key: key,
cache: this._storage.reference(cache)
});
console.log('data.caches', data.caches);
return this._storage.persist(data);
}.bind(this)).then(function () {
return AsyncMap.prototype.set.call(this, key, cache);
}.bind(this))
.catch(function (why) {
console.error(why);
});
};

CacheList.prototype.ready = function () {
Expand Down
11 changes: 3 additions & 8 deletions spec/ServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ var chalk = require('chalk');

module.exports = ServiceWorker;

function ServiceWorker(workerUrl) {
function ServiceWorker(workerUrl, glob) {
this._eventListeners = [];

// TODO: replace this with a constructor param.
// Something like _CacheLists should store a CacheList per origin
this.caches = new CacheList();

// TODO: work out why this is needed, hopefully remove
this.caches.origin = workerUrl.origin;
this.caches = new CacheList(glob);

// importScripts requires execution context info, so it's handled in _Worker.js
// this.importScripts = ...
Expand Down Expand Up @@ -67,8 +62,8 @@ ServiceWorker.prototype.clearInterval = clearInterval;

ServiceWorker.prototype.Map = require('../spec/Map');
ServiceWorker.prototype.AsyncMap = require('../spec/AsyncMap');
ServiceWorker.prototype.Cache = require('../spec/Cache');
ServiceWorker.prototype.CacheList = require('../spec/CacheList');
ServiceWorker.prototype.Cache = require('../spec/Cache');
ServiceWorker.prototype.Event = require('../spec/Event');
ServiceWorker.prototype.InstallEvent = require('../spec/InstallEvent');
ServiceWorker.prototype.FetchEvent = require('../spec/FetchEvent');
Expand Down