diff --git a/src/js/database/BackupDatabase.js b/src/js/database/BackupDatabase.js index f73f3e8..41fb673 100644 --- a/src/js/database/BackupDatabase.js +++ b/src/js/database/BackupDatabase.js @@ -176,4 +176,80 @@ return deferred.promise; }; + + ns.BackupDatabase.prototype.getSessions = 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 sessions = {}; + + var _createSession = function (snapshot) { + sessions[snapshot.sessionId] = { + startDate: snapshot.date, + endDate: snapshot.date, + name: snapshot.name, + id: snapshot.sessionId + }; + }; + + var _updateSessions = function (snapshot) { + var s = sessions[snapshot.sessionId]; + s.startDate = Math.min(s.startDate, snapshot.startDate); + s.endDate = Math.max(s.endDate, snapshot.endDate); + if (s.endDate === snapshot.endDate) { + s.name = snapshot.name; + } + }; + + 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) { + deferred.resolve(sessions); + } else { + if (sessions[snapshot.sessionId]) { + _createSession(snapshot); + } else { + _updateSessions(snapshot); + } + cursor.continue(); + } + }; + + return deferred.promise.then(function (sessions) { + // Convert the sessions map to an array. + return Object.keys(sessions).map(function (key) { + return sessions[key]; + }); + }); + }; + + ns.BackupDatabase.prototype.deleteSnapshotsForSession = 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'); + var keyRange = IDBKeyRange.only(sessionId); + + index.openCursor(keyRange).onsuccess = function(event) { + var cursor = event.target.result; + if (cursor) { + cursor.delete(); + cursor.continue(); + } else { + deferred.resolve(); + } + }; + + return deferred.promise; + }; })(); diff --git a/src/js/service/BackupService.js b/src/js/service/BackupService.js index c8c1f28..d925078 100644 --- a/src/js/service/BackupService.js +++ b/src/js/service/BackupService.js @@ -10,13 +10,15 @@ 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 MAX_SESSIONS = 10; - ns.BackupService = function (piskelController) { + ns.BackupService = function (piskelController, backupDatabase) { this.piskelController = piskelController; this.lastHash = null; this.nextSnapshotDate = -1; - this.backupDatabase = new pskl.database.BackupDatabase(); + // backupDatabase can be provided for testing purposes. + this.backupDatabase = backupDatabase || new pskl.database.BackupDatabase(); }; ns.BackupService.prototype.init = function () { @@ -25,13 +27,18 @@ }.bind(this)); }; + // This is purely exposed for testing, so that backup dates can be set programmatically. + ns.BackupService.prototype.currentDate_ = function () { + return Date.now(); + }; + ns.BackupService.prototype.backup = function () { var piskel = this.piskelController.getPiskel(); var hash = piskel.getHash(); // Do not save an unchanged piskel if (hash === this.lastHash) { - return; + return Q.resolve(); } // Update the hash @@ -40,7 +47,7 @@ // Prepare the backup snapshot. var descriptor = piskel.getDescriptor(); - var date = Date.now(); + var date = this.currentDate_(); var snapshot = { session_id: piskel.sessionId, date: date, @@ -49,7 +56,7 @@ serialized: pskl.utils.serialization.Serializer.serialize(piskel) }; - this.backupDatabase.getSnapshotsBySessionId(piskel.sessionId).then(function (snapshots) { + return this.backupDatabase.getSnapshotsBySessionId(piskel.sessionId).then(function (snapshots) { var latest = snapshots[0]; if (latest && date < this.nextSnapshotDate) { @@ -64,10 +71,27 @@ // remove oldest snapshot return this.backupDatabase.deleteSnapshot(snapshots[snapshots.length - 1]); } + }.bind(this)).then(function () { + var isNewSession = !latest; + if (!isNewSession) { + return; + } + return this.backupDatabase.getSessions().then(function (sessions) { + if (sessions.length <= MAX_SESSIONS) { + // If MAX_SESSIONS has not been reached, no need to delete + // previous sessions. + return; + } + + var oldestSession = sessions.sort(function (s1, s2) { + return s1.startDate - s2.startDate; + })[0].id; + + return this.backupDatabase.deleteSnapshotsForSession(oldestSession); + }.bind(this)); }.bind(this)); } }.bind(this)).catch(function (e) { - console.log('Backup failed'); console.error(e); }); };