Home | History | Annotate | Download | only in endure
      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