Source: js/migrators/browser_migrator.js

'use strict';
/* global BookmarksDatabase */
/* global Places */
// https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabaseException
// IDBDatabaseException obselete

/** Support different browser versions of IndexedDB */
var idb = window.indexedDB || window.webkitIndexedDB ||
    window.mozIndexedDB || window.msIndexedDB;

/**
 * Provide access to bookmarks, topsites, history, search engines and settings
 * in IndexedDB.
 * @namespace BrowserDB
 */
var BrowserDB = {
  init: function browserDB_init(callback) {
    this.db.open(callback);
  },

  /**
   * Get all bookmarks.
   * @param {Function} callback Runs on success with an array of bookmarks
   */
  getBookmarks: function browserDB_getBookmarks(callback) {
    this.db.getAllBookmarks(callback);
  },

  /**
   * Reads visits from the database.
   */
  readVisits: function browserDB_readVisits() {
    return this.db.readVisits();
  },

  removeDB: function browserDB_removeDB() {
    this.db.removeDB();
  }
};

/**
 * @memberOf BrowserDB
 * @namespace BrowserDB.db
 */
BrowserDB.db = {
  _db: null,
  upgradeFrom: -1,

  /**
   * Open a IndexedDB database with name as 'browser' and version as 7.
   * @param {Function} callback The callback to be run on success
   */
  open: function db_open(callback) {
    const DB_VERSION = 7;
    const DB_NAME = 'browser';
    var request = idb.open(DB_NAME, DB_VERSION);

    request.onupgradeneeded = (function onUpgradeNeeded(e) {
      console.log('Browser db upgrade needed, migrating so doesn\'t matter.');
      this._db = e.target.result;
    }).bind(this);

    request.onsuccess = (function onSuccess(e) {
      this._db = e.target.result;
      callback();
    }).bind(this);

    request.onerror = (function onDatabaseError(e) {
      console.log('Error opening browser database');
    }).bind(this);
  },

  removeDB: function db_remove() {
    this._db.close();
    var request = window.indexedDB.deleteDatabase('browser');
    request.onsuccess = function onSuccess(e) {
      console.log('Browser Database migrated and deleted!');
    };
    request.onerror = function onSuccess(e) {
      console.log('Database not deleted!');
    };
  },
  
  getAllBookmarks: function db_getAllBookmarks(callback) {
    var bookmarks = [];
    var db = this._db;

    function makeBookmarkProcessor(bookmark) {
      return function(e) {
        var place = e.target.result;
        bookmark.iconUri = place.iconUri;
        bookmarks.push(bookmark);
      };
    }

    var transaction = db.transaction(['bookmarks', 'places']);
    var bookmarksStore = transaction.objectStore('bookmarks');
    var bookmarksIndex = bookmarksStore.index('timestamp');
    var placesStore = transaction.objectStore('places');
    bookmarksIndex.openCursor(null, 'prev').onsuccess =
        function onSuccess(e) {
          var cursor = e.target.result;
          if (cursor) {
            var bookmark = cursor.value;
            placesStore.get(bookmark.uri).onsuccess =
                makeBookmarkProcessor(bookmark);
            cursor.continue();
          }
        };
    transaction.oncomplete = function db_bookmarkTransactionComplete() {
      callback(bookmarks);
    };
  },

  readVisits: function db_readHistory() {

    var places = {};
    var db = this._db;

    var transaction = db.transaction(['visits']);
    var bookmarksStore = transaction.objectStore('visits');
    var bookmarksIndex = bookmarksStore.index('timestamp');

    function onCursor(e) {

      var cursor = e.target.result;
      if (cursor) {

        if (!(cursor.value.uri in places)) {
          places[cursor.value.uri] = {
            visits: []
          };
        }

        var place = places[cursor.value.uri];

        if (cursor.value.title) {
          place.title = cursor.value.title;
        }

        place.visits.unshift(cursor.value.timestamp);

        if (place.visits.length > 20) {
          place.visits.length = 20;
        }
        cursor.continue();
      }
    }

    return new Promise(function(resolve) {
      transaction.oncomplete = function() {
        resolve(places);
      };
      bookmarksIndex.openCursor(null, 'prev').onsuccess = onCursor;
    });
  }
};

var BrowserMigrator = function BrowserMigrator() {
};

BrowserMigrator.prototype = {
  /**
   * A list of pending bookmarks to migrate.
   */
  _pendingBookmarks: [],

  start: function() {

    BrowserDB.init(() => {
      BrowserDB.readVisits().then(visits => {
        this._saveVisits(visits).then(() => {
          BrowserDB.getBookmarks(bookmarks => {
            this._pendingBookmarks = bookmarks;
            this._iteratePendingBookmarks();
          });
        });
      });
    });
  },

  _saveVisits: function(visits) {

    var urls = Object.keys(visits);
    var places = new Places();

    return new Promise(function(resolve) {

      function saveVisit() {

        if (!urls.length) {
          resolve();
          return;
        }

        var url = urls.shift();
        places.setVisits(url, visits[url].visits).then(saveVisit);
      }

      places.start().then(saveVisit);
    });
  },

  _iteratePendingBookmarks: function() {
    // If there are no bookmarks left, we're done
    if (!this._pendingBookmarks.length) {
      BrowserDB.removeDB();
      window.close();
      return;
    }

    var nextBookmark = this._pendingBookmarks.shift();
    var descriptor = {
      id: nextBookmark.uri,
      url: nextBookmark.uri,
      name: nextBookmark.title,
      icon: nextBookmark.iconUri
    };

    BookmarksDatabase.add(descriptor).then(() => {
      this._iteratePendingBookmarks();
    });
  }
};