Skip to content

Commit

Permalink
Merge branch '8_1_X' into 8_1_X_INZDR
Browse files Browse the repository at this point in the history
* 8_1_X: (46 commits)
  fix(ios): exclude all frameworks from optimizing files step (8_1_X) (tidev#10996)
  fix(android): fix asset encryption on windows 32bit java (tidev#11155)
  fix(ios): add script message handler for local files only (tidev#11086)
  fix(android): improve merge of <uses-feature/> elements (tidev#11111)
  fix(android): splash open animation sometimes stutters on cold start (tidev#11140)
  fix(android): fix dialog without selectedIndex reusage
  fix(android): resuming with intent "FLAG_ACTIVITY_MULTIPLE_TASK" can hang the app (tidev#11081)
  fix(android): regression where closing root window from child window causes app exit issues as of 8.0.1 (tidev#11093)
  fix(android): fix reusing a dialog with a new "parent" window (tidev#11096)
  chore(release): bump version
  fix(ios): console.log does not log properly if it has multiple arguments (tidev#11125)
  fix(android): update titanium_prep windows binaries
  chore(android)(8_1_X): update titanium_prep binaries (tidev#11100)
  fix(android)(8_1_X): ListView's SearchBar/SearchView wrongly overlaps rows as of 8.0.1 (tidev#11105)
  chore(android): update aps-analytics (tidev#11101)
  fix(ios): fix crashes in calendar upon giving permission (tidev#11078)
  fix(ios,android)(8_1_X): implement exception signal handler (tidev#11077)
  fix(cli): add deprecation notice for node < 10.13 (tidev#11063)
  test(db): bump up timeout
  test(db): drop long-running query count to 5000 rows
  ...
  • Loading branch information
rlustemberg committed Aug 21, 2019
2 parents 16720b7 + 27eba2a commit 02887ae
Show file tree
Hide file tree
Showing 60 changed files with 2,003 additions and 579 deletions.
5 changes: 4 additions & 1 deletion android/cli/commands/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ AndroidBuilder.prototype.validate = function validate(logger, config, cli) {
if (cli.argv['source-maps']) {
this.sourceMaps = true;
// if they haven't, respect the tiapp.xml value if set one way or the other
} else if (cli.tiapp.hasOwnProperty['source-maps']) { // they've explicitly set a value in tiapp.xml
} else if (Object.prototype.hasOwnProperty.call(cli.tiapp, 'source-maps')) { // they've explicitly set a value in tiapp.xml
this.sourceMaps = cli.tiapp['source-maps'] === true; // respect the tiapp.xml value
} else { // otherwise turn on by default for non-production builds
this.sourceMaps = this.deployType !== 'production';
Expand Down Expand Up @@ -1973,6 +1973,9 @@ AndroidBuilder.prototype.loginfo = function loginfo(next) {
this.logger.info(__('Profiler disabled'));
}

this.logger.info(__('Transpile javascript: %s', (this.transpile ? 'true' : 'false').cyan));
this.logger.info(__('Generate source maps: %s', (this.sourceMaps ? 'true' : 'false').cyan));

next();
};

Expand Down
33 changes: 30 additions & 3 deletions android/cli/lib/AndroidManifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,12 +513,39 @@ function AndroidManifest(filename) {
case 'uses-feature':
this[tag] || (this[tag] = []);
src[tag].forEach(function (tagItem) {
// Check for already added features.
let duplicateItem = this[tag].find(function (nextItem) {
// If given "uses-feature" name has already been added, then fetch its object.
const duplicateItem = this[tag].find(function (nextItem) {
// Compare them directly or by name.
return (nextItem === tagItem) || (nextItem.name === tagItem.name);
});
if (!duplicateItem) {
if (duplicateItem === tagItem) {
// Given reference was already added. Do nothing.
} else if (duplicateItem) {
// This is a duplicate "uses-feature" element name. Merge its settings.
if (typeof tagItem['tools:replace'] === 'string') {
// This attribute provides an array of other attributes that must be replaced.
tagItem['tools:replace'].split(',').forEach(function (attributeName) {
attributeName = attributeName.replace(androidAttrPrefixRegExp, '');
if (attributeName !== 'name') {
const value = tagItem[attributeName];
if (typeof value === 'undefined') {
delete duplicateItem[attributeName];
} else {
duplicateItem[attributeName] = value;
}
}
});
} else if (duplicateItem.required === false) {
// Do a logical OR on the "required" attribute value.
// If the "required" attribute is not defined, then it is considered true.
if (typeof tagItem.required === 'undefined') {
delete duplicateItem.required;
} else if (tagItem.required) {
duplicateItem.required = tagItem.required;
}
}
} else {
// The given "uses-feature" name has not been added yet. Do so now.
this[tag].push(tagItem);
}
}, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
package ti.modules.titanium.database;

import org.appcelerator.kroll.JSError;
import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollFunction;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.kroll.annotations.Kroll;
Expand All @@ -22,8 +24,11 @@
import android.database.sqlite.SQLiteDatabase;
import android.os.Looper;

import java.lang.Exception;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -36,6 +41,7 @@ public class TiDatabaseProxy extends KrollProxy
private Thread thread;
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
private AtomicBoolean executingQueue = new AtomicBoolean(false);
private boolean isClosed = false;

protected SQLiteDatabase db;
protected String name;
Expand Down Expand Up @@ -125,37 +131,41 @@ private void waitForQueue()
@Kroll.method
public void close()
{
if (db.isOpen()) {

// Wait for query queue to empty.
waitForQueue();

synchronized (this)
{
// Close database.
db.close();
if (db != null && db.isOpen()) {
db.close();
}
db = null;
isClosed = true;

// Abort query queue execution.
if (thread != null) {
thread.interrupt();
thread = null;
}
executingQueue.set(false);
queue.clear();
}
}

private boolean expectResult(String query)
{
String lowerCaseQuery = query.toLowerCase().trim();
return lowerCaseQuery.startsWith("select")
|| (lowerCaseQuery.startsWith("pragma") && !lowerCaseQuery.contains("="));
}

/**
* Synchronously execute a single SQL query.
* @param query SQL query to execute on database.
* @param parameterObjects Parameters for `query`
*/
@Kroll.method
public TiResultSetProxy execute(String query, Object... parameterObjects)
private TiResultSetProxy executeSQL(String query, Object[] parameterObjects)
{
// Validate `query` parameter.
if (query == null) {
throw new InvalidParameterException("'query' parameter is required");
}

// Validate and parse `parameterObjects`.
if (parameterObjects != null) {

// Only an array is defined, use that for parameters
if (parameterObjects.length == 1 && parameterObjects[0] instanceof Object[]) {
parameterObjects = (Object[]) parameterObjects[0];
}

// Validate query parameters, must be either String or Blob.
for (int i = 0; i < parameterObjects.length; i++) {
if (parameterObjects[i] instanceof TiBlob) {
Expand All @@ -164,21 +174,25 @@ public TiResultSetProxy execute(String query, Object... parameterObjects)
parameterObjects[i] = TiConvert.toString(parameterObjects[i]);
}
}
} else {
parameterObjects = new Object[0];
}

// If this is a synchronous call on the main thread, wait for all queued queries
// to maintain correct execution order and prevent write-locks.
waitForQueue();

// Log.d(TAG, "execute: " + query);

TiResultSetProxy result = null;
Cursor cursor = null;

// Execute query using rawQuery() in order to receive results.
String lowerCaseQuery = query.toLowerCase().trim();
if (lowerCaseQuery.startsWith("select")
|| (lowerCaseQuery.startsWith("pragma") && !lowerCaseQuery.contains("="))) {
synchronized (this)
{ // lock on db proxy instance
if (isClosed) {
throw new IllegalStateException("database is closed");
}

if (!expectResult(query)) {
db.execSQL(query, parameterObjects);
return null;
}

// Query parameters must be strings.
String parameters[] = new String[parameterObjects.length];
Expand All @@ -188,34 +202,50 @@ public TiResultSetProxy execute(String query, Object... parameterObjects)
}
}

cursor = db.rawQuery(query, parameters);
Cursor cursor = db.rawQuery(query, parameters);
if (cursor != null) {

// Validate and set query result.
if (cursor.getColumnCount() > 0) {
result = new TiResultSetProxy(cursor);
TiResultSetProxy result = new TiResultSetProxy(cursor);
if (result.isValidRow()) {
result.next();
}
} else {
return result;
}

// Cleanup result.
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
// Ignore...
}
}
// Cleanup result.
try {
cursor.close();
} catch (Exception e) {
// Ignore...
}
}

// Query does not return result, use execSQL().
} else {
db.execSQL(query, parameterObjects);
return null;
}
}

/**
* Synchronously execute a single SQL query.
* @param query SQL query to execute on database.
* @param parameterObjects Parameters for `query`
*/
@Kroll.method
public TiResultSetProxy execute(String query, Object... parameterObjects)
{
// Validate `query` parameter.
if (query == null) {
throw new InvalidParameterException("'query' parameter is required");
}

return result;
// Support either varargs or a single array as params
if (parameterObjects != null) {
// Only an array is defined, use that for parameters
if (parameterObjects.length == 1 && parameterObjects[0] instanceof Object[]) {
parameterObjects = (Object[]) parameterObjects[0];
}
}
return executeSQL(query, parameterObjects);
}

/**
Expand All @@ -241,18 +271,22 @@ public void executeAsync(final String query, final Object... parameterObjects)

// Reconstruct parameters array without `callback` element.
final Object parameters[] = new Object[parameterObjects.length - 1];
for (int i = 0; i < parameters.length; i++) {
parameters[i] = parameterObjects[i];
}
System.arraycopy(parameterObjects, 0, parameters, 0, parameterObjects.length - 1);

executingQueue.set(true);
try {
queue.put(new Runnable() {
@Override
public void run()
{
final TiResultSetProxy result = execute(query, parameters);
callback.callAsync(getKrollObject(), new Object[] { result });
Object[] args = null;
try {
final TiResultSetProxy result = executeSQL(query, parameters);
args = new Object[] { null, result };
} catch (Throwable t) {
args = new Object[] { t };
}
callback.callAsync(getKrollObject(), args);
}
});
} catch (InterruptedException e) {
Expand All @@ -265,17 +299,21 @@ public void run()
* @param queries SQL queries to execute on database.
*/
@Kroll.method
public Object[] executeAll(final String[] queries)
public Object[] executeAll(final String[] queries) throws BatchQueryException
{
// Validate `queries` parameter.
if (queries == null || queries.length == 0) {
throw new InvalidParameterException("'query' parameter is required");
}

ArrayList<TiResultSetProxy> results = new ArrayList<>(queries.length);
for (String query : queries) {
final TiResultSetProxy result = execute(query);
results.add(result);
List<TiResultSetProxy> results = new ArrayList<>(queries.length);
for (int index = 0; index < queries.length; index++) {
try {
final TiResultSetProxy result = executeSQL(queries[index], null);
results.add(result);
} catch (Throwable t) {
throw new BatchQueryException(t, index, results);
}
}
return results.toArray();
}
Expand All @@ -299,8 +337,18 @@ public void executeAllAsync(final String[] queries, final KrollFunction callback
@Override
public void run()
{
final Object[] results = executeAll(queries);
callback.callAsync(getKrollObject(), new Object[] { results });
Throwable error = null;
List<TiResultSetProxy> results = new ArrayList<>(queries.length);
for (int index = 0; index < queries.length; index++) {
try {
final TiResultSetProxy result = executeSQL(queries[index], null);
results.add(result);
} catch (Throwable t) {
error = new BatchQueryException(t, index, null);
break;
}
}
callback.callAsync(getKrollObject(), new Object[] { error, results.toArray() });
}
});
} catch (InterruptedException e) {
Expand Down Expand Up @@ -331,7 +379,13 @@ public String getName()
public int getLastInsertRowId()
// clang-format on
{
return (int) DatabaseUtils.longForQuery(db, "select last_insert_rowid()", null);
synchronized (this)
{ // lock on db proxy instance
if (isClosed) {
throw new IllegalStateException("database is closed");
}
return (int) DatabaseUtils.longForQuery(db, "select last_insert_rowid()", null);
}
}

/**
Expand All @@ -344,7 +398,13 @@ public int getLastInsertRowId()
public int getRowsAffected()
// clang-format on
{
return (int) DatabaseUtils.longForQuery(db, "select changes()", null);
synchronized (this)
{ // lock on db proxy instance
if (isClosed) {
throw new IllegalStateException("database is closed");
}
return (int) DatabaseUtils.longForQuery(db, "select changes()", null);
}
}

/**
Expand Down Expand Up @@ -396,14 +456,29 @@ public String getApiName()
public void release()
{
this.close();
this.db = null;
super.release();
}

// Interrupt and dereference thread.
if (this.thread != null) {
this.thread.interrupt();
this.thread = null;
private static class BatchQueryException extends Exception implements JSError
{
private final int index;
private final List<TiResultSetProxy> partialResults;

BatchQueryException(Throwable t, int index, List<TiResultSetProxy> partialResults)
{
super(t);
this.index = index;
this.partialResults = partialResults;
}

super.release();
public HashMap getJSProperties()
{
HashMap map = new HashMap();
map.put("index", index);
if (partialResults != null) {
map.put("results", partialResults.toArray());
}
return map;
}
}
}

0 comments on commit 02887ae

Please sign in to comment.