Skip to content
This repository has been archived by the owner on Sep 22, 2021. It is now read-only.

Commit

Permalink
Updated database access to improve performance and prevent memory lea…
Browse files Browse the repository at this point in the history
…ks #207 #201
  • Loading branch information
Thomas101 committed Jul 21, 2016
1 parent c2ac426 commit 71969f9
Show file tree
Hide file tree
Showing 20 changed files with 432 additions and 140 deletions.
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wmail",
"version": "1.3.2",
"version": "1.3.3",
"prerelease": true,
"description": "The missing desktop client for Gmail and Google Inbox",
"scripts": {
Expand All @@ -22,18 +22,18 @@
"main": "bin/app/index.js",
"dependencies": {
"babel": "6.5.2",
"babel-core": "6.10.4",
"babel-core": "6.11.4",
"babel-loader": "6.2.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.11.1",
"babel-preset-stage-0": "6.5.0",
"clean-webpack-plugin": "0.1.9",
"clean-webpack-plugin": "0.1.10",
"copy-webpack-plugin": "3.0.1",
"css-loader": "0.23.1",
"electron-packager": "7.3.0",
"electron-prebuilt": "1.2.7",
"electron-prebuilt": "1.2.8",
"electron-rebuild": "1.1.5",
"electron-winstaller": "2.3.1",
"electron-winstaller": "2.3.3",
"extract-text-webpack-plugin": "1.0.1",
"file-loader": "0.9.0",
"jsx-loader": "0.13.2",
Expand Down
3 changes: 3 additions & 0 deletions src/app/src/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
const WindowManager = require('./windows/WindowManager')
const constants = require('../shared/constants')
const spellcheck = require('./spellcheck')
const storage = require('./storage')

Object.keys(storage).forEach((k) => storage[k].checkAwake())

/* ****************************************************************************/
// Global objects
Expand Down
186 changes: 105 additions & 81 deletions src/app/src/app/storage/StorageBucket.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const {ipcMain} = require('electron')
const AppDirectory = require('appdirectory')
const pkg = require('../../package.json')
const mkdirp = require('mkdirp')
const path = require('path')
const Minivents = require('minivents')
const fs = require('fs-extra')
const { DB_BACKUP_INTERVAL_MS, DB_WRITE_DELAY_MS } = require('../../shared/constants')
const { DB_WRITE_DELAY_MS } = require('../../shared/constants')

// Setup
const appDirectory = new AppDirectory(pkg.name)
Expand All @@ -19,64 +20,23 @@ class StorageBucket {

constructor (bucketName) {
this.__path__ = path.join(dbPath, bucketName + '_db.json')
this.__backupPath__ = this.__path__ + 'b'
this.__writer__ = null
this.__writeHold__ = null
this.__writeLock__ = false
this.__data__ = undefined
this.__ipcReplyChannel__ = `storageBucket:${bucketName}:reply`

// Setup backup infrastructure
this._restoreBackup()
this.autoBackup = setInterval(() => {
this._performBackup()
}, DB_BACKUP_INTERVAL_MS + (Math.floor(Math.random() * 10000)))
this._loadFromDiskSync()

this._loadFromDisk()
ipcMain.on(`storageBucket:${bucketName}:setItem`, this._handleIPCSetItem.bind(this))
ipcMain.on(`storageBucket:${bucketName}:removeItem`, this._handleIPCRemoveItem.bind(this))
ipcMain.on(`storageBucket:${bucketName}:getItem`, this._handleIPCGetItem.bind(this))
ipcMain.on(`storageBucket:${bucketName}:allKeys`, this._handleIPCAllKeys.bind(this))
ipcMain.on(`storageBucket:${bucketName}:allItems`, this._handleIPCAllItems.bind(this))

Minivents(this)
}

/* ****************************************************************************/
// Backup
/* ****************************************************************************/

/**
* Attempts to restore the backup if required
* @return true if a backup was performed successfully
*/
_restoreBackup () {
let restoreBackup = false
let didPerform = false
try {
const data = fs.readFileSync(this.__path__, 'utf-8')
if (!data.length) {
restoreBackup = true
}
} catch (ex) {
restoreBackup = true
}

if (restoreBackup) {
try {
fs.copySync(this.__backupPath__, this.__path__)
didPerform = true
} catch (ex) { }
}

return didPerform
}

/**
* Backs up the data
*/
_performBackup () {
let data = ''
try {
data = fs.readFileSync(this.__path__, 'utf8')
} catch (ex) { }

if (data.length) {
fs.outputFileSync(this.__backupPath__, data)
}
}
checkAwake () { return true }

/* ****************************************************************************/
// Persistence
Expand All @@ -85,7 +45,7 @@ class StorageBucket {
/**
* Loads the database from disk
*/
_loadFromDisk () {
_loadFromDiskSync () {
let data = '{}'
try {
data = fs.readFileSync(this.__path__, 'utf8')
Expand All @@ -102,9 +62,18 @@ class StorageBucket {
* Writes the current data to disk
*/
_writeToDisk () {
clearTimeout(this.__writer__)
this.__writer__ = setTimeout(() => {
fs.writeFileSync(this.__path__, JSON.stringify(this.__data__), 'utf8')
clearTimeout(this.__writeHold__)
this.__writeHold__ = setTimeout(() => {
if (this.__writeLock__) {
// Requeue in DB_WRITE_DELAY_MS
this._writeToDisk()
return
} else {
this.__writeLock__ = true
fs.writeFile(this.__path__, JSON.stringify(this.__data__), 'utf8', () => {
this.__writeLock__ = false
})
}
}, DB_WRITE_DELAY_MS)
}

Expand All @@ -115,21 +84,21 @@ class StorageBucket {
/**
* @param k: the key of the item
* @param d=undefined: the default value if not exists
* @return the json item or d
* @return the string item or d
*/
getItem (k, d) {
const json = this.__data__[k]
return json ? JSON.parse(json) : d
return json || d
}

/**
* @param k: the key of the item
* @param d=undefined: the default value if not exists
* @return the string item or d
*/
getString (k, d) {
const json = this.__data__[k]
return json || d
getJSONItem (k, d) {
const item = this.getItem(k)
return item ? JSON.parse(item) : d
}

/**
Expand All @@ -150,57 +119,112 @@ class StorageBucket {
}

/**
* @return all the items in an obj
* @return all the items in an obj json parsed
*/
allStrings () {
allJSONItems () {
return this.allKeys().reduce((acc, key) => {
acc[key] = this.getString(key)
acc[key] = this.getJSONItem(key)
return acc
}, {})
}

/* ****************************************************************************/
// Setters
// Modifiers
/* ****************************************************************************/

/**
* @param k: the key to set
* @param v: the value to set
* @return v
*/
setItem (k, v) {
this.__data__[k] = JSON.stringify(v)
_setItem (k, v) {
this.__data__[k] = '' + v
this._writeToDisk()
this.emit('changed', { type: 'setItem', key: k })
this.emit('changed:' + k, { })
return v
}

/**
* @param k: the key to set
* @param s: the value to set
* @return s
* @param k: the key to remove
*/
setString (k, s) {
this.__data__[k] = s
_removeItem (k) {
delete this.__data__[k]
this._writeToDisk()
this.emit('changed', { type: 'setString', key: k })
this.emit('changed', { type: 'removeItem', key: k })
this.emit('changed:' + k, { })
return s
}

/* ****************************************************************************/
// Removers
// IPC Access
/* ****************************************************************************/

/**
* @param k: the key to remove
* Responds to an ipc message
* @param evt: the original event that fired
* @param response: teh response to send
* @param sendSync: set to true to respond synchronously
*/
removeItem (k) {
delete this.__data__[k]
this._writeToDisk()
this.emit('changed', { type: 'removeItem', key: k })
this.emit('changed:' + k, { })
_sendIPCResponse (evt, response, sendSync = false) {
if (sendSync) {
evt.returnValue = response
} else {
evt.sender.send(this.__ipcReplyChannel__, response)
}
}

/**
* Sets an item over IPC
* @param evt: the fired event
* @param body: request body
*/
_handleIPCSetItem (evt, body) {
this._setItem(body.key, body.value)
this._sendIPCResponse(evt, { id: body.id, response: null }, body.sync)
}

/**
* Removes an item over IPC
* @param evt: the fired event
* @param body: request body
*/
_handleIPCRemoveItem (evt, body) {
this._removeItem(body.key)
this._sendIPCResponse(evt, { id: body.id, response: null }, body.sync)
}

/**
* Gets an item over IPC
* @param evt: the fired event
* @param body: request body
*/
_handleIPCGetItem (evt, body) {
this._sendIPCResponse(evt, {
id: body.id,
response: this.getItem(body.key)
}, body.sync)
}

/**
* Gets the keys over IPC
* @param body: request body
*/
_handleIPCAllKeys (evt, body) {
this._sendIPCResponse(evt, {
id: body.id,
response: this.allKeys()
}, body.sync)
}

/**
* Gets all the items over IPC
* @param body: request body
*/
_handleIPCAllItems (evt, body) {
this._sendIPCResponse(evt, {
id: body.id,
response: this.allItems()
}, body.sync)
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/app/src/app/storage/StorageBucketAppMutable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const StorageBucket = require('./StorageBucket')

class StorageBucketAppMutable extends StorageBucket {
/**
* @param k: the key to set
* @param v: the value to set
* @return v
*/
setItem (k, v) { return this._setItem(k, v) }

/**
* @param k: the key to set
* @param v: the value to set
* @return v
*/
setJSONItem (k, v) { return this._setItem(k, JSON.stringify(v)) }

/**
* @param k: the key to remove
*/
removeItem (k) { return this._removeItem(k) }
}

module.exports = StorageBucketAppMutable
2 changes: 1 addition & 1 deletion src/app/src/app/storage/appStorage.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
const StorageBucket = require('./StorageBucket')
const StorageBucket = require('./StorageBucketAppMutable')
module.exports = new StorageBucket('app')
6 changes: 6 additions & 0 deletions src/app/src/app/storage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
appStorage: require('./appStorage'),
avatarStorage: require('./avatarStorage'),
mailboxStorage: require('./mailboxStorage'),
settingStorage: require('./settingStorage')
}
6 changes: 3 additions & 3 deletions src/app/src/app/stores/mailboxStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class MailboxStore {
this.index = []
this.mailboxes = new Map()

const allRawItems = persistence.allItems()
const allRawItems = persistence.allJSONItems()
Object.keys(allRawItems).forEach((id) => {
if (id === MAILBOX_INDEX_KEY) {
this.index = allRawItems[id]
Expand All @@ -23,10 +23,10 @@ class MailboxStore {
// Listen for changes
persistence.on('changed', (evt) => {
if (evt.key === MAILBOX_INDEX_KEY) {
this.index = persistence.getItem(MAILBOX_INDEX_KEY)
this.index = persistence.getJSONItem(MAILBOX_INDEX_KEY)
} else {
if (evt.type === 'setItem') {
this.mailboxes.set(evt.key, new Mailbox(evt.key, persistence.getItem(evt.key)))
this.mailboxes.set(evt.key, new Mailbox(evt.key, persistence.getJSONItem(evt.key)))
}
if (evt.type === 'removeItem') {
this.mailboxes.delete(evt.key)
Expand Down
Loading

0 comments on commit 71969f9

Please sign in to comment.