Skip to content

Commit

Permalink
Issue #640 - extract backup database code to dedicated file
Browse files Browse the repository at this point in the history
  • Loading branch information
juliandescottes committed Jun 12, 2017
1 parent ab28567 commit d96bd94
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 118 deletions.
140 changes: 140 additions & 0 deletions src/js/database/BackupDatabase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
(function () {
var ns = $.namespace('pskl.database');

var DB_NAME = 'PiskelSessionsDatabase';
var DB_VERSION = 1;

var _requestPromise = function (req) {
var deferred = Q.defer();
req.onsuccess = deferred.resolve.bind(deferred);
req.onerror = deferred.reject.bind(deferred);
return deferred.promise;
};

ns.BackupDatabase = function (options) {
options = options || {};

this.db = null;
this.onUpgrade = options.onUpgrade;
};

ns.BackupDatabase.prototype.init = function () {
this.initDeferred_ = Q.defer();

var request = window.indexedDB.open(DB_NAME, DB_VERSION);

request.onerror = this.onRequestError_.bind(this);
request.onsuccess = this.onRequestSuccess_.bind(this);
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);

return this.initDeferred_.promise;
};

ns.BackupDatabase.prototype.onRequestError_ = function (event) {
console.log('Could not initialize the piskel backup database');
this.initDeferred_.reject();
};

ns.BackupDatabase.prototype.onRequestSuccess_ = function (event) {
this.db = event.target.result;
this.initDeferred_.resolve(this.db);
};

ns.BackupDatabase.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) {
if (typeof this.onUpgrade == 'function') {
this.onUpgrade(this.db);
}
}.bind(this);
};

ns.BackupDatabase.prototype.openObjectStore_ = function () {
return this.db.transaction(['snapshots'], 'readwrite').objectStore('snapshots');
};

ns.BackupDatabase.prototype.createSnapshot = function (snapshot) {
var objectStore = this.openObjectStore_();
var request = objectStore.add(snapshot);
return _requestPromise(request);
};

ns.BackupDatabase.prototype.replaceSnapshot = function (snapshot, replacedSnapshot) {
snapshot.id = replacedSnapshot.id;

var objectStore = this.openObjectStore_();
var request = objectStore.put(snapshot);
return _requestPromise(request);
};

ns.BackupDatabase.prototype.deleteSnapshot = function (snapshot) {
var objectStore = this.openObjectStore_();
var request = objectStore.delete(snapshot.id);
return _requestPromise(request);
};

ns.BackupDatabase.prototype.findLastSnapshot = function (accept) {
// Create the backup promise.
var deferred = Q.defer();

// Open a transaction to the snapshots object store.
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');

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;

// Resolve null if we couldn't find a matching snapshot.
if (!snapshot) {
deferred.resolve(null);
} else if (accept(snapshot)) {
deferred.resolve(snapshot);
} else {
cursor.continue();
}
};

return deferred.promise;
};

ns.BackupDatabase.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;
};
})();
130 changes: 12 additions & 118 deletions src/js/service/BackupService.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
(function () {
var ns = $.namespace('pskl.service');

var DB_NAME = 'PiskelSessionsDatabase';
var DB_VERSION = 1;

var ONE_SECOND = 1000;
var ONE_MINUTE = 60 * ONE_SECOND;

Expand All @@ -14,104 +11,18 @@
// 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) {
this.piskelController = piskelController;
this.lastHash = null;
this.nextSnapshotDate = -1;
};

ns.BackupService.prototype.init = function () {
var request = window.indexedDB.open(DB_NAME, DB_VERSION);

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);
};

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);
this.backupDatabase = new pskl.database.BackupDatabase();
};

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.init = function () {
this.backupDatabase.init().then(function () {
window.setInterval(this.backup.bind(this), BACKUP_INTERVAL);
}.bind(this));
};

ns.BackupService.prototype.backup = function () {
Expand All @@ -138,19 +49,19 @@
serialized: pskl.utils.serialization.Serializer.serialize(piskel)
};

this.getSnapshotsBySessionId_(piskel.sessionId).then(function (snapshots) {
this.backupDatabase.getSnapshotsBySessionId(piskel.sessionId).then(function (snapshots) {
var latest = snapshots[0];

if (latest && date < this.nextSnapshotDate) {
// update the latest snapshot
return this.replaceSnapshot(snapshot, latest);
return this.backupDatabase.replaceSnapshot(snapshot, latest);
} else {
// add a new snapshot
this.nextSnapshotDate = date + SNAPSHOT_INTERVAL;
return this.createSnapshot(snapshot).then(function () {
return this.backupDatabase.createSnapshot(snapshot).then(function () {
if (snapshots.length >= MAX_SNAPSHOTS_PER_SESSION) {
// remove oldest snapshot
return this.deleteSnapshot(snapshots[snapshots.length - 1]);
return this.backupDatabase.deleteSnapshot(snapshots[snapshots.length - 1]);
}
}.bind(this));
}
Expand All @@ -161,27 +72,10 @@
};

ns.BackupService.prototype.getPreviousPiskelInfo = function () {
// Create the backup promise.
var deferred = Q.defer();

// Open a transaction to the snapshots object store.
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;
return this.backupDatabase.findLastSnapshot(function (snapshot) {
return snapshot.session_id !== sessionId;
});
};

ns.BackupService.prototype.load = function() {
Expand Down
3 changes: 3 additions & 0 deletions src/piskel-script-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
"js/model/Palette.js",
"js/model/Piskel.js",

// Database (IndexedDB)
"js/database/BackupDatabase.js",

// Selection
"js/selection/SelectionManager.js",
"js/selection/BaseSelection.js",
Expand Down

0 comments on commit d96bd94

Please sign in to comment.