diff --git a/background/background.js b/background/background.js index b6c090dd..4d246045 100644 --- a/background/background.js +++ b/background/background.js @@ -17,6 +17,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, { getStyle: styleManager.get, getStylesByUrl: styleManager.getStylesByUrl, importStyle: styleManager.importStyle, + importManyStyles: styleManager.importMany, installStyle: styleManager.installStyle, styleExists: styleManager.styleExists, toggleStyle: styleManager.toggleStyle, diff --git a/background/db.js b/background/db.js index 57057553..f0a0b0ae 100644 --- a/background/db.js +++ b/background/db.js @@ -74,39 +74,48 @@ const db = (() => { } function dbExecIndexedDB(method, ...args) { - return new Promise((resolve, reject) => { - Object.assign(indexedDB.open('stylish', 2), { - onsuccess(event) { - const database = event.target.result; - if (!method) { - resolve(database); - } else { - const transaction = database.transaction(['styles'], 'readwrite'); - const store = transaction.objectStore('styles'); - try { - Object.assign(store[method](...args), { - onsuccess: event => resolve(event, store, transaction, database), - onerror: reject, - }); - } catch (err) { - reject(err); - } - } - }, - onerror(event) { - console.warn(event.target.error || event.target.errorCode); - reject(event); - }, - onupgradeneeded(event) { + return open().then(database => { + if (!method) { + return database; + } + if (method === 'putMany') { + return putMany(database, ...args); + } + const mode = method.startsWith('get') ? 'readonly' : 'readwrite'; + const transaction = database.transaction(['styles'], mode); + const store = transaction.objectStore('styles'); + return storeRequest(store, method, ...args); + }); + + function storeRequest(store, method, ...args) { + return new Promise((resolve, reject) => { + const request = store[method](...args); + request.onsuccess = resolve; + request.onerror = reject; + }); + } + + function open() { + return new Promise((resolve, reject) => { + const request = indexedDB.open('stylish', 2); + request.onsuccess = () => resolve(request.result); + request.onerror = reject; + request.onupgradeneeded = event => { if (event.oldVersion === 0) { event.target.result.createObjectStore('styles', { keyPath: 'id', autoIncrement: true, }); } - }, + }; }); - }); + } + + function putMany(database, items) { + const transaction = database.transaction(['styles'], 'readwrite'); + const store = transaction.objectStore('styles'); + return Promise.all(items.map(item => storeRequest(store, 'put', item))); + } } function dbExecChromeStorage(method, data) { @@ -118,17 +127,33 @@ const db = (() => { case 'put': if (!data.id) { - return getAllStyles().then(styles => { - data.id = 1; - for (const style of styles) { - data.id = Math.max(data.id, style.id + 1); - } + return getMaxId().then(id => { + data.id = id + 1; return dbExecChromeStorage('put', data); }); } return chromeLocal.setValue(STYLE_KEY_PREFIX + data.id, data) .then(() => (chrome.runtime.lastError ? Promise.reject() : data.id)); + case 'putMany': { + const newItems = data.filter(i => !i.id); + const doPut = () => + chromeLocal.set(data.reduce((o, item) => { + o[STYLE_KEY_PREFIX + item.id] = item; + return o; + }, {})) + .then(() => data.map(d => ({target: {result: d.id}}))); + if (newItems.length) { + return getMaxId().then(id => { + for (const item of newItems) { + item.id = ++id; + } + return doPut(); + }); + } + return doPut(); + } + case 'delete': return chromeLocal.remove(STYLE_KEY_PREFIX + data); @@ -150,5 +175,17 @@ const db = (() => { return styles; }); } + + function getMaxId() { + return getAllStyles().then(styles => { + let result = 0; + for (const style of styles) { + if (style.id > result) { + result = style.id; + } + } + return result; + }); + } } })(); diff --git a/background/style-manager.js b/background/style-manager.js index d4595f88..a8946485 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -55,6 +55,7 @@ const styleManager = (() => { editSave, findStyle, importStyle, + importMany, toggleStyle, setStyleExclusions, getAllStyles, // used by import-export @@ -138,6 +139,18 @@ const styleManager = (() => { .then(newData => handleSave(newData, 'import')); } + function importMany(items) { + return db.exec('putMany', items) + .then(events => { + for (let i = 0; i < items.length; i++) { + if (!items[i].id) { + items[i].id = events[i].target.result; + } + } + return Promise.all(items.map(i => handleSave(i, 'import'))); + }); + } + function installStyle(data, reason = null) { const style = styles.get(data.id); if (!style) { diff --git a/manage/import-export.js b/manage/import-export.js index 08e5210b..2605e0a5 100644 --- a/manage/import-export.js +++ b/manage/import-export.js @@ -126,13 +126,20 @@ function importFromString(jsonString) { oldStyles.map(style => [style.id, style])); oldStylesByName = json.length && new Map( oldStyles.map(style => [style.name.trim(), style])); - return Promise.all(json.map((item, i) => { + + const items = []; + json.forEach((item, i) => { const info = analyze(item, i); if (info) { - return API.importStyle(item) - .then(style => updateStats(style, info)); + items.push({info, item}); } - })); + }); + return API.importManyStyles(items.map(i => i.item)) + .then(styles => { + for (let i = 0; i < styles.length; i++) { + updateStats(styles[i], items[i].info); + } + }); }) .then(done);