package com.mygeopay.wallet;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import com.mygeopay.core.coins.CoinID;
import com.mygeopay.core.coins.CoinType;
import com.mygeopay.core.coins.Value;
import com.mygeopay.core.exchange.shapeshift.data.ShapeShiftTxStatus;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import java.io.Serializable;
import java.util.List;
import javax.annotation.Nonnull;
import static com.mygeopay.core.Preconditions.checkNotNull;
/**
* @author John L. Jegutanis
*/
public class ExchangeHistoryProvider extends ContentProvider {
private static final String DATABASE_TABLE = "exchange_history";
public static final String KEY_ROWID = "_id";
public static final String KEY_STATUS = "status";
public static final String KEY_DEPOSIT_TXID = "deposit_txid";
public static final String KEY_DEPOSIT_ADDRESS = "deposit_address";
public static final String KEY_DEPOSIT_COIN_ID = "deposit_coin_id";
public static final String KEY_DEPOSIT_AMOUNT_UNIT = "deposit_amount_unit";
public static final String KEY_WITHDRAW_TXID = "withdraw_txid";
public static final String KEY_WITHDRAW_ADDRESS = "withdraw_address";
public static final String KEY_WITHDRAW_COIN_ID = "withdraw_coin_id";
public static final String KEY_WITHDRAW_AMOUNT_UNIT = "withdraw_amount_unit";
private Helper helper;
public static Uri contentUri(@Nonnull final String packageName, @Nonnull final Address deposit) {
return Uri.parse("content://" + packageName + '.' + DATABASE_TABLE).buildUpon()
.appendPath(deposit.getParameters().getId()).appendPath(deposit.toString()).build();
}
public static Uri contentUri(@Nonnull final String packageName) {
return Uri.parse("content://" + packageName + '.' + DATABASE_TABLE);
}
public static ExchangeEntry getExchangeEntry(@Nonnull final Cursor cursor) {
final int status = getStatus(cursor);
CoinType depositType = CoinID.typeFromId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_DEPOSIT_COIN_ID)));
Address depositAddress;
try {
depositAddress = new Address(depositType, cursor.getString(cursor.getColumnIndexOrThrow(KEY_DEPOSIT_ADDRESS)));
} catch (AddressFormatException e) {
// Should never happen
throw new RuntimeException(e);
}
Value depositAmount = depositType.value(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_DEPOSIT_AMOUNT_UNIT)));
String depositTxId = cursor.getString(cursor.getColumnIndexOrThrow(KEY_DEPOSIT_TXID));
Address withdrawAddress;
Value withdrawAmount;
String withdrawTxId;
try {
CoinType withdrawType = CoinID.typeFromId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_WITHDRAW_COIN_ID)));
withdrawAddress = new Address(withdrawType, cursor.getString(cursor.getColumnIndexOrThrow(KEY_WITHDRAW_ADDRESS)));
withdrawAmount = withdrawType.value(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_WITHDRAW_AMOUNT_UNIT)));
withdrawTxId = cursor.getString(cursor.getColumnIndexOrThrow(KEY_WITHDRAW_TXID));
} catch (Exception e) {
withdrawAddress = null;
withdrawAmount = null;
withdrawTxId = null;
}
return new ExchangeEntry(status, depositAddress, depositAmount, depositTxId,
withdrawAddress, withdrawAmount, withdrawTxId);
}
public static int getStatus(@Nonnull final Cursor cursor) {
return cursor.getInt(cursor.getColumnIndexOrThrow(KEY_STATUS));
}
@Override
public boolean onCreate() {
helper = new Helper(getContext());
return true;
}
@Override
public String getType(final Uri uri) {
throw new UnsupportedOperationException();
}
private Address getDepositAddress(Uri uri) {
Address address;
final List<String> pathSegments = getPathSegments(uri);
try {
address = new Address(CoinID.typeFromId(pathSegments.get(0)), pathSegments.get(1));
} catch (AddressFormatException e) {
throw new IllegalArgumentException(e);
}
return address;
}
private List<String> getPathSegments(Uri uri) {
final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.size() != 2)
throw new IllegalArgumentException(uri.toString());
return pathSegments;
}
@Override
public Uri insert(final Uri uri, final ContentValues values) {
final Address address = getDepositAddress(uri);
values.put(KEY_DEPOSIT_COIN_ID, address.getParameters().getId());
values.put(KEY_DEPOSIT_ADDRESS, address.toString());
long rowId = helper.getWritableDatabase().insertOrThrow(DATABASE_TABLE, null, values);
final Uri rowUri = contentUri(getContext().getPackageName(), address).buildUpon()
.appendPath(Long.toString(rowId)).build();
getContext().getContentResolver().notifyChange(rowUri, null);
return rowUri;
}
@Override
public int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
final Address address = getDepositAddress(uri);
values.put(KEY_DEPOSIT_COIN_ID, address.getParameters().getId());
values.put(KEY_DEPOSIT_ADDRESS, address.toString());
final int count = helper.getWritableDatabase().update(DATABASE_TABLE, values,
KEY_DEPOSIT_COIN_ID + "=? AND " + KEY_DEPOSIT_ADDRESS + "=?",
new String[]{address.getParameters().getId(), address.toString()});
if (count > 0)
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
final Address address = getDepositAddress(uri);
final int count = helper.getWritableDatabase().delete(DATABASE_TABLE,
KEY_DEPOSIT_COIN_ID + "=? AND " + KEY_DEPOSIT_ADDRESS + "=?",
new String[]{address.getParameters().getId(), address.toString()});
if (count > 0)
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public Cursor query(final Uri uri, final String[] projection, final String selection,
final String[] selectionArgs, final String sortOrder) {
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(DATABASE_TABLE);
final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.size() > 2)
throw new IllegalArgumentException(uri.toString());
if (pathSegments.size() == 2) {
final Address address = getDepositAddress(uri);
qb.appendWhere(KEY_DEPOSIT_COIN_ID + "=");
qb.appendWhereEscapeString(address.getParameters().getId());
qb.appendWhere(" AND " + KEY_DEPOSIT_ADDRESS + "=");
qb.appendWhereEscapeString(address.toString());
}
final Cursor cursor = qb.query(helper.getReadableDatabase(), projection,
selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
private static class Helper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "exchange_history";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_CREATE = "CREATE TABLE " + DATABASE_TABLE + " ("
+ KEY_ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ KEY_STATUS + " INTEGER NOT NULL, "
+ KEY_DEPOSIT_ADDRESS + " TEXT NOT NULL, "
+ KEY_DEPOSIT_COIN_ID + " TEXT NOT NULL, "
+ KEY_DEPOSIT_AMOUNT_UNIT + " INTEGER NOT NULL, "
+ KEY_DEPOSIT_TXID + " TEXT NOT NULL, "
+ KEY_WITHDRAW_ADDRESS + " TEXT NULL, "
+ KEY_WITHDRAW_COIN_ID + " TEXT NULL, "
+ KEY_WITHDRAW_AMOUNT_UNIT + " INTEGER NULL, "
+ KEY_WITHDRAW_TXID + " TEXT NULL);";
public Helper(final Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(final SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
db.beginTransaction();
try {
for (int v = oldVersion; v < newVersion; v++)
upgrade(db, v);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private void upgrade(final SQLiteDatabase db, final int oldVersion) {
if (oldVersion == 1) {
// future
} else {
throw new UnsupportedOperationException("old=" + oldVersion);
}
}
}
public static class ExchangeEntry implements Serializable {
public static final int STATUS_INITIAL = 0;
public static final int STATUS_PROCESSING = 1;
public static final int STATUS_COMPLETE = 2;
public static final int STATUS_FAILED = -1;
public static final int STATUS_UNKNOWN = -2;
public final int status;
public final Address depositAddress;
public final Value depositAmount;
public final String depositTransactionId;
public final Address withdrawAddress;
public final Value withdrawAmount;
public final String withdrawTransactionId;
public ExchangeEntry(int status, @Nonnull Address depositAddress,
@Nonnull Value depositAmount, @Nonnull String depositTransactionId,
Address withdrawAddress, Value withdrawAmount,
String withdrawTransactionId) {
this.status = status;
this.depositAddress = checkNotNull(depositAddress);
this.depositAmount = checkNotNull(depositAmount);
this.depositTransactionId = checkNotNull(depositTransactionId);
this.withdrawAddress = withdrawAddress;
this.withdrawAmount = withdrawAmount;
this.withdrawTransactionId = withdrawTransactionId;
}
public ExchangeEntry(Address depositAddress, Value depositAmount, String depositTxId) {
this(STATUS_INITIAL, depositAddress, depositAmount, depositTxId, null, null, null);
}
public ExchangeEntry(ExchangeEntry initialEntry, ShapeShiftTxStatus txStatus) {
this.status = convertStatus(txStatus.status);
this.depositAddress = checkNotNull(txStatus.address == null ?
initialEntry.depositAddress : txStatus.address);
this.depositAmount = checkNotNull(txStatus.incomingValue == null ?
initialEntry.depositAmount : txStatus.incomingValue);
this.depositTransactionId = checkNotNull(initialEntry.depositTransactionId);
this.withdrawAddress = txStatus.withdraw;
this.withdrawAmount = txStatus.outgoingValue;
this.withdrawTransactionId = txStatus.transactionId;
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put(KEY_STATUS, status);
values.put(KEY_DEPOSIT_ADDRESS, depositAddress.toString());
values.put(KEY_DEPOSIT_COIN_ID, depositAddress.getParameters().getId());
values.put(KEY_DEPOSIT_AMOUNT_UNIT, depositAmount.value);
values.put(KEY_DEPOSIT_TXID, depositTransactionId);
if (withdrawAddress != null) values.put(KEY_WITHDRAW_ADDRESS, withdrawAddress.toString());
if (withdrawAddress != null) values.put(KEY_WITHDRAW_COIN_ID, withdrawAddress.getParameters().getId());
if (withdrawAmount != null) values.put(KEY_WITHDRAW_AMOUNT_UNIT, withdrawAmount.value);
if (withdrawTransactionId != null) values.put(KEY_WITHDRAW_TXID, withdrawTransactionId);
return values;
}
public ShapeShiftTxStatus getShapeShiftTxStatus() {
ShapeShiftTxStatus.Status shapeShiftStatus;
switch (status) {
case STATUS_INITIAL:
shapeShiftStatus = ShapeShiftTxStatus.Status.NO_DEPOSITS;
case STATUS_PROCESSING:
shapeShiftStatus = ShapeShiftTxStatus.Status.RECEIVED;
case STATUS_COMPLETE:
shapeShiftStatus = ShapeShiftTxStatus.Status.COMPLETE;
case STATUS_FAILED:
shapeShiftStatus = ShapeShiftTxStatus.Status.FAILED;
case STATUS_UNKNOWN:
default:
shapeShiftStatus = ShapeShiftTxStatus.Status.UNKNOWN;
}
return new ShapeShiftTxStatus(shapeShiftStatus, depositAddress, withdrawAddress,
depositAmount, withdrawAmount, withdrawTransactionId);
}
public static int convertStatus(ShapeShiftTxStatus.Status shapeShiftStatus) {
switch (shapeShiftStatus) {
case NO_DEPOSITS:
return STATUS_INITIAL;
case RECEIVED:
return STATUS_PROCESSING;
case COMPLETE:
return STATUS_COMPLETE;
case FAILED:
return STATUS_FAILED;
case UNKNOWN:
default:
return STATUS_UNKNOWN;
}
}
}
}