Created
July 2, 2014 23:13
-
-
Save mcav/046ca990628b46abcacd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
define(function(require) { | |
/** | |
* The AlarmDatabase stores a list of alarms in IndexedDB. All | |
* mutation operations return Promises, for easy chaining and state | |
* management. This module returns the one-and-only instance of | |
* AlarmDatabase. | |
*/ | |
function AlarmDatabase(dbName, storeName, version) { | |
this.dbName = dbName; | |
this.storeName = storeName; | |
this.version = version; | |
console.log('mcav: init AlarmDatabase', dbName, storeName, version); | |
console.log('mcav: ORIGIN', document.domain); | |
this.withDatabase = new Promise((resolve, reject) => { | |
var request = indexedDB.open(this.dbName, this.version); | |
request.onupgradeneeded = (event) => { | |
console.log('mcav: start onupgradeneeded'); | |
var db = event.target.result; | |
// Ensure the object store exists. | |
if (!db.objectStoreNames.contains(this.storeName)) { | |
console.log('mcav: creating object store'); | |
db.createObjectStore(this.storeName, { | |
keyPath: 'id', | |
autoIncrement: true | |
}); | |
} | |
console.log('mcav: end onupgradeneeded'); | |
}; | |
request.onerror = (() => reject(request.errorCode)); | |
request.onsuccess = ((event) => resolve(event.target.result)); | |
}).then((db) => { | |
console.log('mcav: start .then(db)'); | |
// Only return when all of the alarms have been upgraded. | |
return new Promise((resolve, reject) => { | |
console.log('mcav: calling db.transaction'); | |
// Go through existing alarms here, and make sure they conform | |
// to the latest spec (upgrade old versions, etc.). | |
var transaction = db.transaction(this.storeName, 'readwrite'); | |
var store = transaction.objectStore(this.storeName); | |
var cursor = store.openCursor(); | |
var loggd; | |
cursor.onsuccess = (event) => { | |
if (!loggd) { | |
console.log('mcav: cursor.onsuccess'); loggd=1; | |
} | |
var cursor = event.target.result; | |
if (cursor) { | |
store.put(this.normalizeAlarmRecord(cursor.value)); | |
cursor.continue(); | |
} | |
}; | |
transaction.oncomplete = (() => resolve(db)); | |
transaction.onerror = ((evt) => reject(evt.target.errorCode)); | |
}); | |
}).catch(function(err) { | |
console.log('mcav: catch'); | |
// Explicit err.toString() coercion needed to see a message. | |
console.error('AlarmDatabase Fatal Error:', err.toString()); | |
}); | |
} | |
AlarmDatabase.prototype = { | |
/** | |
* Given an Alarm's JSON data (as returned by IndexedDB), | |
* normalize any properties to ensure it conforms to the most | |
* current Alarm specification. | |
*/ | |
normalizeAlarmRecord: function(alarm) { | |
if (!alarm.registeredAlarms) { | |
alarm.registeredAlarms = {}; | |
} | |
if (typeof alarm.enabled !== 'undefined') { | |
delete alarm.enabled; | |
} | |
if (typeof alarm.normalAlarmId !== 'undefined') { | |
alarm.registeredAlarms.normal = alarm.normalAlarmId; | |
delete alarm.normalAlarmId; | |
} | |
if (typeof alarm.snoozeAlarmId !== 'undefined') { | |
alarm.registeredAlarms.snooze = alarm.snoozeAlarmId; | |
delete alarm.snoozeAlarmId; | |
} | |
// Map '1111100' string bitmap to a repeat object with day properties. | |
if (typeof alarm.repeat === 'string') { | |
var days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', | |
'saturday', 'sunday']; | |
var newRepeat = {}; | |
for (var i = 0; i < alarm.repeat.length && i < days.length; i++) { | |
if (alarm.repeat[i] === '1') { | |
newRepeat[days[i]] = true; | |
} | |
} | |
alarm.repeat = newRepeat; | |
} else { | |
alarm.repeat = alarm.repeat || {}; | |
} | |
// Pre-April-2014 code may have stored 'vibrate' and 'sound' as | |
// the string "0", and hour/minute as strings. | |
alarm.sound = (alarm.sound !== '0' ? alarm.sound : null); | |
alarm.vibrate = (alarm.vibrate && alarm.vibrate !== '0') || false; | |
alarm.hour = parseInt(alarm.hour, 10); | |
alarm.minute = parseInt(alarm.minute, 10); | |
return alarm; | |
}, | |
/** | |
* Execute a database store request with the given method and | |
* arguments, returning a Promise that will be fulfilled with the | |
* Store's result. | |
*/ | |
withStoreRequest: function(method /*, args... */) { | |
var args = Array.slice(arguments, 1); | |
var readmode = (/get/.test(method) ? 'readonly' : 'readwrite'); | |
return this.withDatabase.then((database) => { | |
var store = database | |
.transaction(this.storeName, readmode) | |
.objectStore(this.storeName); | |
if (method === 'getAll') { | |
return objectStoreGetAll(store); | |
} else { | |
return new Promise((resolve, reject) => { | |
var request = store[method].apply(store, args); | |
request.onsuccess = (() => resolve(request.result)); | |
request.onerror = (() => reject(request.errorCode)); | |
}); | |
} | |
}); | |
}, | |
put: function(alarm) { | |
var data = alarm.toJSON(); | |
if (!data.id) { | |
delete data.id; // IndexedDB requires _no_ ID key, not null/undefined. | |
} | |
return this.withStoreRequest('put', data).then((id) => { | |
alarm.id = id; | |
}); | |
}, | |
getAll: function() { | |
var Alarm = require('alarm'); // Circular dependency. | |
return this.withStoreRequest('getAll').then((alarms) => { | |
return alarms.map((data) => new Alarm(data)); | |
}); | |
}, | |
get: function(id) { | |
var Alarm = require('alarm'); // Circular dependency. | |
return this.withStoreRequest('get', id).then((data) => { | |
return new Alarm(data); | |
}); | |
}, | |
delete: function(id) { | |
return this.withStoreRequest('delete', id); | |
} | |
}; | |
/** | |
* Return all records from an ObjectStore. This function is | |
* non-standard, but is such a common pattern that it has actually | |
* been included in certain implementations of IndexedDB. It is | |
* extracted here for clarity. | |
*/ | |
function objectStoreGetAll(objectStore) { | |
return new Promise((resolve, reject) => { | |
var items = []; | |
var cursor = objectStore.openCursor(); | |
cursor.onerror = reject; | |
cursor.onsuccess = function(event) { | |
var cursor = event.target.result; | |
if (cursor) { | |
items.push(cursor.value); | |
cursor.continue(); | |
} | |
else { | |
resolve(items); | |
} | |
}; | |
}); | |
} | |
// For Clock, we only use one database and store, both named 'alarms'. | |
// Right now, we're on version 7. | |
return new AlarmDatabase('alarms', 'alarms', 7); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment