mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Issue #640 - migrate backup service to indexeddb
This commit is contained in:
parent
ed749a747f
commit
f9cb631acb
|
@ -29,18 +29,19 @@
|
||||||
|
|
||||||
ns.ImportController.prototype.initRestoreSession_ = function () {
|
ns.ImportController.prototype.initRestoreSession_ = function () {
|
||||||
var previousSessionContainer = document.querySelector('.previous-session');
|
var previousSessionContainer = document.querySelector('.previous-session');
|
||||||
var previousInfo = pskl.app.backupService.getPreviousPiskelInfo();
|
pskl.app.backupService.getPreviousPiskelInfo().then(function (previousInfo) {
|
||||||
if (previousInfo) {
|
if (previousInfo) {
|
||||||
var previousSessionTemplate_ = pskl.utils.Template.get('previous-session-info-template');
|
var previousSessionTemplate_ = pskl.utils.Template.get('previous-session-info-template');
|
||||||
var date = pskl.utils.DateUtils.format(previousInfo.date, '{{H}}:{{m}} - {{Y}}/{{M}}/{{D}}');
|
var date = pskl.utils.DateUtils.format(previousInfo.date, '{{H}}:{{m}} - {{Y}}/{{M}}/{{D}}');
|
||||||
previousSessionContainer.innerHTML = pskl.utils.Template.replace(previousSessionTemplate_, {
|
previousSessionContainer.innerHTML = pskl.utils.Template.replace(previousSessionTemplate_, {
|
||||||
name : previousInfo.name,
|
name : previousInfo.name,
|
||||||
date : date
|
date : date
|
||||||
});
|
});
|
||||||
this.addEventListener('.restore-session-button', 'click', this.onRestorePreviousSessionClick_);
|
this.addEventListener('.restore-session-button', 'click', this.onRestorePreviousSessionClick_);
|
||||||
} else {
|
} else {
|
||||||
previousSessionContainer.innerHTML = 'No piskel backup was found on this browser.';
|
previousSessionContainer.innerHTML = 'No piskel backup was found on this browser.';
|
||||||
}
|
}
|
||||||
|
}.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.ImportController.prototype.closeDrawer_ = function () {
|
ns.ImportController.prototype.closeDrawer_ = function () {
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
this.savePath = null;
|
this.savePath = null;
|
||||||
this.fps = fps;
|
this.fps = fps;
|
||||||
|
// This id is used to keep track of sessions in the BackupService.
|
||||||
|
this.sessionId = pskl.utils.Uuid.generate();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ',');
|
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ',');
|
||||||
|
|
|
@ -1,65 +1,197 @@
|
||||||
(function () {
|
(function () {
|
||||||
var ns = $.namespace('pskl.service');
|
var ns = $.namespace('pskl.service');
|
||||||
|
|
||||||
// 1 minute = 1000 * 60
|
var DB_NAME = 'PiskelSessionsDatabase';
|
||||||
var BACKUP_INTERVAL = 1000 * 60;
|
var DB_VERSION = 1;
|
||||||
|
|
||||||
|
var ONE_SECOND = 1000;
|
||||||
|
var ONE_MINUTE = 60 * ONE_SECOND;
|
||||||
|
|
||||||
|
// Save every minute = 1000 * 60
|
||||||
|
var BACKUP_INTERVAL = ONE_MINUTE;
|
||||||
|
// Store a new snapshot every 5 minutes.
|
||||||
|
var SNAPSHOT_INTERVAL = ONE_MINUTE * 5;
|
||||||
|
// Store up to 12 snapshots for a piskel session, min. 1 hour of work
|
||||||
|
var MAX_SNAPSHOTS_PER_SESSION = 12;
|
||||||
|
|
||||||
|
var _requestPromise = function (req) {
|
||||||
|
var deferred = Q.defer();
|
||||||
|
req.onsuccess = deferred.resolve.bind(deferred);
|
||||||
|
req.onerror = deferred.reject.bind(deferred);
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
ns.BackupService = function (piskelController) {
|
ns.BackupService = function (piskelController) {
|
||||||
this.piskelController = piskelController;
|
this.piskelController = piskelController;
|
||||||
this.lastHash = null;
|
this.lastHash = null;
|
||||||
|
this.nextSnapshotDate = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.BackupService.prototype.init = function () {
|
ns.BackupService.prototype.init = function () {
|
||||||
var previousPiskel = window.localStorage.getItem('bkp.next.piskel');
|
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
var previousInfo = window.localStorage.getItem('bkp.next.info');
|
|
||||||
if (previousPiskel && previousInfo) {
|
|
||||||
this.savePiskel_('prev', previousPiskel, previousInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
request.onerror = this.onRequestError_.bind(this);
|
||||||
|
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);
|
||||||
|
request.onsuccess = this.onRequestSuccess_.bind(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.onRequestError_ = function (event) {
|
||||||
|
console.log('Could not initialize the piskel backup database');
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.onUpgradeNeeded_ = function (event) {
|
||||||
|
// Set this.db early to allow migration scripts to access it in oncomplete.
|
||||||
|
this.db = event.target.result;
|
||||||
|
|
||||||
|
// Create an object store "piskels" with the autoIncrement flag set as true.
|
||||||
|
var objectStore = this.db.createObjectStore('snapshots', { keyPath: 'id', autoIncrement : true });
|
||||||
|
|
||||||
|
objectStore.createIndex('session_id', 'session_id', { unique: false });
|
||||||
|
objectStore.createIndex('date', 'date', { unique: false });
|
||||||
|
objectStore.createIndex('session_id, date', ['session_id', 'date'], { unique: false });
|
||||||
|
|
||||||
|
objectStore.transaction.oncomplete = function(event) {
|
||||||
|
// TODO: Migrate existing data from local storage?
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.onRequestSuccess_ = function (event) {
|
||||||
|
this.db = event.target.result;
|
||||||
window.setInterval(this.backup.bind(this), BACKUP_INTERVAL);
|
window.setInterval(this.backup.bind(this), BACKUP_INTERVAL);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.openObjectStore_ = function () {
|
||||||
|
return this.db.transaction(['snapshots'], 'readwrite').objectStore('snapshots');
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.createSnapshot = function (snapshot) {
|
||||||
|
var objectStore = this.openObjectStore_();
|
||||||
|
var request = objectStore.add(snapshot);
|
||||||
|
return _requestPromise(request);
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.replaceSnapshot = function (snapshot, replacedSnapshot) {
|
||||||
|
snapshot.id = replacedSnapshot.id;
|
||||||
|
|
||||||
|
var objectStore = this.openObjectStore_();
|
||||||
|
var request = objectStore.put(snapshot);
|
||||||
|
return _requestPromise(request);
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.deleteSnapshot = function (snapshot) {
|
||||||
|
var objectStore = this.openObjectStore_();
|
||||||
|
var request = objectStore.delete(snapshot.id);
|
||||||
|
return _requestPromise(request);
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.getSnapshotsBySessionId_ = function (sessionId) {
|
||||||
|
// Create the backup promise.
|
||||||
|
var deferred = Q.defer();
|
||||||
|
|
||||||
|
// Open a transaction to the snapshots object store.
|
||||||
|
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
|
||||||
|
|
||||||
|
// Loop on all the saved snapshots for the provided piskel id
|
||||||
|
var index = objectStore.index('session_id, date');
|
||||||
|
var keyRange = IDBKeyRange.bound(
|
||||||
|
[sessionId, 0],
|
||||||
|
[sessionId, Infinity]
|
||||||
|
);
|
||||||
|
|
||||||
|
var snapshots = [];
|
||||||
|
// Ordered by date in descending order.
|
||||||
|
index.openCursor(keyRange, 'prev').onsuccess = function(event) {
|
||||||
|
var cursor = event.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
snapshots.push(cursor.value);
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
console.log('consumed all piskel snapshots');
|
||||||
|
deferred.resolve(snapshots);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
ns.BackupService.prototype.backup = function () {
|
ns.BackupService.prototype.backup = function () {
|
||||||
var piskel = this.piskelController.getPiskel();
|
var piskel = this.piskelController.getPiskel();
|
||||||
var descriptor = piskel.getDescriptor();
|
|
||||||
var hash = piskel.getHash();
|
var hash = piskel.getHash();
|
||||||
var info = {
|
|
||||||
name : descriptor.name,
|
|
||||||
description : descriptor.info,
|
|
||||||
date : Date.now(),
|
|
||||||
hash : hash
|
|
||||||
};
|
|
||||||
|
|
||||||
// Do not save an unchanged piskel
|
// Do not save an unchanged piskel
|
||||||
if (hash !== this.lastHash) {
|
if (hash === this.lastHash) {
|
||||||
this.lastHash = hash;
|
return;
|
||||||
var serializedPiskel = pskl.utils.serialization.Serializer.serialize(piskel);
|
|
||||||
this.savePiskel_('next', serializedPiskel, JSON.stringify(info));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
ns.BackupService.prototype.getPreviousPiskelInfo = function () {
|
// Update the hash
|
||||||
var previousInfo = window.localStorage.getItem('bkp.prev.info');
|
// TODO: should only be done after a successfull save.
|
||||||
if (previousInfo) {
|
this.lastHash = hash;
|
||||||
return JSON.parse(previousInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ns.BackupService.prototype.load = function() {
|
// Prepare the backup snapshot.
|
||||||
var previousPiskel = window.localStorage.getItem('bkp.prev.piskel');
|
var descriptor = piskel.getDescriptor();
|
||||||
previousPiskel = JSON.parse(previousPiskel);
|
var date = Date.now();
|
||||||
|
var snapshot = {
|
||||||
|
session_id: piskel.sessionId,
|
||||||
|
date: date,
|
||||||
|
name: descriptor.name,
|
||||||
|
description: descriptor.description,
|
||||||
|
serialized: pskl.utils.serialization.Serializer.serialize(piskel)
|
||||||
|
};
|
||||||
|
|
||||||
pskl.utils.serialization.Deserializer.deserialize(previousPiskel, function (piskel) {
|
this.getSnapshotsBySessionId_(piskel.sessionId).then(function (snapshots) {
|
||||||
pskl.app.piskelController.setPiskel(piskel);
|
var latest = snapshots[0];
|
||||||
|
|
||||||
|
if (latest && date < this.nextSnapshotDate) {
|
||||||
|
// update the latest snapshot
|
||||||
|
return this.replaceSnapshot(snapshot, latest);
|
||||||
|
} else {
|
||||||
|
// add a new snapshot
|
||||||
|
this.nextSnapshotDate = date + SNAPSHOT_INTERVAL;
|
||||||
|
return this.createSnapshot(snapshot).then(function () {
|
||||||
|
if (snapshots.length >= MAX_SNAPSHOTS_PER_SESSION) {
|
||||||
|
// remove oldest snapshot
|
||||||
|
return this.deleteSnapshot(snapshots[snapshots.length - 1]);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
}.bind(this)).catch(function (e) {
|
||||||
|
console.log('Backup failed');
|
||||||
|
console.error(e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.BackupService.prototype.savePiskel_ = function (type, piskel, info) {
|
ns.BackupService.prototype.getPreviousPiskelInfo = function () {
|
||||||
try {
|
// Create the backup promise.
|
||||||
window.localStorage.setItem('bkp.' + type + '.piskel', piskel);
|
var deferred = Q.defer();
|
||||||
window.localStorage.setItem('bkp.' + type + '.info', info);
|
|
||||||
} catch (e) {
|
// Open a transaction to the snapshots object store.
|
||||||
console.error('Could not save piskel backup in localStorage.', e);
|
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
|
||||||
}
|
|
||||||
|
var sessionId = this.piskelController.getPiskel().sessionId;
|
||||||
|
var index = objectStore.index('date');
|
||||||
|
var range = IDBKeyRange.upperBound(Infinity);
|
||||||
|
index.openCursor(range, 'prev').onsuccess = function(event) {
|
||||||
|
var cursor = event.target.result;
|
||||||
|
var snapshot = cursor && cursor.value;
|
||||||
|
if (snapshot && snapshot.session_id === sessionId) {
|
||||||
|
// Skip snapshots for the current session.
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
deferred.resolve(snapshot);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.BackupService.prototype.load = function() {
|
||||||
|
this.getPreviousPiskelInfo().then(function (snapshot) {
|
||||||
|
pskl.utils.serialization.Deserializer.deserialize(
|
||||||
|
JSON.parse(snapshot.serialized),
|
||||||
|
function (piskel) {
|
||||||
|
pskl.app.piskelController.setPiskel(piskel);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user