diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.java b/app/src/main/java/ch/dissem/apps/abit/MainActivity.java index ca8ec07..c6e9296 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.java @@ -188,6 +188,16 @@ public class MainActivity extends AppCompatActivity .withTag(identity) ); } + if (profiles.isEmpty()){ + // Create an initial identity + BitmessageAddress identity = Singleton.getIdentity(this); + profiles.add(new ProfileDrawerItem() + .withIcon(new Identicon(identity)) + .withName(identity.toString()) + .withEmail(identity.getAddress()) + .withTag(identity) + ); + } profiles.add(new ProfileSettingDrawerItem() .withName("Add Identity") .withDescription("Create new identity") diff --git a/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java index 6d7c3b4..955531f 100644 --- a/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java +++ b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java @@ -100,7 +100,8 @@ public class SubscriptionDetailFragment extends Fragment { TextView address = (TextView) rootView.findViewById(R.id.address); address.setText(item.getAddress()); address.setSelected(true); - ((TextView) rootView.findViewById(R.id.stream_number)).setText(getActivity().getString(R.string.stream_number, item.getStream())); + ((TextView) rootView.findViewById(R.id.stream_number)).setText(getActivity() + .getString(R.string.stream_number, item.getStream())); Switch active = (Switch) rootView.findViewById(R.id.active); active.setChecked(item.isSubscribed()); active.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @@ -109,6 +110,14 @@ public class SubscriptionDetailFragment extends Fragment { item.setSubscribed(isChecked); } }); + + ImageView pubkeyAvailableImg = (ImageView) rootView.findViewById(R.id.pubkey_available); + if (item.getPubkey() == null) { + pubkeyAvailableImg.setAlpha(0.3f); + TextView pubkeyAvailableDesc = (TextView) rootView.findViewById(R.id + .pubkey_available_desc); + pubkeyAvailableDesc.setText(R.string.pubkey_not_available); + } } return rootView; diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java index aa20b3e..b7fccaf 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidInventory.java @@ -33,10 +33,18 @@ import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import static ch.dissem.apps.abit.repository.SqlHelper.join; +import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; import static ch.dissem.bitmessage.utils.UnixTime.now; /** @@ -55,47 +63,64 @@ public class AndroidInventory implements Inventory { private final SqlHelper sql; + private final Map> cache = new ConcurrentHashMap<>(); + public AndroidInventory(SqlHelper sql) { this.sql = sql; } @Override public List getInventory(long... streams) { - return getInventory(false, streams); + List result = new LinkedList<>(); + long now = now(); + for (long stream : streams) { + for (Map.Entry e : getCache(stream).entrySet()) { + if (e.getValue() > now) { + result.add(e.getKey()); + } + } + } + return result; } - public List getInventory(boolean includeExpired, long... streams) { - // Define a projection that specifies which columns from the database - // you will actually use after this query. - String[] projection = { - COLUMN_HASH - }; + private Map getCache(long stream) { + Map result = cache.get(stream); + if (result == null) { + synchronized (cache) { + if (cache.get(stream) == null) { + result = new ConcurrentHashMap<>(); + cache.put(stream, result); - SQLiteDatabase db = sql.getReadableDatabase(); - Cursor c = db.query( - TABLE_NAME, projection, - (includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join - (streams) + ")", - null, null, null, null - ); - List result = new LinkedList<>(); - try { - c.moveToFirst(); - while (!c.isAfterLast()) { - byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); - result.add(new InventoryVector(blob)); - c.moveToNext(); + String[] projection = { + COLUMN_HASH, COLUMN_EXPIRES + }; + + SQLiteDatabase db = sql.getReadableDatabase(); + try (Cursor c = db.query( + TABLE_NAME, projection, + "stream = " + stream, + null, null, null, null + )) { + c.moveToFirst(); + while (!c.isAfterLast()) { + byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); + long expires = c.getLong(c.getColumnIndex(COLUMN_EXPIRES)); + result.put(new InventoryVector(blob), expires); + c.moveToNext(); + } + } + LOG.info("Stream #" + stream + " inventory size: " + result.size()); + } } - } finally { - c.close(); } - LOG.info("Inventory size: " + result.size()); return result; } @Override public List getMissing(List offer, long... streams) { - offer.removeAll(getInventory(true, streams)); + for (long stream : streams) { + offer.removeAll(getCache(stream).keySet()); + } LOG.info(offer.size() + " objects missing."); return offer; } @@ -110,12 +135,11 @@ public class AndroidInventory implements Inventory { }; SQLiteDatabase db = sql.getReadableDatabase(); - Cursor c = db.query( + try (Cursor c = db.query( TABLE_NAME, projection, "hash = X'" + vector + "'", null, null, null, null - ); - try { + )) { c.moveToFirst(); if (c.isAfterLast()) { LOG.info("Object requested that we don't have. IV: " + vector); @@ -125,8 +149,6 @@ public class AndroidInventory implements Inventory { int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length); - } finally { - c.close(); } } @@ -150,13 +172,12 @@ public class AndroidInventory implements Inventory { } SQLiteDatabase db = sql.getReadableDatabase(); - Cursor c = db.query( + List result = new LinkedList<>(); + try (Cursor c = db.query( TABLE_NAME, projection, where.toString(), null, null, null, null - ); - List result = new LinkedList<>(); - try { + )) { c.moveToFirst(); while (!c.isAfterLast()) { int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION)); @@ -165,8 +186,6 @@ public class AndroidInventory implements Inventory { blob.length)); c.moveToNext(); } - } finally { - c.close(); } return result; } @@ -174,6 +193,10 @@ public class AndroidInventory implements Inventory { @Override public void storeObject(ObjectMessage object) { InventoryVector iv = object.getInventoryVector(); + + if (getCache(object.getStream()).containsKey(iv)) + return; + LOG.trace("Storing object " + iv); try { @@ -188,6 +211,8 @@ public class AndroidInventory implements Inventory { values.put(COLUMN_VERSION, object.getVersion()); db.insertOrThrow(TABLE_NAME, null, values); + + getCache(object.getStream()).put(iv, object.getExpiresTime()); } catch (SQLiteConstraintException e) { LOG.trace(e.getMessage(), e); } catch (IOException e) { @@ -197,22 +222,22 @@ public class AndroidInventory implements Inventory { @Override public boolean contains(ObjectMessage object) { - SQLiteDatabase db = sql.getReadableDatabase(); - Cursor c = db.query( - TABLE_NAME, new String[]{COLUMN_STREAM}, - "hash = X'" + object.getInventoryVector() + "'", - null, null, null, null - ); - try { - return c.getCount() > 0; - } finally { - c.close(); - } + return getCache(object.getStream()).keySet().contains(object.getInventoryVector()); } @Override public void cleanup() { + long fiveMinutesAgo = now() - 5 * MINUTE; SQLiteDatabase db = sql.getWritableDatabase(); - db.delete(TABLE_NAME, "expires < " + (now() - 300), null); + db.delete(TABLE_NAME, "expires < " + fiveMinutesAgo, null); + + for (Map c : cache.values()) { + Iterator> iterator = c.entrySet().iterator(); + while (iterator.hasNext()) { + if (iterator.next().getValue() < fiveMinutesAgo) { + iterator.remove(); + } + } + } } } diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java index b5c18f6..691db2c 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java +++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java @@ -56,7 +56,7 @@ public class BitmessageService extends Service { private static Messenger messenger; public static boolean isRunning() { - return running; + return running && bmc.isRunning(); } @Override @@ -102,8 +102,6 @@ public class BitmessageService extends Service { switch (msg.what) { case MSG_CREATE_IDENTITY: { BitmessageAddress identity = bmc.createIdentity(false); - identity.setAlias(service.get().getString(R.string.alias_default_identity)); - bmc.addresses().save(identity); if (msg.replyTo != null) { try { Message message = Message.obtain(this, MSG_CREATE_IDENTITY); diff --git a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java index 9c60ecf..50c95da 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java +++ b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java @@ -4,6 +4,7 @@ import android.content.Context; import java.util.List; +import ch.dissem.apps.abit.R; import ch.dissem.apps.abit.adapter.AndroidCryptography; import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine; import ch.dissem.apps.abit.listener.MessageListener; @@ -92,10 +93,15 @@ public class Singleton { if (identity == null) { synchronized (Singleton.class) { if (identity == null) { - List identities = getBitmessageContext(ctx).addresses() + BitmessageContext bmc = getBitmessageContext(ctx); + List identities = bmc.addresses() .getIdentities(); if (identities.size() > 0) { identity = identities.get(0); + } else { + identity = bmc.createIdentity(false); + identity.setAlias(ctx.getString(R.string.alias_default_identity)); + bmc.addresses().save(identity); } } } diff --git a/app/src/main/res/drawable/public_key.xml b/app/src/main/res/drawable/public_key.xml new file mode 100644 index 0000000..d3cea1a --- /dev/null +++ b/app/src/main/res/drawable/public_key.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_contact_detail.xml b/app/src/main/res/layout/fragment_contact_detail.xml index 4aa33dd..f8b4bda 100644 --- a/app/src/main/res/layout/fragment_contact_detail.xml +++ b/app/src/main/res/layout/fragment_contact_detail.xml @@ -16,65 +16,90 @@ --> + android:id="@+id/avatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_margin="16dp" + android:src="@color/colorAccent" + tools:ignore="ContentDescription"/> + android:id="@+id/name" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignTop="@+id/avatar" + android:layout_marginEnd="16dp" + android:layout_toEndOf="@+id/avatar" + android:text="" + android:inputType="textPersonName" + tools:ignore="LabelFor"/> + android:id="@+id/address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/avatar" + android:lines="1" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:text="BM-XyYxXyYxXyYxXyYxXyYx" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textStyle="bold" + tools:ignore="HardcodedText"/> + android:id="@+id/stream_number" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/address" + android:ellipsize="end" + android:lines="1" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:text="Stream #" + android:textAppearance="?android:attr/textAppearanceSmall" + tools:ignore="HardcodedText"/> + android:id="@+id/active" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/stream_number" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="16dp" + android:text="@string/subscribed"/> + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 279491a..bc0d062 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -56,4 +56,6 @@ Ich Debugging Technische Infos + Öffentlicher Schlüssel verfügbar + Öffentlicher Schlüssel noch nicht verfügbar \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83c5d60..7a4e21c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,4 +59,6 @@ Debugging Technical information Me + Public key available + Public key not yet available