nativescript-toolbox-sw
Version:
Fork of nativescript-toolbox - A NativeScript module that is a composition of useful classes, tools and helpers.
862 lines (761 loc) • 25.7 kB
JavaScript
/**************************************************************************************
* (c) 2015, 2016, Master Technology
* Licensed under the MIT license or contact me for a support, changes, enhancements,
* and/or if you require a commercial licensing
*
* Any questions please feel free to email me or put a issue up on github
* Version 0.1.3 - Android Nathan@master-technology.com
*************************************************************************************/
"use strict";
var appModule = require("application");
/*jshint undef: true */
/*global java, android, Promise */
// Needed for Creating Database - Android Specific flag
//var CREATEIFNEEDED = 0x10000000;
/***
* Parses a Row of data into a JS Array (as Native)
* @param cursor {Object}
* @returns {Array}
* @constructor
*/
function DBGetRowArrayNative(cursor) {
//noinspection JSUnresolvedFunction
var count = cursor.getColumnCount();
var results = [];
for (var i=0;i<count;i++) {
var type = cursor.getType(i);
switch (type) {
case 0: // NULL
results.push(null);
break;
case 1: // Integer
//noinspection JSUnresolvedFunction
results.push(cursor.getInt(i));
break;
case 2: // Float
//noinspection JSUnresolvedFunction
results.push(cursor.getFloat(i));
break;
case 3: // String
results.push(cursor.getString(i));
break;
case 4: // Blob
results.push(cursor.getBlob(i));
break;
default:
throw new Error('SQLITE - Unknown Field Type ' + type);
}
}
return results;
}
/***
* Parses a Row of data into a JS Array (as String)
* @param cursor
* @returns {Array}
* @constructor
*/
function DBGetRowArrayString(cursor) {
//noinspection JSUnresolvedFunction
var count = cursor.getColumnCount();
var results = [];
for (var i=0;i<count;i++) {
var type = cursor.getType(i);
switch (type) {
case 0: // NULL
results.push(null);
break;
case 1: // Integer
//noinspection JSUnresolvedFunction
results.push(cursor.getString(i));
break;
case 2: // Float
//noinspection JSUnresolvedFunction
results.push(cursor.getString(i));
break;
case 3: // String
results.push(cursor.getString(i));
break;
case 4: // Blob
results.push(cursor.getBlob(i));
break;
default:
throw new Error('SQLITE - Unknown Field Type ' + type);
}
}
return results;
}
/***
* Parses a Row of data into a JS Object (as Native)
* @param cursor
* @returns {{}}
* @constructor
*/
function DBGetRowObjectNative(cursor) {
//noinspection JSUnresolvedFunction
var count = cursor.getColumnCount();
var results = {};
for (var i=0;i<count;i++) {
var type = cursor.getType(i);
//noinspection JSUnresolvedFunction
var name = cursor.getColumnName(i);
switch (type) {
case 0: // NULL
results[name] = null;
break;
case 1: // Integer
//noinspection JSUnresolvedFunction
results[name] = cursor.getInt(i);
break;
case 2: // Float
//noinspection JSUnresolvedFunction
results[name] = cursor.getFloat(i);
break;
case 3: // String
results[name] = cursor.getString(i);
break;
case 4: // Blob
results[name] = cursor.getBlob(i);
break;
default:
throw new Error('SQLITE - Unknown Field Type '+ type);
}
}
return results;
}
/***
* Parses a Row of data into a JS Object (as String)
* @param cursor
* @returns {{}}
* @constructor
*/
function DBGetRowObjectString(cursor) {
//noinspection JSUnresolvedFunction
var count = cursor.getColumnCount();
var results = {};
for (var i=0;i<count;i++) {
var type = cursor.getType(i);
//noinspection JSUnresolvedFunction
var name = cursor.getColumnName(i);
switch (type) {
case 0: // NULL
results[name] = null;
break;
case 1: // Integer
//noinspection JSUnresolvedFunction
results[name] = cursor.getString(i);
break;
case 2: // Float
//noinspection JSUnresolvedFunction
results[name] = cursor.getString(i);
break;
case 3: // String
results[name] = cursor.getString(i);
break;
case 4: // Blob
results[name] = cursor.getBlob(i);
break;
default:
throw new Error('SQLITE - Unknown Field Type '+ type);
}
}
return results;
}
// Default Resultset engine
var DBGetRowResults = DBGetRowArrayNative;
function setResultValueTypeEngine(resultType, valueType) {
if (resultType === Database.RESULTSASOBJECT) {
if (valueType === Database.VALUESARENATIVE) {
DBGetRowResults = DBGetRowObjectNative;
} else {
DBGetRowResults = DBGetRowObjectString;
}
} else { // RESULTSASARRAY
if (valueType === Database.VALUESARENATIVE) {
DBGetRowResults = DBGetRowArrayNative;
} else {
DBGetRowResults = DBGetRowArrayString;
}
}
}
/***
* Database Constructor
* @param dbname - Database Name
* @param callback - Callback when Done
* @param options
* @returns {Promise} object
* @constructor
*/
function Database(dbname, options, callback) {
if (!this instanceof Database) { // jshint ignore:line
//noinspection JSValidateTypes
return new Database(dbname, options, callback);
}
this._isOpen = false;
this._resultType = Database.RESULTSASARRAY;
this._valuesType = Database.VALUESARENATIVE;
if (typeof options === 'function') {
callback = options;
//noinspection JSUnusedAssignment
options = {};
} else {
//noinspection JSUnusedAssignment
options = options || {};
}
// Check to see if it has a path, or if it is a relative dbname
// dbname = "" - Temporary Database
// dbname = ":memory:" = memory database
if (dbname !== "" && dbname !== ":memory:") {
//var pkgName = appModule.android.context.getPackageName();
//noinspection JSUnresolvedFunction
dbname = _getContext().getDatabasePath(dbname).getAbsolutePath();
var path = dbname.substr(0, dbname.lastIndexOf('/') + 1);
// Create "databases" folder if it is missing. This causes issues on Emulators if it is missing
// So we create it if it is missing
try {
var javaFile = new java.io.File(path);
if (!javaFile.exists()) {
//noinspection JSUnresolvedFunction
javaFile.mkdirs();
//noinspection JSUnresolvedFunction
javaFile.setReadable(true);
//noinspection JSUnresolvedFunction
javaFile.setWritable(true);
}
}
catch (err) {
console.info("SQLITE.CONSTRUCTOR - Creating DB Folder Error", err);
}
}
var self = this;
return new Promise(function (resolve, reject) {
try {
if (dbname === ":memory:") {
//noinspection JSUnresolvedVariable
self._db = android.database.sqlite.SQLiteDatabase.create(null);
} else {
//noinspection JSUnresolvedVariable,JSUnresolvedFunction
self._db = android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(dbname, null);
}
} catch (err) {
console.error("SQLITE.CONSTRUCTOR - Open DB Error", err);
if (callback) { callback(err, null); }
reject(err);
return;
}
self._isOpen = true;
if (callback) { callback(null, self); }
resolve(self);
});
}
/***
* Constant that this structure is a sqlite structure
* @type {boolean}
*/
Database.prototype._isSqlite = true;
/***
* This gets or sets the database version
* @param valueOrCallback to set or callback(err, version)
* @returns Promise
*/
Database.prototype.version = function(valueOrCallback) {
if (typeof valueOrCallback === 'function') {
return this.get('PRAGMA user_version', function (err, data) {
valueOrCallback(err, data && data[0]);
}, Database.RESULTSASARRAY);
} else if (!isNaN(valueOrCallback+0)) {
return this.execSQL('PRAGMA user_version='+(valueOrCallback+0).toString());
} else {
return this.get('PRAGMA user_version', Database.RESULTSASARRAY);
}
};
/***
* Is the database currently open
* @returns {boolean} - true if the db is open
*/
Database.prototype.isOpen = function() {
return this._isOpen;
};
/***
* Gets/Sets whether you get Arrays or Objects for the row values
* @param value - Database.RESULTSASARRAY or Database.RESULTSASOBJECT
* @returns {number} - Database.RESULTSASARRAY or Database.RESULTSASOBJECT
*/
Database.prototype.resultType = function(value) {
if (value === Database.RESULTSASARRAY) {
this._resultType = Database.RESULTSASARRAY;
setResultValueTypeEngine(this._resultType, this._valuesType);
} else if (value === Database.RESULTSASOBJECT) {
this._resultType = Database.RESULTSASOBJECT;
setResultValueTypeEngine(this._resultType, this._valuesType);
}
return this._resultType;
};
/***
* Gets/Sets whether you get Native or Strings for the row values
* @param value - Database.VALUESARENATIVE or Database.VALUESARESTRINGS
* @returns {number} - Database.VALUESARENATIVE or Database.VALUESARESTRINGS
*/
Database.prototype.valueType = function(value) {
if (value === Database.VALUESARENATIVE) {
this._valuesType = Database.VALUESARENATIVE;
setResultValueTypeEngine(this._resultType, this._valuesType);
} else if (value === Database.VALUESARESTRINGS) {
this._valuesType = Database.VALUESARESTRINGS;
setResultValueTypeEngine(this._resultType, this._valuesType);
}
return this._resultType;
};
/***
* Closes this database, any queries after this will fail with an error
* @param callback
*/
Database.prototype.close = function(callback) {
var self = this;
return new Promise(function(resolve, reject) {
if (!self._isOpen) {
if (callback) {
callback('SQLITE.CLOSE - Database is already closed');
}
reject('SQLITE.CLOSE - Database is already closed');
return;
}
self._db.close();
self._isOpen = false;
if (callback) {
callback(null, null);
}
resolve();
});
};
/***
* Exec SQL
* @param sql - sql to use
* @param params - optional array of parameters
* @param callback - (err, result) - can be last_row_id for insert, and rows affected for update/delete
* @returns Promise
*/
Database.prototype.execSQL = function(sql, params, callback) {
if (typeof params === 'function') {
callback = params;
params = undefined;
}
var self = this;
return new Promise(function(resolve, reject) {
var hasCallback = true;
if (typeof callback !== 'function') {
callback = reject;
hasCallback = false;
}
if (!self._isOpen) {
callback("SQLITE.EXECSQL - Database is not open");
return;
}
// Need to see if we have to run any status queries afterwords
var flags = 0;
var test = sql.trim().substr(0, 7).toLowerCase();
if (test === 'insert ') {
flags = 1;
} else if (test === 'update ' || test === 'delete ') {
flags = 2;
}
try {
if (params !== undefined) {
self._db.execSQL(sql, self._toStringArray(params));
} else {
self._db.execSQL(sql);
}
} catch (Err) {
callback(Err, null);
return;
}
switch (flags) {
case 0:
if (hasCallback) {
callback(null, null);
}
resolve(null);
break;
case 1:
self.get('select last_insert_rowid()', function (err, data) {
if (hasCallback) {
callback(err, data && data[0]);
}
if (err) {
reject(err);
} else {
resolve(data && data[0]);
}
}, Database.RESULTSASARRAY | Database.VALUESARENATIVE);
break;
case 2:
self.get('select changes()', function (err, data) {
if (hasCallback) {
callback(err, data && data[0]);
}
if (err) {
reject(err);
} else {
resolve(data && data[0]);
}
}, Database.RESULTSASARRAY | Database.VALUESARENATIVE);
break;
default:
resolve();
}
});
};
/***
* Get the first record result set
* @param sql - sql to run
* @param params - optional
* @param callback - callback (error, results)
* @param mode - allows you to manually override the results set to be a array or object
* @returns Promise
*/
Database.prototype.get = function(sql, params, callback, mode) {
if (typeof params === 'function') {
mode = callback;
callback = params;
params = undefined;
}
var self = this;
return new Promise(function(resolve, reject) {
var hasCallback = true;
if (typeof callback !== 'function') {
callback = reject;
hasCallback = false;
}
if (!self._isOpen) {
callback("SQLITE.GET - Database is not open", null);
return;
}
var cursor;
try {
if (params !== undefined) {
//noinspection JSUnresolvedFunction
cursor = self._db.rawQuery(sql, self._toStringArray(params));
} else {
//noinspection JSUnresolvedFunction
cursor = self._db.rawQuery(sql, null);
}
} catch (err) {
callback(err, null);
return;
}
// No Records
if (cursor.getCount() === 0) {
cursor.close();
if (hasCallback) {
callback(null, null);
}
resolve(null);
return;
}
var results;
var resultEngine = self._getResultEngine(mode);
try {
//noinspection JSUnresolvedFunction
cursor.moveToFirst();
results = resultEngine(cursor);
cursor.close();
} catch (err) {
callback(err, null);
return;
}
if (hasCallback) {
callback(null, results);
}
resolve(results);
});
};
Database.prototype._getResultEngine = function(mode) {
if (mode == null || mode === 0) return DBGetRowResults;
var resultType = (mode & Database.RESULTSASARRAY|Database.RESULTSASOBJECT);
if (resultType === 0) {
resultType = this._resultType;
}
var valueType = (mode & Database.VALUESARENATIVE|Database.VALUESARESTRINGS);
if (valueType === 0) {
valueType = this._valuesType;
}
if (resultType === Database.RESULTSASOBJECT) {
if (valueType === Database.VALUESARESTRINGS) {
return DBGetRowObjectString;
} else {
return DBGetRowObjectNative;
}
} else {
if (valueType === Database.VALUESARESTRINGS) {
return DBGetRowArrayString;
} else {
return DBGetRowArrayNative;
}
}
};
/***
* This returns the entire result set in a array of rows
* @param sql - Sql to run
* @param params - optional
* @param callback - (err, results)
* @returns Promise
*/
Database.prototype.all = function(sql, params, callback) {
if (typeof params === 'function') {
callback = params;
params = undefined;
}
var self = this;
return new Promise(function(resolve, reject) {
var hasCallback = true;
if (typeof callback !== 'function') {
callback = reject;
hasCallback = false;
}
if (!self._isOpen) {
callback("SQLITE.ALL - Database is not open");
return;
}
var cursor, count;
try {
if (params !== undefined) {
//noinspection JSUnresolvedFunction
cursor = self._db.rawQuery(sql, self._toStringArray(params));
} else {
//noinspection JSUnresolvedFunction
cursor = self._db.rawQuery(sql, null);
}
count = cursor.getCount();
} catch (err) {
callback(err);
return;
}
// No Records
if (count === 0) {
cursor.close();
if (hasCallback) {
callback(null, []);
}
resolve([]);
return;
}
//noinspection JSUnresolvedFunction
cursor.moveToFirst();
var results = [];
try {
for (var i = 0; i < count; i++) {
var data = DBGetRowResults(cursor); // jshint ignore:line
results.push(data);
//noinspection JSUnresolvedFunction
cursor.moveToNext();
}
cursor.close();
} catch (err) {
callback(err);
return;
}
if (hasCallback) {
callback(null, results);
}
resolve(results);
});
};
/***
* This sends each row of the result set to the "Callback" and at the end calls the complete callback upon completion
* @param sql - sql to run
* @param params - optional
* @param callback - callback (err, rowsResult)
* @param complete - callback (err, recordCount)
* @returns Promise
*/
Database.prototype.each = function(sql, params, callback, complete) {
if (typeof params === 'function') {
complete = callback;
callback = params;
params = undefined;
}
// Callback is required
if (typeof callback !== 'function') {
throw new Error("SQLITE.EACH - requires a callback");
}
var self = this;
return new Promise(function (resolve, reject) {
// Set the error Callback
var errorCB = complete || callback;
var cursor, count;
try {
if (params !== undefined) {
//noinspection JSUnresolvedFunction
cursor = self._db.rawQuery(sql, self._toStringArray(params));
} else {
//noinspection JSUnresolvedFunction
cursor = self._db.rawQuery(sql, null);
}
count = cursor.getCount();
} catch (err) {
errorCB(err, null);
reject(err);
return;
}
// No Records
if (count === 0) {
cursor.close();
if (complete) {
complete(null, 0);
}
resolve(0);
return;
}
//noinspection JSUnresolvedFunction
cursor.moveToFirst();
try {
for (var i = 0; i < count; i++) {
var data = DBGetRowResults(cursor); // jshint ignore:line
callback(null, data);
//noinspection JSUnresolvedFunction
cursor.moveToNext();
}
cursor.close();
} catch (err) {
errorCB(err, null);
reject(err);
return;
}
if (complete) {
complete(null, count);
}
resolve(count);
});
};
/***
* Converts a Mixed Array to a String Array
* @param params
* @returns {Array}
* @private
*/
Database.prototype._toStringArray = function(params) {
var stringParams = [];
if (Object.prototype.toString.apply(params) === '[object Array]') {
var count = params.length;
for (var i=0; i<count; ++i) {
if (params[i] == null) { // jshint ignore:line
stringParams.push(null);
} else {
stringParams.push(params[i].toString());
}
}
} else {
if (params == null) { // jshint ignore:line
stringParams.push(null);
} else {
stringParams.push(params.toString());
}
}
return stringParams;
};
/***
* Is this a SQLite object
* @param obj - possible sqlite object to check
* @returns {boolean}
*/
Database.isSqlite = function(obj) {
return obj && obj._isSqlite;
};
/**
* Does this database exist on disk
* @param name
* @returns {*}
*/
Database.exists = function(name) {
//noinspection JSUnresolvedFunction
var dbName = _getContext().getDatabasePath(name).getAbsolutePath();
var dbFile = new java.io.File(dbName);
return dbFile.exists();
};
/**
* Delete the database file if it exists
* @param name
*/
Database.deleteDatabase = function(name) {
//noinspection JSUnresolvedFunction
var dbName = _getContext().getDatabasePath(name).getAbsolutePath();
var dbFile = new java.io.File(dbName);
if (dbFile.exists()) {
dbFile.delete();
dbFile = new java.io.File(dbName + '-journal');
if (dbFile.exists()) {
dbFile.delete();
}
}
};
/**
* Copy the database from the install location
* @param name
*/
Database.copyDatabase = function(name) {
//Open your local db as the input stream
//noinspection JSUnresolvedFunction
var myInput = _getContext().getAssets().open("app/"+name);
if (name.indexOf('/')) {
name = name.substring(name.indexOf('/')+1);
}
//noinspection JSUnresolvedFunction
var dbname = _getContext().getDatabasePath(name).getAbsolutePath();
var path = dbname.substr(0, dbname.lastIndexOf('/') + 1);
// Create "databases" folder if it is missing. This causes issues on Emulators if it is missing
// So we create it if it is missing
try {
var javaFile = new java.io.File(path);
if (!javaFile.exists()) {
//noinspection JSUnresolvedFunction
javaFile.mkdirs();
//noinspection JSUnresolvedFunction
javaFile.setReadable(true);
//noinspection JSUnresolvedFunction
javaFile.setWritable(true);
}
}
catch (err) {
console.info("SQLITE - COPYDATABASE - Creating DB Folder Error", err);
}
//Open the empty db as the output stream
var myOutput = new java.io.FileOutputStream(dbname);
var success = true;
try {
//transfer bytes from the inputfile to the outputfile
//noinspection JSUnresolvedFunction,JSUnresolvedVariable
var buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.class.getField("TYPE").get(null), 1024);
var length;
while ((length = myInput.read(buffer)) > 0) {
//noinspection JSCheckFunctionSignatures
myOutput.write(buffer, 0, length);
}
}
catch (err) {
success = false;
}
//Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
return success;
};
// Literal Defines
Database.RESULTSASARRAY = 1;
Database.RESULTSASOBJECT = 2;
Database.VALUESARENATIVE = 4;
Database.VALUESARESTRINGS = 8;
module.exports = Database;
/**
* gets the current application context
* @returns {*}
* @private
*/
function _getContext() {
if (appModule.android.context) {
return (appModule.android.context);
}
var ctx = java.lang.Class.forName("android.app.AppGlobals").getMethod("getInitialApplication", null).invoke(null, null);
if (ctx) return ctx;
ctx = java.lang.Class.forName("android.app.ActivityThread").getMethod("currentApplication", null).invoke(null, null);
return ctx;
}