mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
1
Makefile
1
Makefile
@@ -37,6 +37,7 @@ dist/iD.js: \
|
||||
js/id/id.js \
|
||||
js/id/services/*.js \
|
||||
js/id/util.js \
|
||||
js/id/util/*.js \
|
||||
js/id/geo.js \
|
||||
js/id/geo/*.js \
|
||||
js/id/actions.js \
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
<script src='js/lib/marked.js'></script>
|
||||
|
||||
<script src='js/id/id.js'></script>
|
||||
|
||||
<script src='js/id/util.js'></script>
|
||||
<script src='js/id/util/session_mutex.js'></script>
|
||||
|
||||
<script src='js/id/services/taginfo.js'></script>
|
||||
<script src='js/id/services/wikipedia.js'></script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ iD.History = function(context) {
|
||||
var stack, index, tree,
|
||||
imageryUsed = ['Bing'],
|
||||
dispatch = d3.dispatch('change', 'undone', 'redone'),
|
||||
lock = false;
|
||||
lock = iD.util.SessionMutex('lock');
|
||||
|
||||
function perform(actions) {
|
||||
actions = Array.prototype.slice.call(actions);
|
||||
@@ -273,39 +273,37 @@ iD.History = function(context) {
|
||||
},
|
||||
|
||||
save: function() {
|
||||
if (!lock) return history;
|
||||
context.storage(getKey('lock'), null);
|
||||
context.storage(getKey('saved_history'), this.toJSON() || null);
|
||||
if (lock.locked()) context.storage(getKey('saved_history'), history.toJSON() || null);
|
||||
return history;
|
||||
},
|
||||
|
||||
clearSaved: function() {
|
||||
if (!lock) return;
|
||||
context.storage(getKey('saved_history'), null);
|
||||
if (lock.locked()) context.storage(getKey('saved_history'), null);
|
||||
return history;
|
||||
},
|
||||
|
||||
lock: function() {
|
||||
if (context.storage(getKey('lock'))) return false;
|
||||
context.storage(getKey('lock'), true);
|
||||
lock = true;
|
||||
return lock;
|
||||
return lock.lock();
|
||||
},
|
||||
|
||||
unlock: function() {
|
||||
lock.unlock();
|
||||
},
|
||||
|
||||
// is iD not open in another window and it detects that
|
||||
// there's a history stored in localStorage that's recoverable?
|
||||
restorableChanges: function() {
|
||||
return lock && !!context.storage(getKey('saved_history'));
|
||||
return lock.locked() && !!context.storage(getKey('saved_history'));
|
||||
},
|
||||
|
||||
// load history from a version stored in localStorage
|
||||
restore: function() {
|
||||
if (!lock) return;
|
||||
if (!lock.locked()) return;
|
||||
|
||||
var json = context.storage(getKey('saved_history'));
|
||||
if (json) this.fromJSON(json);
|
||||
if (json) history.fromJSON(json);
|
||||
|
||||
context.storage(getKey('saved_history', null));
|
||||
|
||||
},
|
||||
|
||||
_getKey: getKey
|
||||
|
||||
@@ -126,6 +126,10 @@ iD.ui = function(context) {
|
||||
return context.save();
|
||||
};
|
||||
|
||||
window.onunload = function() {
|
||||
context.history().unlock();
|
||||
};
|
||||
|
||||
d3.select(window).on('resize.editor', function() {
|
||||
map.dimensions(m.dimensions());
|
||||
});
|
||||
|
||||
36
js/id/util/session_mutex.js
Normal file
36
js/id/util/session_mutex.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// A per-domain session mutex backed by a cookie and dead man's
|
||||
// switch. If the session crashes, the mutex will auto-release
|
||||
// after 5 seconds.
|
||||
|
||||
iD.util.SessionMutex = function(name) {
|
||||
var mutex = {},
|
||||
intervalID;
|
||||
|
||||
function renew() {
|
||||
var expires = new Date();
|
||||
expires.setSeconds(expires.getSeconds() + 5);
|
||||
document.cookie = name + '=1; expires=' + expires.toUTCString();
|
||||
}
|
||||
|
||||
mutex.lock = function() {
|
||||
if (intervalID) return true;
|
||||
var cookie = document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1");
|
||||
if (cookie) return false;
|
||||
renew();
|
||||
intervalID = window.setInterval(renew, 4000);
|
||||
return true;
|
||||
};
|
||||
|
||||
mutex.unlock = function() {
|
||||
if (!intervalID) return;
|
||||
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
clearInterval(intervalID);
|
||||
intervalID = null;
|
||||
};
|
||||
|
||||
mutex.locked = function() {
|
||||
return !!intervalID;
|
||||
};
|
||||
|
||||
return mutex;
|
||||
};
|
||||
@@ -189,6 +189,8 @@
|
||||
<script src='../js/id/presets/collection.js'></script>
|
||||
<script src='../js/id/presets/field.js'></script>
|
||||
|
||||
<script src='../js/id/util/session_mutex.js'></script>
|
||||
|
||||
<script src='../js/id/validate.js'></script>
|
||||
|
||||
<script src='../js/lib/locale.js'></script>
|
||||
@@ -265,7 +267,9 @@
|
||||
|
||||
<script src="spec/geo.js"></script>
|
||||
<script src="spec/taginfo.js"></script>
|
||||
|
||||
<script src="spec/util.js"></script>
|
||||
<script src='spec/util/session_mutex.js'></script>
|
||||
|
||||
<script src="spec/behavior/hash.js"></script>
|
||||
<script src="spec/behavior/hover.js"></script>
|
||||
|
||||
@@ -87,7 +87,9 @@
|
||||
|
||||
<script src="spec/geo.js"></script>
|
||||
<script src="spec/taginfo.js"></script>
|
||||
|
||||
<script src="spec/util.js"></script>
|
||||
<script src='spec/util/session_mutex.js'></script>
|
||||
|
||||
<script src="spec/behavior/hash.js"></script>
|
||||
<script src="spec/behavior/hover.js"></script>
|
||||
|
||||
@@ -244,52 +244,6 @@ describe("iD.History", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#lock", function() {
|
||||
it("acquires lock if possible", function() {
|
||||
expect(history.lock()).to.be.true;
|
||||
expect(history.lock()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#save", function() {
|
||||
it("doesn't do anything if it doesn't have the lock", function() {
|
||||
var key = history._getKey('saved_history');
|
||||
context.storage(key, null);
|
||||
history.save();
|
||||
expect(context.storage(key)).to.be.null;
|
||||
context.storage(key, 'something');
|
||||
expect(context.storage(key)).to.equal('something');
|
||||
history.save();
|
||||
context.storage(key, null);
|
||||
});
|
||||
|
||||
it("saves to localStorage", function() {
|
||||
var node = iD.Node({ id: 'n' });
|
||||
history.lock();
|
||||
history.perform(iD.actions.AddEntity(node));
|
||||
history.save();
|
||||
var saved = JSON.parse(context.storage(history._getKey('saved_history')));
|
||||
expect(saved.stack[1].modified[0]).to.eql('nv0');
|
||||
});
|
||||
});
|
||||
|
||||
describe("#restore", function() {
|
||||
it("saves and restores a created and deleted entities", function() {
|
||||
var node = iD.Node({ id: 'n' }),
|
||||
node2 = iD.Node({ id: 'n2' });
|
||||
history.lock();
|
||||
history.perform(iD.actions.AddEntity(node));
|
||||
history.perform(iD.actions.AddEntity(node2));
|
||||
history.perform(iD.actions.DeleteNode('n2'));
|
||||
history.save();
|
||||
history.reset();
|
||||
expect(history.graph().hasEntity('n')).to.be.undefined
|
||||
history.restore();
|
||||
expect(history.graph().entity('n').id).to.equal('n');
|
||||
expect(history.graph().hasEntity('n2')).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe("#toJSON", function() {
|
||||
it("generates v2 JSON", function() {
|
||||
var node = iD.Node({id: 'n-1'});
|
||||
|
||||
94
test/spec/util/session_mutex.js
Normal file
94
test/spec/util/session_mutex.js
Normal file
@@ -0,0 +1,94 @@
|
||||
describe("iD.util.SessionMutex", function() {
|
||||
var clock, a, b;
|
||||
|
||||
beforeEach(function () {
|
||||
clock = sinon.useFakeTimers(Date.now());
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
clock.restore();
|
||||
if (a) a.unlock();
|
||||
if (b) b.unlock();
|
||||
});
|
||||
|
||||
describe("#lock", function() {
|
||||
it("returns true when it gets a lock", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
expect(a.lock()).to.equal(true);
|
||||
});
|
||||
|
||||
it("returns true when already locked", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
a.lock();
|
||||
expect(a.lock()).to.equal(true);
|
||||
});
|
||||
|
||||
it("returns false when the lock is held by another session", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
a.lock();
|
||||
|
||||
b = iD.util.SessionMutex('name');
|
||||
expect(b.lock()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#locked", function() {
|
||||
it("returns false by default", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
expect(a.locked()).to.equal(false);
|
||||
});
|
||||
|
||||
it("returns true when locked", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
a.lock();
|
||||
expect(a.locked()).to.equal(true);
|
||||
});
|
||||
|
||||
it("returns false when unlocked", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
a.lock();
|
||||
a.unlock();
|
||||
expect(a.locked()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#unlock", function() {
|
||||
it("unlocks the mutex", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
a.lock();
|
||||
a.unlock();
|
||||
|
||||
b = iD.util.SessionMutex('name');
|
||||
expect(b.lock()).to.equal(true);
|
||||
});
|
||||
|
||||
it("does nothing when the lock is held by another session", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
a.lock();
|
||||
|
||||
b = iD.util.SessionMutex('name');
|
||||
b.unlock();
|
||||
|
||||
expect(a.locked()).to.equal(true);
|
||||
});
|
||||
|
||||
it("does nothing when not locked", function() {
|
||||
a = iD.util.SessionMutex('name');
|
||||
a.unlock();
|
||||
expect(a.locked()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("namespaces locks", function() {
|
||||
a = iD.util.SessionMutex('a');
|
||||
a.lock();
|
||||
|
||||
b = iD.util.SessionMutex('b');
|
||||
expect(b.locked()).to.equal(false);
|
||||
expect(b.lock()).to.equal(true);
|
||||
});
|
||||
|
||||
it("automatically unlocks when a session crashes", function() {
|
||||
// Tested manually.
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user