1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // This file simulates a typical background process of an offline-capable 6 // authoring application. When in an "online" state it receives chunks of 7 // data updates from a simulated server and stores them in a temporary IDB 8 // data store. On a different timer, the chunks are drained from the 9 // temporary store and combined into larger records in a permanent store. 10 // When in an "offline" state, nothing else happens. 11 12 function unexpectedErrorCallback(e) { 13 self.postMessage({type: 'ERROR', error: { 14 name: e.target.error.name, 15 message: e.target.error.message 16 }}); 17 } 18 19 function unexpectedAbortCallback(e) { 20 self.postMessage({type: 'ABORT', error: { 21 name: e.target.error.name, 22 message: e.target.error.message 23 }}); 24 } 25 26 function log(message) { 27 self.postMessage({type: 'LOG', message: message}); 28 } 29 30 function error(message) { 31 self.postMessage({type: 'ERROR', message: message}); 32 } 33 34 var DBNAME = 'endurance-db'; 35 var DBVERSION = 1; 36 37 var MAX_DOC_ID = 25; 38 var MAX_CHUNK_ID = 10; 39 var MAX_CHUNK_SIZE = 5 * 1024; 40 var SYNC_TIMEOUT = 100; 41 var COMBINE_TIMEOUT = 234; // relatively prime with SYNC_TIMEOUT 42 43 function randomString(len) 44 { 45 var s = ''; 46 while (len--) 47 s += Math.floor((Math.random() * 36)).toString(36); 48 return s; 49 } 50 51 var getNextChunk = ( 52 function () { 53 var nextDocID = 0; 54 var nextChunkID = 0; 55 56 return function () { 57 var doc_id = nextDocID; 58 var chunk_id = nextChunkID; 59 60 nextDocID += 1; 61 if (nextDocID >= MAX_DOC_ID) { 62 nextDocID = 0; 63 nextChunkID += 1; 64 if (nextChunkID >= MAX_CHUNK_ID) 65 nextChunkID = 0; 66 } 67 68 return { 69 docid: doc_id, 70 chunkid: chunk_id, 71 timestamp: new Date(), 72 data: randomString(MAX_CHUNK_SIZE) 73 }; 74 }; 75 }() 76 ); 77 78 79 self.onmessage = function (event) { 80 switch (event.data.type) { 81 case 'offline': 82 goOffline(); 83 break; 84 case 'online': 85 goOnline(); 86 break; 87 default: 88 throw new Error("Unexpected message: " + event.data.type); 89 } 90 }; 91 92 93 var offline = true; 94 var syncTimeoutId = 0; 95 var combineTimeoutId = 0; 96 97 function goOffline() { 98 if (offline) 99 return; 100 log('offline'); 101 offline = true; 102 clearTimeout(syncTimeoutId); 103 syncTimeoutId = 0; 104 clearTimeout(combineTimeoutId); 105 combineTimeoutId = 0; 106 } 107 108 function goOnline() { 109 if (!offline) 110 return; 111 offline = false; 112 log('online'); 113 syncTimeoutId = setTimeout(sync, SYNC_TIMEOUT); 114 combineTimeoutId = setTimeout(combine, COMBINE_TIMEOUT); 115 // NOTE: Not using setInterval as we need to be sure they complete. 116 } 117 118 var sync_count = 0; 119 function sync() { 120 if (offline) 121 return; 122 123 var sync_id = ++sync_count; 124 log('sync ' + sync_id + ' started'); 125 126 var chunk = getNextChunk(); 127 log('sync ' + sync_id + 128 ' adding chunk: ' + chunk.chunkid + 129 ' to doc: ' + chunk.docid); 130 131 var request = indexedDB.open(DBNAME); 132 request.onerror = unexpectedErrorCallback; 133 request.onsuccess = function () { 134 var db = request.result; 135 if (db.version !== DBVERSION) { 136 error('DB version incorrect'); 137 return; 138 } 139 140 var transaction = db.transaction('sync-chunks', 'readwrite'); 141 var store = transaction.objectStore('sync-chunks'); 142 request = store.put(chunk); 143 transaction.onabort = unexpectedAbortCallback; 144 transaction.oncomplete = function () { 145 log('sync ' + sync_id + ' finished'); 146 db.close(); 147 syncTimeoutId = setTimeout(sync, SYNC_TIMEOUT); 148 }; 149 }; 150 } 151 152 var combine_count = 0; 153 function combine() { 154 if (offline) 155 return; 156 157 var combine_id = ++combine_count; 158 log('combine ' + combine_id + ' started'); 159 160 var combine_chunk_count = 0; 161 162 var request = indexedDB.open(DBNAME); 163 request.onerror = unexpectedErrorCallback; 164 request.onsuccess = function () { 165 var db = request.result; 166 if (db.version !== DBVERSION) { 167 error('DB version incorrect'); 168 return; 169 } 170 171 var transaction = db.transaction(['sync-chunks', 'docs'], 'readwrite'); 172 var syncStore = transaction.objectStore('sync-chunks'); 173 var docStore = transaction.objectStore('docs'); 174 175 var cursorRequest = syncStore.openCursor(); 176 cursorRequest.onerror = unexpectedErrorCallback; 177 cursorRequest.onsuccess = function () { 178 var cursor = cursorRequest.result; 179 if (cursor) { 180 combine_chunk_count += 1; 181 log('combine ' + combine_id + 182 ' processing chunk # ' + combine_chunk_count); 183 184 var key = cursor.key; 185 var chunk = cursor.value; 186 var docRequest = docStore.get(chunk.docid); 187 docRequest.onerror = unexpectedErrorCallback; 188 docRequest.onsuccess = function () { 189 var doc = docRequest.result; 190 if (!doc) { 191 doc = { 192 docid: chunk.docid, 193 chunks: [] 194 }; 195 log('combine # ' + combine_id + 196 ' created doc: ' + doc.docid); 197 } 198 199 log('combine # ' + combine_id + 200 ' updating doc: ' + doc.docid + 201 ' chunk: ' + chunk.chunkid); 202 203 doc.chunks[chunk.chunkid] = chunk; 204 doc.timestamp = new Date(); 205 request = docStore.put(doc); 206 request.onerror = unexpectedErrorCallback; 207 cursor.delete(key); 208 cursor.continue(); 209 }; 210 } else { 211 // let transaction complete 212 log('combine ' + combine_id + 213 ' done, processed ' + combine_chunk_count + ' chunks'); 214 } 215 }; 216 transaction.onabort = unexpectedAbortCallback; 217 transaction.oncomplete = function () { 218 log('combine ' + combine_id + 219 ' finished, processed ' + combine_chunk_count + ' chunks'); 220 db.close(); 221 combineTimeoutId = setTimeout(combine, COMBINE_TIMEOUT); 222 }; 223 }; 224 } 225