mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-26 01:53:13 +08:00
refactor(database): Open only one sqlite connection, run requests from Browser process
Summary: This diff is the beginning of a larger refactor to move lots of stuff to the Browser process! Test Plan: No tests Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1156
This commit is contained in:
parent
e4889b390f
commit
a54f4ed3ba
2 changed files with 92 additions and 35 deletions
|
@ -70,6 +70,7 @@ class AtomApplication
|
|||
@pidsToOpenWindows = {}
|
||||
@mainWindow = null
|
||||
@windows = []
|
||||
@databases = {}
|
||||
|
||||
@autoUpdateManager = new AutoUpdateManager(@version)
|
||||
@applicationMenu = new ApplicationMenu(@version)
|
||||
|
@ -94,6 +95,64 @@ class AtomApplication
|
|||
for urlToOpen in (urlsToOpen || [])
|
||||
@openUrl({urlToOpen})
|
||||
|
||||
prepareDatabaseInterface: ->
|
||||
return @dblitePromise if @dblitePromise
|
||||
|
||||
# configure a listener that watches for incoming queries over IPC,
|
||||
# executes them, and returns the responses to the remote renderer processes
|
||||
ipc.on 'database-query', (event, {databasePath, queryKey, query, values}) =>
|
||||
db = @databases[databasePath]
|
||||
done = (err, result) ->
|
||||
unless err
|
||||
runtime = db.lastQueryTime()
|
||||
if runtime > 250
|
||||
console.log("Query #{queryKey}: #{query} took #{runtime}msec")
|
||||
event.sender.send('database-result', {queryKey, err, result})
|
||||
|
||||
return done(new Error("Database not prepared.")) unless db
|
||||
if query[0..5] is 'SELECT'
|
||||
db.query(query, values, null, done)
|
||||
else
|
||||
db.query(query, values, done)
|
||||
|
||||
# return a promise that resolves after we've configured dblite for our platform
|
||||
return @dblitePromise = new Promise (resolve, reject) =>
|
||||
dblite = require('../../vendor/dblite-custom').withSQLite('3.8.6+')
|
||||
vendor = @resourcePath + "/vendor"
|
||||
|
||||
if process.platform is 'win32'
|
||||
dblite.bin = "#{vendor}/sqlite3-win32.exe"
|
||||
resolve(dblite)
|
||||
else if process.platform is 'linux'
|
||||
exec "uname -a", (err, stdout, stderr) ->
|
||||
arch = if stdout.toString().indexOf('x86_64') is -1 then "32" else "64"
|
||||
dblite.bin = "#{vendor}/sqlite3-linux-#{arch}"
|
||||
resolve(dblite)
|
||||
else if process.platform is 'darwin'
|
||||
dblite.bin = "#{vendor}/sqlite3-darwin"
|
||||
resolve(dblite)
|
||||
|
||||
prepareDatabase: (databasePath, callback) ->
|
||||
@prepareDatabaseInterface().then (dblite) =>
|
||||
# Avoid opening a new connection to an existing database
|
||||
return callback() if @databases[databasePath]
|
||||
|
||||
# Create a new database for the requested path
|
||||
db = dblite(databasePath)
|
||||
|
||||
# By default, dblite stops all query execution when a query returns an error.
|
||||
# We want to propogate those errors out, but still allow queries to be made.
|
||||
db.ignoreErrors = true
|
||||
@databases[databasePath] = db
|
||||
|
||||
# Tell the person who requested the database that they can begin making queries
|
||||
callback()
|
||||
|
||||
teardownDatabase: (databasePath, callback) ->
|
||||
@databases[databasePath]?.close()
|
||||
delete @databases[databasePath]
|
||||
fs.unlink(databasePath, callback)
|
||||
|
||||
# Public: Removes the {AtomWindow} from the global window list.
|
||||
removeWindow: (window) ->
|
||||
@windows.splice @windows.indexOf(window), 1
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
Reflux = require 'reflux'
|
||||
async = require 'async'
|
||||
remote = require 'remote'
|
||||
_ = require 'underscore-plus'
|
||||
Actions = require '../actions'
|
||||
Model = require '../models/model'
|
||||
|
@ -10,10 +11,35 @@ ModelQuery = require '../models/query'
|
|||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
exec = require('child_process').exec
|
||||
ipc = require 'ipc'
|
||||
|
||||
silent = atom.getLoadSettings().isSpec
|
||||
verbose = false
|
||||
|
||||
# DatabaseConnection is a small shim for making database queries. Queries
|
||||
# are actually executed in the Browser process and eventually, we'll move
|
||||
# more and more of this class there.
|
||||
class DatabaseProxy
|
||||
constructor: (@databasePath) ->
|
||||
@windowId = remote.getCurrentWindow().id
|
||||
@queryCallbacks = {}
|
||||
@queryId = 0
|
||||
|
||||
ipc.on 'database-result', ({queryKey, err, result}) =>
|
||||
@queryCallbacks[queryKey](err, result) if @queryCallbacks[queryKey]
|
||||
delete @queryCallbacks[queryKey]
|
||||
|
||||
@
|
||||
|
||||
query: (query, values, callback) ->
|
||||
@queryId += 1
|
||||
queryKey = "#{@windowId}-#{@queryId}"
|
||||
@queryCallbacks[queryKey] = callback if callback
|
||||
ipc.send('database-query', {@databasePath, queryKey, query, values})
|
||||
|
||||
# DatabasePromiseTransaction converts the callback syntax of the Database
|
||||
# into a promise syntax with nice features like serial execution of many
|
||||
# queries in the same promise.
|
||||
class DatabasePromiseTransaction
|
||||
constructor: (@_db, @_resolve, @_reject) ->
|
||||
@_running = 0
|
||||
|
@ -22,17 +48,9 @@ class DatabasePromiseTransaction
|
|||
# Wrap any user-provided success callback in one that checks query time
|
||||
callback = (err, result) =>
|
||||
if err
|
||||
if err.message.indexOf('database is locked') != -1
|
||||
alert('Database lock error. You need to restart Edgehill (this is a known issue.)')
|
||||
|
||||
console.log("Query #{query}, #{JSON.stringify(values)} failed #{err.message}")
|
||||
queryFailure(err) if queryFailure
|
||||
@_reject(err)
|
||||
else
|
||||
runtime = @_db.lastQueryTime()
|
||||
if (runtime > 250 or verbose) and not silent
|
||||
console.log("Query: #{query} took #{runtime}msec")
|
||||
querySuccess(result) if querySuccess
|
||||
|
||||
# The user can attach things to the finish promise to run code after
|
||||
# the completion of all pending queries in the transaction. We fire
|
||||
|
@ -43,17 +61,13 @@ class DatabasePromiseTransaction
|
|||
@_resolve(result)
|
||||
|
||||
@_running += 1
|
||||
if query[0..5] == 'SELECT'
|
||||
@_db.query(query, values || [], null, callback)
|
||||
else
|
||||
@_db.query(query, values || [], callback)
|
||||
@_db.query(query, values || [], callback)
|
||||
|
||||
executeInSeries: (queries) ->
|
||||
async.eachSeries queries
|
||||
, (query, callback) =>
|
||||
@execute(query, [], -> callback())
|
||||
, (err) =>
|
||||
console.log(err) if err
|
||||
@_resolve()
|
||||
|
||||
|
||||
|
@ -83,26 +97,10 @@ DatabaseStore = Reflux.createStore
|
|||
for key, klass of classMap
|
||||
callback(klass) if klass.attributes
|
||||
|
||||
prepareSqlite: (callback) ->
|
||||
dblite = require('../../../vendor/dblite-custom').withSQLite('3.8.6+')
|
||||
vendor = atom.getLoadSettings().resourcePath + "/vendor"
|
||||
|
||||
if process.platform is 'win32'
|
||||
dblite.bin = "#{vendor}/sqlite3-win32.exe"
|
||||
callback(dblite)
|
||||
else if process.platform is 'linux'
|
||||
exec "uname -a", (err, stdout, stderr) ->
|
||||
arch = if stdout.toString().indexOf('x86_64') is -1 then "32" else "64"
|
||||
dblite.bin = "#{vendor}/sqlite3-linux-#{arch}"
|
||||
callback(dblite)
|
||||
else if process.platform is 'darwin'
|
||||
dblite.bin = "#{vendor}/sqlite3-darwin"
|
||||
callback(dblite)
|
||||
|
||||
openDatabase: (options = {createTables: false}) ->
|
||||
@prepareSqlite (dblite) =>
|
||||
# Open the database
|
||||
database = dblite(@_dbPath)
|
||||
app = remote.getGlobal('atomApplication')
|
||||
app.prepareDatabase @_dbPath, =>
|
||||
database = new DatabaseProxy(@_dbPath)
|
||||
|
||||
if options.createTables
|
||||
# Initialize the database and setup our schema. Note that we try to do this every
|
||||
|
@ -124,9 +122,9 @@ DatabaseStore = Reflux.createStore
|
|||
@_db = database
|
||||
|
||||
teardownDatabase: (callback) ->
|
||||
@_db?.close()
|
||||
@_db = null
|
||||
fs.unlink @_dbPath, (err) =>
|
||||
app = remote.getGlobal('atomApplication')
|
||||
app.teardownDatabase @_dbPath, =>
|
||||
@_db = null
|
||||
@trigger({})
|
||||
callback()
|
||||
|
||||
|
|
Loading…
Reference in a new issue