B2G/gaia/apps/communications/dialer/js/call_log_db.js
/**
   * Upgrade schema to version 4. Recreate the object store to host groups of
   * calls with a new schema changing the old string primary key for an array
   * which will contain the date of the first call of the group, the phone
   * number associated with the call, the type of the call (incoming, dialing)
   * and the status of the call (accepted or not). We also add a new
   * 'lastEntryDate' field containing the date of the last call of the group
   * and substitute the unused 'contact' field by a more detailed contact
   * information to avoid querying the contacts API database each time that
   * we need to render the call log.
   *
   * param db
   *        Database instance.
   * param transaction
   *        IDB transaction instance.
   */
Upgrade schema to version 4. Recreate the object store to host groups of
calls with a new schema changing the old string primary key for an array
which will contain the date of the first call of the group, the phone
number associated with the call, the type of the call (incoming, dialing)
and the status of the call (accepted or not). We also add a new
'lastEntryDate' field containing the date of the last call of the group
and substitute the unused 'contact' field by a more detailed contact
information to avoid querying the contacts API database each time that
we need to render the call log.

param db
Database instance.
param transaction
IDB transaction instance.
Upgrade schema to version 4 . Recreate the object store to host groups of calls with a new schema changing the old string primary key for an array which will contain the date of the first call of the group the phone number associated with the call the type of the call ( incoming dialing ) and the status of the call ( accepted or not ) . We also add a new ' lastEntryDate ' field containing the date of the last call of the group and substitute the unused ' contact ' field by a more detailed contact information to avoid querying the contacts API database each time that we need to render the call log . param db Database instance . param transaction IDB transaction instance .
function upgradeSchemaVersion4(db, transaction, next) {
    var self = this;
    var waitForAsyncCall = 0;
    var cursorDone = false;
    var groups = {};
    var recentsCount = 0;
    var groupCount = 0;
    function onGroupsDone() {
      if (!cursorDone || waitForAsyncCall) {
        return;
      }
      if (groupCount == 0) {
        next();
        return;
      }
      self._newTxn('readwrite', [self._dbGroupsStore],
                   function(error, txn, store) {
        if (error) {
          console.log('Error upgrading the database ' + error);
          return;
        }
        for (var group in groups) {
          store.put(groups[group]).onsuccess = function onsuccess() {
            groupCount--;
            if (groupCount == 0) {
              next();
            }
          };
          delete groups[group];
        }
      });
    }
    function populateGroups() {
      var percent = parseInt(recentsCount * 0.15);
      var countToProgressEvent = percent;
      var percent2 = parseInt(recentsCount * 0.25);
      var countToProgressEvent2 = percent2;
      var progress = 0;
      recentsStore.openCursor().onsuccess = function onsuccess(event) {
        if (countToProgressEvent === 0) {
          countToProgressEvent = percent;
          progress += 10;
          self._notifyObservers('upgradeprogress', progress);
        } else {
          countToProgressEvent--;
        }
        var cursor = event.target.result;
        if (!cursor) {
          cursorDone = true;
          onGroupsDone();
          return;
        }
        var record = cursor.value;
        var type = '';
        var status;
        switch (record.type) {
          case 'incoming-connected':
            type = 'incoming';
            status = 'connected';
            break;
          case 'incoming-refused':
            type = 'incoming';
            break;
          default:
            if (record.type && record.type.indexOf('dialing') != -1) {
              type = 'dialing';
            }
            break;
        }
        var id = self._getGroupId({
          date: record.date,
          number: record.number,
          type: type,
          status: status
        });
        var date = new Date(record.date);
        var key = date.getDay() + date.getMonth() + date.getFullYear() +
                  record.number + type;
        if (status) {
          key += status;
        }
        waitForAsyncCall++;
        Contacts.findByNumber(record.number,
                              function(contact, matchingTel) {
          if (countToProgressEvent2 === 0) {
            countToProgressEvent2 = percent2;
            progress += 10;
            self._notifyObservers('upgradeprogress', progress);
          } else {
            countToProgressEvent2--;
          }
          if (key in groups) {
            if (groups[key].lastEntryDate <= record.date) {
              groups[key].lastEntryDate = record.date;
            }
            groups[key].retryCount++;
          } else {
            var group = {
              id: id,
              number: record.number,
              lastEntryDate: record.date,
              retryCount: 1
            };
            if (contact && contact !== null) {
              group.contactId = contact.id;
              if (Array.isArray(contact.photo) && contact.photo[0]) {
                group.contactPhoto = contact.photo[0];
              }
              if (matchingTel) {
                var primaryInfo = Utils.getPhoneNumberPrimaryInfo(matchingTel,
                                                                  contact);
                if (Array.isArray(primaryInfo)) {
                  primaryInfo = primaryInfo[0];
                }
                group.contactPrimaryInfo = String(primaryInfo);
                if (Array.isArray(matchingTel.type) && matchingTel.type[0]) {
                  group.contactMatchingTelType = String(matchingTel.type[0]);
                }
                if (matchingTel.carrier) {
                  group.contactMatchingTelCarrier = String(matchingTel.carrier);
                }
              }
            }
            groups[key] = group;
            groupCount++;
          }
          waitForAsyncCall--;
          onGroupsDone();
        });
        cursor.continue();
      };
    }
    var groupsStore = transaction.objectStore(self._dbGroupsStore);
    db.deleteObjectStore(self._dbGroupsStore);
      The <Date> value from the 'id' field contains only the day of the call.
      'lastEntryDate' contains a full date.
      'retryCount' is incremented when we store a new call.
    var groupsStore = db.createObjectStore(self._dbGroupsStore,
                                           { keyPath: 'id' });
     We create indexes for 'contact-id', 'number' and 'lastEntryDate'
     as we will be searching by number, contact and date.
     Unfortunately, even if we have the number data in the group id field, we
     can't search for [*, "exact number match", *, *], so we need a new
     'number' index.
    groupsStore.createIndex('number', 'number');
    groupsStore.createIndex('contactId', 'contactId');
    groupsStore.createIndex('lastEntryDate', 'lastEntryDate');
     We get the number of calls stored in the database so we can send
     progress events with the appropriate percentage of upgrade process
     completed.
    var recentsStore = transaction.objectStore(self._dbRecentsStore);
    recentsStore.count().onsuccess = function(event) {
      recentsCount = event.target.result;
      populateGroups();
    };
  }
function upgradeSchemaVersion4 ( db , transaction , next ) { var self = this ; var waitForAsyncCall = 0 ; var cursorDone = false ; var groups = { } ; var recentsCount = 0 ; var groupCount = 0 ; function onGroupsDone ( ) { if ( ! cursorDone || waitForAsyncCall ) { return ; } if ( groupCount == 0 ) { next ( ) ; return ; } self . _newTxn ( 'readwrite' , [ self . _dbGroupsStore ] , function ( error , txn , store ) { if ( error ) { console . log ( 'Error upgrading the database ' + error ) ; return ; } for ( var group in groups ) { store . put ( groups [ group ] ) . onsuccess = function onsuccess ( ) { groupCount -- ; if ( groupCount == 0 ) { next ( ) ; } } ; delete groups [ group ] ; } } ) ; } function populateGroups ( ) { var percent = parseInt ( recentsCount * 0.15 ) ; var countToProgressEvent = percent ; var percent2 = parseInt ( recentsCount * 0.25 ) ; var countToProgressEvent2 = percent2 ; var progress = 0 ; recentsStore . openCursor ( ) . onsuccess = function onsuccess ( event ) { if ( countToProgressEvent === 0 ) { countToProgressEvent = percent ; progress += 10 ; self . _notifyObservers ( 'upgradeprogress' , progress ) ; } else { countToProgressEvent -- ; } var cursor = event . target . result ; if ( ! cursor ) { cursorDone = true ; onGroupsDone ( ) ; return ; } var record = cursor . value ; var type = '' ; var status ; switch ( record . type ) { case 'incoming-connected' : type = 'incoming' ; status = 'connected' ; break ; case 'incoming-refused' : type = 'incoming' ; break ; default : if ( record . type && record . type . indexOf ( 'dialing' ) != - 1 ) { type = 'dialing' ; } break ; } var id = self . _getGroupId ( { date : record . date , number : record . number , type : type , status : status } ) ; var date = new Date ( record . date ) ; var key = date . getDay ( ) + date . getMonth ( ) + date . getFullYear ( ) + record . number + type ; if ( status ) { key += status ; } waitForAsyncCall ++ ; Contacts . findByNumber ( record . number , function ( contact , matchingTel ) { if ( countToProgressEvent2 === 0 ) { countToProgressEvent2 = percent2 ; progress += 10 ; self . _notifyObservers ( 'upgradeprogress' , progress ) ; } else { countToProgressEvent2 -- ; } if ( key in groups ) { if ( groups [ key ] . lastEntryDate <= record . date ) { groups [ key ] . lastEntryDate = record . date ; } groups [ key ] . retryCount ++ ; } else { var group = { id : id , number : record . number , lastEntryDate : record . date , retryCount : 1 } ; if ( contact && contact !== null ) { group . contactId = contact . id ; if ( Array . isArray ( contact . photo ) && contact . photo [ 0 ] ) { group . contactPhoto = contact . photo [ 0 ] ; } if ( matchingTel ) { var primaryInfo = Utils . getPhoneNumberPrimaryInfo ( matchingTel , contact ) ; if ( Array . isArray ( primaryInfo ) ) { primaryInfo = primaryInfo [ 0 ] ; } group . contactPrimaryInfo = String ( primaryInfo ) ; if ( Array . isArray ( matchingTel . type ) && matchingTel . type [ 0 ] ) { group . contactMatchingTelType = String ( matchingTel . type [ 0 ] ) ; } if ( matchingTel . carrier ) { group . contactMatchingTelCarrier = String ( matchingTel . carrier ) ; } } } groups [ key ] = group ; groupCount ++ ; } waitForAsyncCall -- ; onGroupsDone ( ) ; } ) ; cursor . continue ( ) ; } ; } var groupsStore = transaction . objectStore ( self . _dbGroupsStore ) ; db . deleteObjectStore ( self . _dbGroupsStore ) ; var groupsStore = db . createObjectStore ( self . _dbGroupsStore , { keyPath : 'id' } ) ; groupsStore . createIndex ( 'number' , 'number' ) ; groupsStore . createIndex ( 'contactId' , 'contactId' ) ; groupsStore . createIndex ( 'lastEntryDate' , 'lastEntryDate' ) ; var recentsStore = transaction . objectStore ( self . _dbRecentsStore ) ; recentsStore . count ( ) . onsuccess = function ( event ) { recentsCount = event . target . result ; populateGroups ( ) ; } ; }
Upgrade schema to version 4.
Upgrade schema to version 4 .
// Once we built all the group of calls we can push them to the // groups object store and notify the UI about it. // Populate quick group view with already existing calls information. // We send 'upgradeprogress' events from two different places: // 1- For each 15% of the recentsStore cursor requests completed. //    We increment the progress by 10%, leaving the total amount //    in 60%. // 2- For each 25% of the Contacts API requests completed. We //    also increment the progress by 10%, leaving the total //    amount in 90%. // The final progress 100% event is avoided as we send a 'upgradedone' // event instead. // As soon as the cursor is done, we bail out. We still need to wait // for all the async calls to retrieve the contacts information to // finish before pushing the data to the groups object store. We will // notify the UI about that so it can request the data again. // Get group type and status. // Create the id and a temporary unique key for the group. // Get the contact information associated with the number. // Store the group or increment the 'retryCount' field if we already // created a group for this call. // Groups should have the date of the newest call. // First of all we delete the old groups object store. // We recreate the object store that can be used to quickly construct a // group view of the recent calls database. Each entry looks like this: // { id: [date<Date>, number<String>, type<String>, status<String>], //   lastEntryDate: <Date>, (index) //   retryCount: <Number>, //   number: <String>, (index) //   contactId: <String>, (index) //   contactPrimaryInfo: <String>, //   contactMatchingTelType: <String>, //   contactMatchingTelCarrier: <String>, //   contactPhoto: <Blob> } // //  The <Date> value from the 'id' field contains only the day of the call. // //  'lastEntryDate' contains a full date. // //  'retryCount' is incremented when we store a new call. // We create indexes for 'contact-id', 'number' and 'lastEntryDate' // as we will be searching by number, contact and date. // Unfortunately, even if we have the number data in the group id field, we // can't search for [*, "exact number match", *, *], so we need a new // 'number' index. // We get the number of calls stored in the database so we can send // progress events with the appropriate percentage of upgrade process // completed.