Skip to content

Commit

Permalink
added backup/restore by sql
Browse files Browse the repository at this point in the history
  • Loading branch information
budiadiono committed Oct 10, 2018
1 parent 0900228 commit d918de6
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
96 changes: 96 additions & 0 deletions src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,102 @@ export class Db<T> {
}
}

async backupToSql(
callback: (
state: 'generating' | 'sending-email' | 'sent' | 'cancel'
) => void
) {
let sql = ''

callback('generating')
for (const key of Object.keys(this.tables)) {
const table: Table<any, any> = this.tables[key]
sql += (await table.buildBackupSql()) + '\r\n'
}

const now = new Date()
const timestamp = Utils.timeStamp(now)
const { name } = Constants.manifest

const targetUri = `${FileSystem.documentDirectory}backup/${name
.replace(/\s/g, '-')
.toLowerCase()}-${timestamp}.db.sql`

// avoid (No such file or directory) while writing file
await FileSystem.copyAsync({
from: this.getFileUri(),
to: targetUri
})
await FileSystem.writeAsStringAsync(targetUri, sql)

callback('sending-email')

const res = await MailComposer.composeAsync({
attachments: [targetUri],
body: `${name} backup data`,
subject: `${name} backup data ${now.toISOString()}`
})

if (res.status === 'sent') {
callback('sent')
} else {
callback('cancel')
}
}

async restoreFromSql(recreateTables: boolean = true): Promise<boolean> {
try {
const res: any = await DocumentPicker.getDocumentAsync({
type: '*/*'
})

if (res.type === 'success') {
return new Promise<boolean>(resolve => {
FileSystem.readAsStringAsync(res.uri).then(async sql => {
if (recreateTables) {
debug('dropping tables...')
for (const key of Object.keys(this.tables)) {
const table: Table<any, any> = this.tables[key]
await table.dropTable()
}

debug('recreate tables...')
for (const key of Object.keys(this.tables)) {
const table: Table<any, any> = this.tables[key]
await table.createTable()
}
}

debug('starting restore database...')
const sqls = sql.split('INSERT INTO')
this.sqliteDb.transaction(
t => {
for (let s of sqls) {
s = s.trim()
if (s && s.length) {
// @ts-ignore
const tableName = s.match(/"(.*?)"/)[1]
debug('restoring ', tableName)
t.executeSql(`INSERT INTO ${s}`)
}
}
},
error => {},
() => {
debug('restore database done!')
resolve(true)
}
)
})
})
}

return false
} catch (error) {
throw error
}
}

getFileUri() {
return `${FileSystem.documentDirectory}SQLite/${this.config.database}`
}
Expand Down
57 changes: 57 additions & 0 deletions src/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,63 @@ export class Table<M, T extends TableClass<M>> {
return this.exec(sql)
}

async buildBackupSql() {
const { name, properties, table } = this

const columns = properties
.map(key => {
let column = Reflect.getMetadata(
COLUMN_META_KEY,
table,
key
) as ColumnInfo

if (!column) {
column = Reflect.getMetadata(
PRIMARY_META_KEY,
table,
key
) as ColumnInfo
}

if (!column) {
return false
}

return Utils.quote(key)
})
.filter(x => x !== false)

const cols = columns.join(', ')
const tbl = Utils.quote(name)

// const res = await this.exec(`SELECT ${cols} FROM ${tbl}`)
const values: any[] = await this.db.trx(async ({ query }) => {
return new Promise(resolve => {
query(`SELECT ${cols} FROM ${tbl}`).then(data => {
resolve(data)
})
})
})

if (!values || !values.length) {
return ''
}

const sql = `INSERT INTO ${tbl} (${cols}) VALUES ${values
.map(value => {
return (
'(' +
Object.keys(value)
.map(col => Utils.asRawValue(this.columns[col].type, value[col]))
.join(',') +
')'
)
})
.join(',')}`
return sql + ';'
}

dropTable() {
return this.exec(`DROP TABLE IF EXISTS ${Utils.quote(this.name)}`)
}
Expand Down
16 changes: 16 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ export class Utils {
return v
}

static asRawValue(colType: ColumnTypes, v: any) {
if (v === null) {
return 'NULL'
}

switch (colType) {
case 'DATETIME':
case 'INTEGER':
case 'BOOLEAN':
case 'DECIMAL':
return v
}

return `'${v}'`
}

static timeStamp(date: Date) {
return date
.toISOString()
Expand Down

0 comments on commit d918de6

Please sign in to comment.