blob: 9bcc75ec691714980c5fdc0bc6effbad7fe33a36 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.taobao.weex.appfram.storage;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.Nullable;
import com.taobao.weex.common.WXThread;
import com.taobao.weex.utils.WXLogUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DefaultWXStorage implements IWXStorageAdapter {
private WXSQLiteOpenHelper mDatabaseSupplier;
private ExecutorService mExecutorService;
private void execute(@Nullable final Runnable runnable) {
if (mExecutorService == null) {
mExecutorService = Executors.newSingleThreadExecutor();
}
if(runnable != null && !mExecutorService.isShutdown() && !mExecutorService.isTerminated()) {
mExecutorService.execute(WXThread.secure(runnable));
}
}
public DefaultWXStorage(Context context) {
this.mDatabaseSupplier = new WXSQLiteOpenHelper(context);
}
@Override
public void setItem(final String key, final String value, final OnResultReceivedListener listener) {
execute(new Runnable() {
@Override
public void run() {
Map<String, Object> data = StorageResultHandler.setItemResult(performSetItem(key, value, false, true));
if (listener == null) {
return;
}
listener.onReceived(data);
}
});
}
@Override
public void getItem(final String key, final OnResultReceivedListener listener) {
execute(new Runnable() {
@Override
public void run() {
Map<String, Object> data = StorageResultHandler.getItemResult(performGetItem(key));
if (listener == null) {
return;
}
listener.onReceived(data);
}
});
}
@Override
public void removeItem(final String key, final OnResultReceivedListener listener) {
execute(new Runnable() {
@Override
public void run() {
Map<String, Object> data = StorageResultHandler.removeItemResult(performRemoveItem(key));
if (listener == null) {
return;
}
listener.onReceived(data);
}
});
}
@Override
public void length(final OnResultReceivedListener listener) {
execute(new Runnable() {
@Override
public void run() {
Map<String, Object> data = StorageResultHandler.getLengthResult(performGetLength());
if (listener == null) {
return;
}
listener.onReceived(data);
}
});
}
@Override
public void getAllKeys(final OnResultReceivedListener listener) {
execute(new Runnable() {
@Override
public void run() {
Map<String, Object> data = StorageResultHandler.getAllkeysResult(performGetAllKeys());
if (listener == null) {
return;
}
listener.onReceived(data);
}
});
}
@Override
public void setItemPersistent(final String key, final String value, final OnResultReceivedListener listener) {
execute(new Runnable() {
@Override
public void run() {
Map<String, Object> data = StorageResultHandler.setItemResult(performSetItem(key, value, true, true));
if (listener == null) {
return;
}
listener.onReceived(data);
}
});
}
@Override
public void close() {
final ExecutorService needCloseService = mExecutorService;
execute(new Runnable() {
@Override
public void run() {
try {
mDatabaseSupplier.closeDatabase();
if (needCloseService != null) {
needCloseService.shutdown();
}
} catch (Exception e) {
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, e.getMessage());
}
}
});
mExecutorService = null;
}
private boolean performSetItem(String key, String value, boolean isPersistent, boolean allowRetryWhenFull) {
SQLiteDatabase database = mDatabaseSupplier.getDatabase();
if (database == null) {
return false;
}
WXLogUtils.d(WXSQLiteOpenHelper.TAG_STORAGE, "set k-v to storage(key:" + key + ",value:" + value + ",isPersistent:" + isPersistent + ",allowRetry:" + allowRetryWhenFull + ")");
String sql = "INSERT OR REPLACE INTO " + WXSQLiteOpenHelper.TABLE_STORAGE + " VALUES (?,?,?,?);";
SQLiteStatement statement = null;
String timeStamp = WXSQLiteOpenHelper.sDateFormatter.format(new Date());
try {
statement = database.compileStatement(sql);
statement.clearBindings();
statement.bindString(1, key);
statement.bindString(2, value);
statement.bindString(3, timeStamp);
statement.bindLong(4, isPersistent ? 1 : 0);
statement.execute();
return true;
} catch (Exception e) {
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, "DefaultWXStorage occurred an exception when execute setItem :" + e.getMessage());
if (e instanceof SQLiteFullException) {
if (allowRetryWhenFull && trimToSize()) {
//try again
//setItem/setItemPersistent method only allow try once when occurred a sqliteFullException.
WXLogUtils.d(WXSQLiteOpenHelper.TAG_STORAGE, "retry set k-v to storage(key:" + key + ",value:" + value + ")");
return performSetItem(key, value, isPersistent, false);
}
}
return false;
} finally {
if(statement != null) {
statement.close();
}
}
}
/**
* remove 10% of total record(at most) ordered by timestamp.
* */
private boolean trimToSize() {
SQLiteDatabase database = mDatabaseSupplier.getDatabase();
if (database == null) {
return false;
}
List<String> toEvict = new ArrayList<>();
int num = 0;
Cursor c = database.query(WXSQLiteOpenHelper.TABLE_STORAGE, new String[]{WXSQLiteOpenHelper.COLUMN_KEY, WXSQLiteOpenHelper.COLUMN_PERSISTENT}, null, null, null, null, WXSQLiteOpenHelper.COLUMN_TIMESTAMP + " ASC");
try {
int evictSize = c.getCount() / 10;
while (c.moveToNext()) {
String key = c.getString(c.getColumnIndex(WXSQLiteOpenHelper.COLUMN_KEY));
boolean persistent = c.getInt(c.getColumnIndex(WXSQLiteOpenHelper.COLUMN_PERSISTENT)) == 1;
if (!persistent && key != null) {
num++;
toEvict.add(key);
if (num == evictSize) {
break;
}
}
}
} catch (Exception e) {
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, "DefaultWXStorage occurred an exception when execute trimToSize:" + e.getMessage());
} finally {
c.close();
}
if (num <= 0) {
return false;
}
for (String key : toEvict) {
performRemoveItem(key);
}
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, "remove " + num + " items by lru");
return true;
}
private String performGetItem(String key) {
SQLiteDatabase database = mDatabaseSupplier.getDatabase();
if (database == null) {
return null;
}
Cursor c = database.query(WXSQLiteOpenHelper.TABLE_STORAGE,
new String[]{WXSQLiteOpenHelper.COLUMN_VALUE},
WXSQLiteOpenHelper.COLUMN_KEY + "=?",
new String[]{key},
null, null, null);
try {
if (c.moveToNext()) {
ContentValues values = new ContentValues();
//update timestamp
values.put(WXSQLiteOpenHelper.COLUMN_TIMESTAMP, WXSQLiteOpenHelper.sDateFormatter.format(new Date()));
int updateResult = mDatabaseSupplier.getDatabase().update(WXSQLiteOpenHelper.TABLE_STORAGE, values, WXSQLiteOpenHelper.COLUMN_KEY + "= ?", new String[]{key});
WXLogUtils.d(WXSQLiteOpenHelper.TAG_STORAGE, "update timestamp " + (updateResult == 1 ? "success" : "failed") + " for operation [getItem(key = " + key + ")]");
return c.getString(c.getColumnIndex(WXSQLiteOpenHelper.COLUMN_VALUE));
} else {
return null;
}
} catch (Exception e) {
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, "DefaultWXStorage occurred an exception when execute getItem:" + e.getMessage());
return null;
} finally {
c.close();
}
}
private boolean performRemoveItem(String key) {
SQLiteDatabase database = mDatabaseSupplier.getDatabase();
if (database == null) {
return false;
}
int count = 0;
try {
count = database.delete(WXSQLiteOpenHelper.TABLE_STORAGE,
WXSQLiteOpenHelper.COLUMN_KEY + "=?",
new String[]{key});
} catch (Exception e) {
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, "DefaultWXStorage occurred an exception when execute removeItem:" + e.getMessage());
return false;
}
return count == 1;
}
private long performGetLength() {
SQLiteDatabase database = mDatabaseSupplier.getDatabase();
if (database == null) {
return 0;
}
String sql = "SELECT count(" + WXSQLiteOpenHelper.COLUMN_KEY + ") FROM " + WXSQLiteOpenHelper.TABLE_STORAGE;
SQLiteStatement statement = null;
try {
statement = database.compileStatement(sql);
return statement.simpleQueryForLong();
} catch (Exception e) {
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, "DefaultWXStorage occurred an exception when execute getLength:" + e.getMessage());
return 0;
} finally {
if(statement != null) {
statement.close();
}
}
}
private List<String> performGetAllKeys() {
SQLiteDatabase database = mDatabaseSupplier.getDatabase();
if (database == null) {
return null;
}
List<String> result = new ArrayList<>();
Cursor c = database.query(WXSQLiteOpenHelper.TABLE_STORAGE, new String[]{WXSQLiteOpenHelper.COLUMN_KEY}, null, null, null, null, null);
try {
while (c.moveToNext()) {
result.add(c.getString(c.getColumnIndex(WXSQLiteOpenHelper.COLUMN_KEY)));
}
return result;
} catch (Exception e) {
WXLogUtils.e(WXSQLiteOpenHelper.TAG_STORAGE, "DefaultWXStorage occurred an exception when execute getAllKeys:" + e.getMessage());
return result;
} finally {
c.close();
}
}
}