Server POW should work now
This commit is contained in:
parent
b0828ec1e5
commit
41f4571bf6
@ -29,6 +29,7 @@ dependencies {
|
||||
compile 'ch.dissem.jabit:jabit-domain:0.2.1-SNAPSHOT'
|
||||
compile 'ch.dissem.jabit:jabit-networking:0.2.1-SNAPSHOT'
|
||||
compile 'ch.dissem.jabit:jabit-security-spongy:0.2.1-SNAPSHOT'
|
||||
compile 'ch.dissem.jabit:jabit-extensions:0.2.1-SNAPSHOT'
|
||||
|
||||
compile 'org.slf4j:slf4j-android:1.7.12'
|
||||
|
||||
|
@ -8,6 +8,7 @@ CREATE TABLE Message (
|
||||
sent INTEGER,
|
||||
received INTEGER,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
initial_hash BINARY(64) UNIQUE,
|
||||
|
||||
FOREIGN KEY (sender) REFERENCES Address (address),
|
||||
FOREIGN KEY (recipient) REFERENCES Address (address)
|
||||
|
@ -0,0 +1,7 @@
|
||||
-- This is done in V1.2, as SQLite doesn't support ADD CONSTRAINT and a proper migration
|
||||
-- wasn't really necessary yet.
|
||||
--
|
||||
-- This file is here to reduce confusion regarding to the original migration files.
|
||||
|
||||
--ALTER TABLE Message ADD COLUMN initial_hash BINARY(64);
|
||||
--ALTER TABLE Message ADD CONSTRAINT initial_hash_unique UNIQUE(initial_hash);
|
@ -0,0 +1,7 @@
|
||||
CREATE TABLE POW (
|
||||
initial_hash BINARY(64) PRIMARY KEY,
|
||||
data BLOB NOT NULL,
|
||||
version BIGINT NOT NULL,
|
||||
nonce_trials_per_byte BIGINT NOT NULL,
|
||||
extra_bytes BIGINT NOT NULL
|
||||
);
|
@ -50,6 +50,8 @@ import ch.dissem.apps.abit.listener.ListSelectionListener;
|
||||
import ch.dissem.apps.abit.service.BitmessageService;
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.apps.abit.synchronization.Authenticator;
|
||||
import ch.dissem.apps.abit.synchronization.SyncAdapter;
|
||||
import ch.dissem.apps.abit.util.Preferences;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
@ -85,7 +87,6 @@ public class MainActivity extends AppCompatActivity
|
||||
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
|
||||
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
|
||||
private static final int ADD_IDENTITY = 1;
|
||||
|
||||
/**
|
||||
@ -134,7 +135,8 @@ public class MainActivity extends AppCompatActivity
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
MessageListFragment listFragment = new MessageListFragment();
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment).commit();
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment)
|
||||
.commit();
|
||||
|
||||
if (findViewById(R.id.message_detail_container) != null) {
|
||||
// The detail container view will be present only in the
|
||||
@ -157,21 +159,10 @@ public class MainActivity extends AppCompatActivity
|
||||
onItemSelected(getIntent().getSerializableExtra(EXTRA_SHOW_MESSAGE));
|
||||
}
|
||||
|
||||
createSyncAccount();
|
||||
}
|
||||
|
||||
private void createSyncAccount() {
|
||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||
Account account = new Account(Authenticator.ACCOUNT_NAME, Authenticator.ACCOUNT_TYPE);
|
||||
|
||||
if (AccountManager.get(this).addAccountExplicitly(account, null, null)) {
|
||||
// Inform the system that this account supports sync
|
||||
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
|
||||
// Inform the system that this account is eligible for auto sync when the network is up
|
||||
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
|
||||
// Recommend a schedule for automatic synchronization. The system may modify this based
|
||||
// on other scheduled syncs and network utilization.
|
||||
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
|
||||
if (Preferences.useTrustedNode(this)) {
|
||||
SyncAdapter.startSync(this);
|
||||
} else {
|
||||
SyncAdapter.stopSync(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,10 +211,12 @@ public class MainActivity extends AppCompatActivity
|
||||
.withProfiles(profiles)
|
||||
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
|
||||
@Override
|
||||
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
|
||||
public boolean onProfileChanged(View view, IProfile profile, boolean
|
||||
currentProfile) {
|
||||
if (profile.getIdentifier() == ADD_IDENTITY) {
|
||||
try {
|
||||
Message message = Message.obtain(null, BitmessageService.MSG_CREATE_IDENTITY);
|
||||
Message message = Message.obtain(null, BitmessageService
|
||||
.MSG_CREATE_IDENTITY);
|
||||
message.replyTo = messenger;
|
||||
service.send(message);
|
||||
} catch (RemoteException e) {
|
||||
@ -240,14 +233,15 @@ public class MainActivity extends AppCompatActivity
|
||||
}
|
||||
})
|
||||
.build();
|
||||
if (profiles.size() > 0) {
|
||||
if (profiles.size() > 2) { // There's always the add and manage identity items
|
||||
accountHeader.setActiveProfile(profiles.get(0), true);
|
||||
}
|
||||
incomingHandler.updateAccountHeader(accountHeader);
|
||||
|
||||
ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
|
||||
for (Label label : labels) {
|
||||
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag(label);
|
||||
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag
|
||||
(label);
|
||||
if (label.getType() == null) {
|
||||
item.withIcon(CommunityMaterial.Icon.cmd_label);
|
||||
} else {
|
||||
@ -301,17 +295,21 @@ public class MainActivity extends AppCompatActivity
|
||||
.withChecked(BitmessageService.isRunning())
|
||||
.withOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) {
|
||||
public void onCheckedChanged(IDrawerItem drawerItem,
|
||||
CompoundButton buttonView,
|
||||
boolean isChecked) {
|
||||
if (messenger != null) {
|
||||
if (isChecked) {
|
||||
try {
|
||||
service.send(Message.obtain(null, MSG_START_NODE));
|
||||
service.send(Message.obtain(null,
|
||||
MSG_START_NODE));
|
||||
} catch (RemoteException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
service.send(Message.obtain(null, MSG_STOP_NODE));
|
||||
service.send(Message.obtain(null,
|
||||
MSG_STOP_NODE));
|
||||
} catch (RemoteException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
@ -322,7 +320,8 @@ public class MainActivity extends AppCompatActivity
|
||||
)
|
||||
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
|
||||
@Override
|
||||
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long l, IDrawerItem item) {
|
||||
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long
|
||||
l, IDrawerItem item) {
|
||||
if (item.getTag() instanceof Label) {
|
||||
selectedLabel = (Label) item.getTag();
|
||||
showSelectedLabel();
|
||||
@ -331,15 +330,18 @@ public class MainActivity extends AppCompatActivity
|
||||
Nameable<?> ni = (Nameable<?>) item;
|
||||
switch (ni.getNameRes()) {
|
||||
case R.string.contacts_and_subscriptions:
|
||||
if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof SubscriptionListFragment)) {
|
||||
if (!(getSupportFragmentManager().findFragmentById(R.id
|
||||
.item_list) instanceof SubscriptionListFragment)) {
|
||||
changeList(new SubscriptionListFragment());
|
||||
} else {
|
||||
((SubscriptionListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.item_list)).updateList();
|
||||
}
|
||||
|
||||
break;
|
||||
case R.string.settings:
|
||||
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
|
||||
startActivity(new Intent(MainActivity.this, SettingsActivity
|
||||
.class));
|
||||
break;
|
||||
case R.string.archive:
|
||||
selectedLabel = null;
|
||||
@ -357,7 +359,8 @@ public class MainActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
private void showSelectedLabel() {
|
||||
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment) {
|
||||
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
|
||||
MessageListFragment) {
|
||||
((MessageListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
|
||||
} else {
|
||||
@ -385,7 +388,8 @@ public class MainActivity extends AppCompatActivity
|
||||
else if (item instanceof BitmessageAddress)
|
||||
fragment = new SubscriptionDetailFragment();
|
||||
else
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
|
||||
"was "
|
||||
+ item.getClass().getSimpleName());
|
||||
fragment.setArguments(arguments);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
@ -400,7 +404,8 @@ public class MainActivity extends AppCompatActivity
|
||||
else if (item instanceof BitmessageAddress)
|
||||
detailIntent = new Intent(this, SubscriptionDetailActivity.class);
|
||||
else
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was "
|
||||
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
|
||||
"was "
|
||||
+ item.getClass().getSimpleName());
|
||||
|
||||
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
|
||||
@ -421,7 +426,8 @@ public class MainActivity extends AppCompatActivity
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
|
||||
bindService(new Intent(this, BitmessageService.class), connection, Context
|
||||
.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -463,8 +469,10 @@ public class MainActivity extends AppCompatActivity
|
||||
.withEmail(identity.getAddress())
|
||||
.withTag(identity);
|
||||
if (accountHeader.getProfiles() != null) {
|
||||
//we know that there are 2 setting elements. set the new profile above them ;)
|
||||
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2);
|
||||
//we know that there are 2 setting elements. set the new profile
|
||||
// above them ;)
|
||||
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size
|
||||
() - 2);
|
||||
} else {
|
||||
accountHeader.addProfiles(newProfile);
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS;
|
||||
import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT;
|
||||
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE;
|
||||
import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT;
|
||||
|
||||
public class OpenBitmessageLinkActivity extends AppCompatActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class);
|
||||
@ -106,7 +105,7 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
|
||||
|
||||
final int what;
|
||||
if (subscribe.isChecked())
|
||||
what = MSG_SUBSCRIBE_AND_ADD_CONTACT;
|
||||
what = MSG_SUBSCRIBE;
|
||||
else
|
||||
what = MSG_ADD_CONTACT;
|
||||
|
||||
@ -155,7 +154,8 @@ public class OpenBitmessageLinkActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
bindService(new Intent(this, BitmessageService.class), connection, Context.BIND_AUTO_CREATE);
|
||||
bindService(new Intent(this, BitmessageService.class), connection, Context
|
||||
.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,7 +5,7 @@ import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
|
||||
/**
|
||||
* Created by chris on 14.07.15.
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
@Override
|
||||
|
@ -1,13 +1,22 @@
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import ch.dissem.apps.abit.synchronization.SyncAdapter;
|
||||
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
|
||||
|
||||
/**
|
||||
* Created by chris on 14.07.15.
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class SettingsFragment extends PreferenceFragment {
|
||||
public class SettingsFragment
|
||||
extends PreferenceFragment
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -15,4 +24,32 @@ public class SettingsFragment extends PreferenceFragment {
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context ctx) {
|
||||
super.onAttach(ctx);
|
||||
PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
switch (key) {
|
||||
case PREFERENCE_TRUSTED_NODE:
|
||||
String node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null);
|
||||
if (node != null) {
|
||||
SyncAdapter.startSync(getActivity());
|
||||
} else {
|
||||
SyncAdapter.stopSync(getActivity());
|
||||
}
|
||||
break;
|
||||
case PREFERENCE_SERVER_POW:
|
||||
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
|
||||
SyncAdapter.startPowSync(getActivity());
|
||||
} else {
|
||||
SyncAdapter.stopPowSync(getActivity());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
@ -25,6 +26,7 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import ch.dissem.apps.abit.listener.ActionBarListener;
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
@ -100,12 +102,18 @@ public class SubscriptionListFragment extends AbstractItemListFragment<Bitmessag
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context ctx) {
|
||||
super.onAttach(ctx);
|
||||
if (ctx instanceof ActionBarListener){
|
||||
((ActionBarListener) ctx).updateTitle(getString(R.string.contacts_and_subscriptions));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_contact_list, container, false);
|
||||
|
||||
return rootView;
|
||||
return inflater.inflate(R.layout.fragment_contact_list, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -28,57 +28,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class AndroidSecurity extends SpongySecurity {
|
||||
private final SharedPreferences preferences;
|
||||
|
||||
public AndroidSecurity(Context ctx) {
|
||||
public AndroidSecurity() {
|
||||
PRNGFixes.apply();
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, ProofOfWorkEngine.Callback callback) {
|
||||
if (preferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
|
||||
object.setNonce(new byte[8]);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
object.write(out);
|
||||
sendAsBroadcast(getContext().getAddressRepo().getIdentities().get(0), out.toByteArray());
|
||||
if (object.getPayload() instanceof PlaintextHolder) {
|
||||
Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
|
||||
plaintext.setInventoryVector(object.getInventoryVector());
|
||||
plaintext.setStatus(SENT);
|
||||
plaintext.removeLabel(Label.Type.OUTBOX);
|
||||
plaintext.addLabels(getContext().getMessageRepository().getLabels(Label.Type.SENT));
|
||||
getContext().getMessageRepository().save(plaintext);
|
||||
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
super.doProofOfWork(object, nonceTrialsPerByte, extraBytes, callback);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAsBroadcast(BitmessageAddress identity, byte[] data) throws IOException {
|
||||
Plaintext msg = new Plaintext.Builder(BROADCAST)
|
||||
.from(identity)
|
||||
.message(data)
|
||||
.build();
|
||||
Broadcast payload = Factory.getBroadcast(identity, msg);
|
||||
long expires = UnixTime.now(+2 * DAY);
|
||||
final ObjectMessage object = new ObjectMessage.Builder()
|
||||
.stream(identity.getStream())
|
||||
.expiresTime(expires)
|
||||
.payload(payload)
|
||||
.build();
|
||||
object.sign(identity.getPrivateKey());
|
||||
payload.encrypt();
|
||||
object.setNonce(new byte[8]);
|
||||
|
||||
getContext().getInventory().storeObject(object);
|
||||
getContext().getNetworkHandler().offer(object.getInventoryVector());
|
||||
// TODO: offer to the trusted node only?
|
||||
// at least make sure it is offered to the trusted node!
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
package ch.dissem.apps.abit.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import ch.dissem.apps.abit.util.Preferences;
|
||||
import ch.dissem.bitmessage.InternalContext;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
||||
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
|
||||
|
||||
/**
|
||||
* Switches between two {@link ProofOfWorkEngine}s depending on the configuration.
|
||||
*
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class SwitchingProofOfWorkEngine implements ProofOfWorkEngine, InternalContext.ContextHolder {
|
||||
private final Context ctx;
|
||||
private final String preference;
|
||||
private final ProofOfWorkEngine option;
|
||||
private final ProofOfWorkEngine fallback;
|
||||
|
||||
public SwitchingProofOfWorkEngine(Context ctx, String preference,
|
||||
ProofOfWorkEngine option, ProofOfWorkEngine fallback) {
|
||||
this.ctx = ctx;
|
||||
this.preference = preference;
|
||||
this.option = option;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
if (preferences.getBoolean(preference, false)) {
|
||||
option.calculateNonce(initialHash, target, callback);
|
||||
} else {
|
||||
fallback.calculateNonce(initialHash, target, callback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(InternalContext context) {
|
||||
for (ProofOfWorkEngine e : Arrays.asList(option, fallback)) {
|
||||
if (e instanceof InternalContext.ContextHolder) {
|
||||
((InternalContext.ContextHolder) e).setContext(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package ch.dissem.apps.abit.pow;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.apps.abit.synchronization.SyncAdapter;
|
||||
import ch.dissem.apps.abit.util.Preferences;
|
||||
import ch.dissem.bitmessage.InternalContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
|
||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
||||
|
||||
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class ServerPowEngine implements ProofOfWorkEngine, InternalContext
|
||||
.ContextHolder {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServerPowEngine.class);
|
||||
|
||||
private final Context ctx;
|
||||
private InternalContext context;
|
||||
|
||||
private final ExecutorService pool;
|
||||
|
||||
public ServerPowEngine(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
pool = Executors.newCachedThreadPool(new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable r) {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
return thread;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculateNonce(final byte[] initialHash, final byte[] target, Callback callback) {
|
||||
pool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
BitmessageAddress identity = Singleton.getIdentity(ctx);
|
||||
if (identity == null) throw new RuntimeException("No Identity for calculating POW");
|
||||
|
||||
ProofOfWorkRequest request = new ProofOfWorkRequest(identity, initialHash,
|
||||
CALCULATE, target);
|
||||
SyncAdapter.startPowSync(ctx);
|
||||
try {
|
||||
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>
|
||||
(request);
|
||||
cryptoMsg.signAndEncrypt(
|
||||
identity,
|
||||
security().createPublicKey(identity.getPublicDecryptionKey())
|
||||
);
|
||||
context.getNetworkHandler().send(
|
||||
Preferences.getTrustedNode(ctx), Preferences.getTrustedNodePort(ctx),
|
||||
cryptoMsg);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(InternalContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repository;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
|
||||
@ -27,6 +28,7 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -118,27 +120,30 @@ public class AndroidAddressRepository implements AddressRepository {
|
||||
COLUMN_SUBSCRIBED
|
||||
};
|
||||
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null, null
|
||||
);
|
||||
try {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
BitmessageAddress address;
|
||||
|
||||
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
|
||||
if (privateKeyBytes != null) {
|
||||
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream(privateKeyBytes));
|
||||
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
|
||||
(privateKeyBytes));
|
||||
address = new BitmessageAddress(privateKey);
|
||||
} else {
|
||||
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
|
||||
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
|
||||
if (publicKeyBytes != null) {
|
||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(),
|
||||
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, false);
|
||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
|
||||
.getStream(),
|
||||
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
|
||||
false);
|
||||
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
|
||||
pubkey = new V4Pubkey((V3Pubkey) pubkey);
|
||||
}
|
||||
@ -153,8 +158,9 @@ public class AndroidAddressRepository implements AddressRepository {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -173,9 +179,14 @@ public class AndroidAddressRepository implements AddressRepository {
|
||||
|
||||
private boolean exists(BitmessageAddress address) {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address.getAddress() + "'", null);
|
||||
cursor.moveToFirst();
|
||||
return cursor.getInt(0) > 0;
|
||||
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address
|
||||
.getAddress() + "'", null);
|
||||
try {
|
||||
cursor.moveToFirst();
|
||||
return cursor.getInt(0) > 0;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void update(BitmessageAddress address) throws IOException {
|
||||
@ -194,7 +205,8 @@ public class AndroidAddressRepository implements AddressRepository {
|
||||
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
|
||||
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
|
||||
|
||||
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() + "'", null);
|
||||
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() +
|
||||
"'", null);
|
||||
if (update < 0) {
|
||||
LOG.error("Could not update address " + address);
|
||||
}
|
||||
|
@ -74,15 +74,20 @@ public class AndroidInventory implements Inventory {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join(streams) + ")",
|
||||
(includeExpired ? "" : "expires > " + now() + " AND ") + "stream IN (" + join
|
||||
(streams) + ")",
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
List<InventoryVector> result = new LinkedList<>();
|
||||
while (!c.isAfterLast()) {
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
|
||||
result.add(new InventoryVector(blob));
|
||||
c.moveToNext();
|
||||
try {
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
|
||||
result.add(new InventoryVector(blob));
|
||||
c.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -108,15 +113,19 @@ public class AndroidInventory implements Inventory {
|
||||
"hash = X'" + vector + "'",
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
if (c.isAfterLast()) {
|
||||
LOG.info("Object requested that we don't have. IV: " + vector);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
c.moveToFirst();
|
||||
if (c.isAfterLast()) {
|
||||
LOG.info("Object requested that we don't have. IV: " + vector);
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -144,13 +153,18 @@ public class AndroidInventory implements Inventory {
|
||||
where.toString(),
|
||||
null, null, null, null
|
||||
);
|
||||
c.moveToFirst();
|
||||
List<ObjectMessage> result = new LinkedList<>();
|
||||
while (!c.isAfterLast()) {
|
||||
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), blob.length));
|
||||
c.moveToNext();
|
||||
try {
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
|
||||
blob.length));
|
||||
c.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -187,7 +201,11 @@ public class AndroidInventory implements Inventory {
|
||||
"hash = X'" + object.getInventoryVector() + "'",
|
||||
null, null, null, null
|
||||
);
|
||||
return c.getCount() > 0;
|
||||
try {
|
||||
return c.getCount() > 0;
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -30,6 +30,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.Strings;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -58,6 +59,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
private static final String COLUMN_SENT = "sent";
|
||||
private static final String COLUMN_RECEIVED = "received";
|
||||
private static final String COLUMN_STATUS = "status";
|
||||
private static final String COLUMN_INITIAL_HASH = "initial_hash";
|
||||
|
||||
private static final String JOIN_TABLE_NAME = "Message_Label";
|
||||
private static final String JT_COLUMN_MESSAGE = "message_id";
|
||||
@ -112,10 +114,14 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
null, null, null,
|
||||
LBL_COLUMN_ORDER
|
||||
);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
result.add(getLabel(c));
|
||||
c.moveToNext();
|
||||
try {
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
result.add(getLabel(c));
|
||||
c.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -124,27 +130,31 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE));
|
||||
Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName);
|
||||
String text;
|
||||
switch (type) {
|
||||
case INBOX:
|
||||
text = ctx.getString(R.string.inbox);
|
||||
break;
|
||||
case DRAFT:
|
||||
text = ctx.getString(R.string.draft);
|
||||
break;
|
||||
case SENT:
|
||||
text = ctx.getString(R.string.sent);
|
||||
break;
|
||||
case UNREAD:
|
||||
text = ctx.getString(R.string.unread);
|
||||
break;
|
||||
case TRASH:
|
||||
text = ctx.getString(R.string.trash);
|
||||
break;
|
||||
case BROADCAST:
|
||||
text = ctx.getString(R.string.broadcasts);
|
||||
break;
|
||||
default:
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
if (type == null) {
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
} else {
|
||||
switch (type) {
|
||||
case INBOX:
|
||||
text = ctx.getString(R.string.inbox);
|
||||
break;
|
||||
case DRAFT:
|
||||
text = ctx.getString(R.string.draft);
|
||||
break;
|
||||
case SENT:
|
||||
text = ctx.getString(R.string.sent);
|
||||
break;
|
||||
case UNREAD:
|
||||
text = ctx.getString(R.string.unread);
|
||||
break;
|
||||
case TRASH:
|
||||
text = ctx.getString(R.string.trash);
|
||||
break;
|
||||
case BROADCAST:
|
||||
text = ctx.getString(R.string.broadcasts);
|
||||
break;
|
||||
default:
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
}
|
||||
}
|
||||
Label label = new Label(
|
||||
text,
|
||||
@ -158,7 +168,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
public int countUnread(Label label) {
|
||||
String where;
|
||||
if (label != null) {
|
||||
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND ";
|
||||
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId()
|
||||
+ ") AND ";
|
||||
} else {
|
||||
where = "";
|
||||
}
|
||||
@ -169,13 +180,32 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))",
|
||||
null, null, null, null
|
||||
);
|
||||
return c.getColumnCount();
|
||||
try {
|
||||
return c.getColumnCount();
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plaintext getMessage(byte[] initialHash) {
|
||||
List<Plaintext> results = find("initial_hash=X'" + Strings.hex(initialHash) + "'");
|
||||
switch (results.size()) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return results.get(0);
|
||||
default:
|
||||
throw new RuntimeException("This shouldn't happen, found " + results.size() +
|
||||
" messages, one or none was expected");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Plaintext> findMessages(Label label) {
|
||||
if (label != null) {
|
||||
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
|
||||
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label
|
||||
.getId() + ")");
|
||||
} else {
|
||||
return find("id NOT IN (SELECT message_id FROM Message_Label)");
|
||||
}
|
||||
@ -183,7 +213,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
|
||||
@Override
|
||||
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) {
|
||||
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
|
||||
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() +
|
||||
"'");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -213,34 +244,41 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
COLUMN_STATUS
|
||||
};
|
||||
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null,
|
||||
COLUMN_RECEIVED + " DESC"
|
||||
);
|
||||
try {
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null,
|
||||
COLUMN_RECEIVED + " DESC"
|
||||
);
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));
|
||||
byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex(COLUMN_TYPE)));
|
||||
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new ByteArrayInputStream(data));
|
||||
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex
|
||||
(COLUMN_TYPE)));
|
||||
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new
|
||||
ByteArrayInputStream(data));
|
||||
long id = c.getLong(c.getColumnIndex(COLUMN_ID));
|
||||
builder.id(id);
|
||||
builder.IV(new InventoryVector(iv));
|
||||
builder.from(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_SENDER))));
|
||||
builder.to(bmc.getAddressRepo().getAddress(c.getString(c.getColumnIndex(COLUMN_RECIPIENT))));
|
||||
builder.from(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
|
||||
(COLUMN_SENDER))));
|
||||
builder.to(bmc.getAddressRepository().getAddress(c.getString(c.getColumnIndex
|
||||
(COLUMN_RECIPIENT))));
|
||||
builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
|
||||
builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED)));
|
||||
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex(COLUMN_STATUS))));
|
||||
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex
|
||||
(COLUMN_STATUS))));
|
||||
builder.labels(findLabels(id));
|
||||
result.add(builder.build());
|
||||
c.moveToNext();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -257,12 +295,13 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
|
||||
// save from address if necessary
|
||||
if (message.getId() == null) {
|
||||
BitmessageAddress savedAddress = bmc.getAddressRepo().getAddress(message.getFrom().getAddress());
|
||||
BitmessageAddress savedAddress = bmc.getAddressRepository().getAddress(message
|
||||
.getFrom().getAddress());
|
||||
if (savedAddress == null || savedAddress.getPrivateKey() == null) {
|
||||
if (savedAddress != null && savedAddress.getAlias() != null) {
|
||||
message.getFrom().setAlias(savedAddress.getAlias());
|
||||
}
|
||||
bmc.getAddressRepo().save(message.getFrom());
|
||||
bmc.getAddressRepository().save(message.getFrom());
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +334,8 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
|
||||
private void insert(SQLiteDatabase db, Plaintext message) throws IOException {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
|
||||
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
|
||||
.getInventoryVector().getHash());
|
||||
values.put(COLUMN_TYPE, message.getType().name());
|
||||
values.put(COLUMN_SENDER, message.getFrom().getAddress());
|
||||
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
|
||||
@ -303,13 +343,15 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
values.put(COLUMN_SENT, message.getSent());
|
||||
values.put(COLUMN_RECEIVED, message.getReceived());
|
||||
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
|
||||
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
|
||||
long id = db.insertOrThrow(TABLE_NAME, null, values);
|
||||
message.setId(id);
|
||||
}
|
||||
|
||||
private void update(SQLiteDatabase db, Plaintext message) throws IOException {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash());
|
||||
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
|
||||
.getInventoryVector().getHash());
|
||||
values.put(COLUMN_TYPE, message.getType().name());
|
||||
values.put(COLUMN_SENDER, message.getFrom().getAddress());
|
||||
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
|
||||
@ -317,6 +359,7 @@ public class AndroidMessageRepository implements MessageRepository, InternalCont
|
||||
values.put(COLUMN_SENT, message.getSent());
|
||||
values.put(COLUMN_RECEIVED, message.getReceived());
|
||||
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
|
||||
values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
|
||||
db.update(TABLE_NAME, values, "id = " + message.getId(), null);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,133 @@
|
||||
package ch.dissem.apps.abit.repository;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.Strings;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
public class AndroidProofOfWorkRepository implements ProofOfWorkRepository {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class);
|
||||
|
||||
private static final String TABLE_NAME = "POW";
|
||||
private static final String COLUMN_INITIAL_HASH = "initial_hash";
|
||||
private static final String COLUMN_DATA = "data";
|
||||
private static final String COLUMN_VERSION = "version";
|
||||
private static final String COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte";
|
||||
private static final String COLUMN_EXTRA_BYTES = "extra_bytes";
|
||||
|
||||
private final SqlHelper sql;
|
||||
|
||||
public AndroidProofOfWorkRepository(SqlHelper sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(byte[] initialHash) {
|
||||
// Define a projection that specifies which columns from the database
|
||||
// you will actually use after this query.
|
||||
String[] projection = {
|
||||
COLUMN_DATA,
|
||||
COLUMN_VERSION,
|
||||
COLUMN_NONCE_TRIALS_PER_BYTE,
|
||||
COLUMN_EXTRA_BYTES
|
||||
};
|
||||
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
"initial_hash = X'" + Strings.hex(initialHash) + "'",
|
||||
null, null, null, null
|
||||
);
|
||||
try {
|
||||
c.moveToFirst();
|
||||
if (!c.isAfterLast()) {
|
||||
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
||||
return new Item(
|
||||
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
|
||||
.length),
|
||||
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
|
||||
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
throw new RuntimeException("Object requested that we don't have. Initial hash: " +
|
||||
Strings.hex(initialHash));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<byte[]> getItems() {
|
||||
// Define a projection that specifies which columns from the database
|
||||
// you will actually use after this query.
|
||||
String[] projection = {
|
||||
COLUMN_INITIAL_HASH
|
||||
};
|
||||
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
null, null, null, null, null
|
||||
);
|
||||
List<byte[]> result = new LinkedList<>();
|
||||
try {
|
||||
c.moveToFirst();
|
||||
while (!c.isAfterLast()) {
|
||||
byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH));
|
||||
result.add(initialHash);
|
||||
c.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
||||
try {
|
||||
SQLiteDatabase db = sql.getWritableDatabase();
|
||||
// Create a new map of values, where column names are the keys
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_INITIAL_HASH, security().getInitialHash(object));
|
||||
values.put(COLUMN_DATA, Encode.bytes(object));
|
||||
values.put(COLUMN_VERSION, object.getVersion());
|
||||
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, nonceTrialsPerByte);
|
||||
values.put(COLUMN_EXTRA_BYTES, extraBytes);
|
||||
|
||||
db.insertOrThrow(TABLE_NAME, null, values);
|
||||
} catch (SQLiteConstraintException e) {
|
||||
LOG.trace(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObject(byte[] initialHash) {
|
||||
SQLiteDatabase db = sql.getWritableDatabase();
|
||||
db.delete(TABLE_NAME,
|
||||
"initial_hash = X'" + Strings.hex(initialHash) + "'",
|
||||
null);
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ import ch.dissem.apps.abit.util.Assets;
|
||||
*/
|
||||
public class SqlHelper extends SQLiteOpenHelper {
|
||||
// If you change the database schema, you must increment the database version.
|
||||
public static final int DATABASE_VERSION = 1;
|
||||
public static final int DATABASE_VERSION = 2;
|
||||
public static final String DATABASE_NAME = "jabit.db";
|
||||
|
||||
protected final Context ctx;
|
||||
@ -38,7 +38,7 @@ public class SqlHelper extends SQLiteOpenHelper {
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
onUpgrade(db, 0, 1);
|
||||
onUpgrade(db, 0, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,6 +48,9 @@ public class SqlHelper extends SQLiteOpenHelper {
|
||||
executeMigration(db, "V1.0__Create_table_inventory");
|
||||
executeMigration(db, "V1.1__Create_table_address");
|
||||
executeMigration(db, "V1.2__Create_table_message");
|
||||
case 1:
|
||||
// executeMigration(db, "V2.0__Update_table_message");
|
||||
executeMigration(db, "V2.1__Create_table_POW");
|
||||
default:
|
||||
// Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION.
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ public class BitmessageService extends Service {
|
||||
public static final int MSG_CREATE_IDENTITY = 10;
|
||||
public static final int MSG_SUBSCRIBE = 20;
|
||||
public static final int MSG_ADD_CONTACT = 21;
|
||||
public static final int MSG_SUBSCRIBE_AND_ADD_CONTACT = 23;
|
||||
public static final int MSG_SEND_MESSAGE = 30;
|
||||
public static final int MSG_SEND_BROADCAST = 31;
|
||||
public static final int MSG_START_NODE = 100;
|
||||
@ -122,6 +121,13 @@ public class BitmessageService extends Service {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MSG_ADD_CONTACT: {
|
||||
Serializable data = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
|
||||
if (data instanceof BitmessageAddress) {
|
||||
bmc.addContact((BitmessageAddress) data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MSG_SEND_MESSAGE: {
|
||||
Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY);
|
||||
Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS);
|
||||
|
@ -73,9 +73,9 @@ public class ProofOfWorkService extends Service {
|
||||
service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification());
|
||||
engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() {
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] nonce) {
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
||||
try {
|
||||
callback.onNonceCalculated(nonce);
|
||||
callback.onNonceCalculated(initialHash, nonce);
|
||||
} finally {
|
||||
service.stopForeground(true);
|
||||
service.stopSelf();
|
||||
|
@ -53,8 +53,8 @@ public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Ca
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNonceCalculated(byte[] bytes) {
|
||||
callback.onNonceCalculated(bytes);
|
||||
public void onNonceCalculated(byte[] initialHash, byte[] bytes) {
|
||||
callback.onNonceCalculated(initialHash, bytes);
|
||||
ctx.unbindService(connection);
|
||||
}
|
||||
|
||||
|
@ -2,18 +2,27 @@ package ch.dissem.apps.abit.service;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ch.dissem.apps.abit.adapter.AndroidSecurity;
|
||||
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine;
|
||||
import ch.dissem.apps.abit.listener.MessageListener;
|
||||
import ch.dissem.apps.abit.pow.ServerPowEngine;
|
||||
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
|
||||
import ch.dissem.apps.abit.repository.AndroidInventory;
|
||||
import ch.dissem.apps.abit.repository.AndroidMessageRepository;
|
||||
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository;
|
||||
import ch.dissem.apps.abit.repository.SqlHelper;
|
||||
import ch.dissem.apps.abit.util.Constants;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
|
||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
||||
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
import ch.dissem.bitmessage.security.sc.SpongySecurity;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
|
||||
/**
|
||||
* Provides singleton objects across the application.
|
||||
@ -22,6 +31,8 @@ public class Singleton {
|
||||
public static final Object lock = new Object();
|
||||
private static BitmessageContext bitmessageContext;
|
||||
private static MessageListener messageListener;
|
||||
private static BitmessageAddress identity;
|
||||
private static AndroidProofOfWorkRepository powRepo;
|
||||
|
||||
public static BitmessageContext getBitmessageContext(Context context) {
|
||||
if (bitmessageContext == null) {
|
||||
@ -29,15 +40,23 @@ public class Singleton {
|
||||
if (bitmessageContext == null) {
|
||||
final Context ctx = context.getApplicationContext();
|
||||
SqlHelper sqlHelper = new SqlHelper(ctx);
|
||||
powRepo = new AndroidProofOfWorkRepository(sqlHelper);
|
||||
bitmessageContext = new BitmessageContext.Builder()
|
||||
.proofOfWorkEngine(new ServicePowEngine(ctx))
|
||||
.security(new AndroidSecurity(ctx))
|
||||
.proofOfWorkEngine(new SwitchingProofOfWorkEngine(
|
||||
ctx, Constants.PREFERENCE_SERVER_POW,
|
||||
new ServerPowEngine(ctx),
|
||||
new ServicePowEngine(ctx)
|
||||
))
|
||||
.security(new AndroidSecurity())
|
||||
.nodeRegistry(new MemoryNodeRegistry())
|
||||
.inventory(new AndroidInventory(sqlHelper))
|
||||
.addressRepo(new AndroidAddressRepository(sqlHelper))
|
||||
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
|
||||
.powRepo(powRepo)
|
||||
.networkHandler(new DefaultNetworkHandler())
|
||||
.listener(getMessageListener(ctx))
|
||||
.doNotSendPubkeyOnIdentityCreation()
|
||||
.pubkeyTTL(2 * DAY)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -63,4 +82,24 @@ public class Singleton {
|
||||
public static AddressRepository getAddressRepository(Context ctx) {
|
||||
return getBitmessageContext(ctx).addresses();
|
||||
}
|
||||
|
||||
public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) {
|
||||
if (powRepo == null) getBitmessageContext(ctx);
|
||||
return powRepo;
|
||||
}
|
||||
|
||||
public static BitmessageAddress getIdentity(Context ctx) {
|
||||
if (identity == null) {
|
||||
synchronized (Singleton.class) {
|
||||
if (identity == null) {
|
||||
List<BitmessageAddress> identities = getBitmessageContext(ctx).addresses()
|
||||
.getIdentities();
|
||||
if (identities.size() > 0) {
|
||||
identity = identities.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import android.os.Bundle;
|
||||
* of its methods
|
||||
*/
|
||||
public class Authenticator extends AbstractAccountAuthenticator {
|
||||
public static final String ACCOUNT_NAME = "Bitmessage";
|
||||
public static final String ACCOUNT_TYPE = "ch.dissem.bitmessage";
|
||||
public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage");
|
||||
public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage");
|
||||
|
||||
// Simple constructor
|
||||
public Authenticator(Context context) {
|
||||
|
@ -1,27 +1,34 @@
|
||||
package ch.dissem.apps.abit.synchronization;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
|
||||
import ch.dissem.apps.abit.R;
|
||||
import ch.dissem.apps.abit.notification.ErrorNotification;
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.apps.abit.util.Preferences;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
|
||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
||||
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT;
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
|
||||
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_POW;
|
||||
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_SYNC;
|
||||
import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
|
||||
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
|
||||
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Sync Adapter to synchronize with the Bitmessage network - fetches
|
||||
@ -30,6 +37,8 @@ import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
|
||||
public class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
|
||||
|
||||
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
|
||||
|
||||
private final BitmessageContext bmc;
|
||||
|
||||
/**
|
||||
@ -41,7 +50,17 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
public void onPerformSync(Account account, Bundle extras, String authority,
|
||||
ContentProviderClient provider, SyncResult syncResult) {
|
||||
if (account.equals(Authenticator.ACCOUNT_SYNC))
|
||||
syncData();
|
||||
else if (account.equals(Authenticator.ACCOUNT_POW))
|
||||
syncPOW();
|
||||
else
|
||||
throw new RuntimeException("Unknown " + account);
|
||||
}
|
||||
|
||||
private void syncData() {
|
||||
// If the Bitmessage context acts as a full node, synchronization isn't necessary
|
||||
if (bmc.isRunning()) {
|
||||
LOG.info("Synchronization skipped, Abit is acting as a full node");
|
||||
@ -49,40 +68,103 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
LOG.info("Synchronizing Bitmessage");
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
|
||||
String trustedNode = preferences.getString(PREFERENCE_TRUSTED_NODE, null);
|
||||
if (trustedNode == null) return;
|
||||
trustedNode = trustedNode.trim();
|
||||
if (trustedNode.isEmpty()) return;
|
||||
|
||||
int port;
|
||||
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
|
||||
int index = trustedNode.lastIndexOf(':');
|
||||
String portString = trustedNode.substring(index + 1);
|
||||
trustedNode = trustedNode.substring(0, index);
|
||||
try {
|
||||
port = Integer.parseInt(portString);
|
||||
} catch (NumberFormatException e) {
|
||||
new ErrorNotification(getContext())
|
||||
.setError(R.string.error_invalid_sync_port, portString)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
port = 8444;
|
||||
}
|
||||
long timeoutInSeconds = Long.parseLong(preferences.getString(PREFERENCE_SYNC_TIMEOUT, "120"));
|
||||
try {
|
||||
LOG.info("Synchronization started");
|
||||
bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true);
|
||||
bmc.synchronize(
|
||||
Preferences.getTrustedNode(getContext()),
|
||||
Preferences.getTrustedNodePort(getContext()),
|
||||
Preferences.getTimeoutInSeconds(getContext()),
|
||||
true);
|
||||
LOG.info("Synchronization finished");
|
||||
} catch (UnknownHostException e) {
|
||||
new ErrorNotification(getContext())
|
||||
.setError(R.string.error_invalid_sync_host)
|
||||
.show();
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void syncPOW() {
|
||||
// If the Bitmessage context acts as a full node, synchronization isn't necessary
|
||||
LOG.info("Looking for completed POW");
|
||||
|
||||
try {
|
||||
BitmessageAddress identity = Singleton.getIdentity(getContext());
|
||||
byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey();
|
||||
byte[] signingKey = security().createPublicKey(identity.getPublicDecryptionKey());
|
||||
ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity);
|
||||
ProofOfWorkRepository powRepo = Singleton.getProofOfWorkRepository(getContext());
|
||||
List<byte[]> items = powRepo.getItems();
|
||||
for (byte[] initialHash : items) {
|
||||
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
|
||||
byte[] target = security().getProofOfWorkTarget(item.object, item
|
||||
.nonceTrialsPerByte, item.extraBytes);
|
||||
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>(
|
||||
new ProofOfWorkRequest(identity, initialHash, CALCULATE, target));
|
||||
cryptoMsg.signAndEncrypt(identity, signingKey);
|
||||
CustomMessage response = bmc.send(
|
||||
Preferences.getTrustedNode(getContext()),
|
||||
Preferences.getTrustedNodePort(getContext()),
|
||||
cryptoMsg
|
||||
);
|
||||
if (response.isError()) {
|
||||
LOG.error("Server responded with error: " + new String(response.getData(),
|
||||
"UTF-8"));
|
||||
} else {
|
||||
ProofOfWorkRequest decryptedResponse = CryptoCustomMessage.read(
|
||||
response, reader).decrypt(privateKey);
|
||||
if (decryptedResponse.getRequest() == COMPLETE) {
|
||||
bmc.internals().getProofOfWorkService().onNonceCalculated(
|
||||
initialHash, decryptedResponse.getData());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (items.size() == 0) {
|
||||
stopPowSync(getContext());
|
||||
}
|
||||
LOG.info("Synchronization finished");
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void startSync(Context ctx) {
|
||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||
Account account = addAccount(ctx, ACCOUNT_SYNC);
|
||||
|
||||
// Recommend a schedule for automatic synchronization. The system may modify this based
|
||||
// on other scheduled syncs and network utilization.
|
||||
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
|
||||
}
|
||||
|
||||
public static void stopSync(Context ctx) {
|
||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||
Account account = addAccount(ctx, ACCOUNT_SYNC);
|
||||
|
||||
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
|
||||
}
|
||||
|
||||
|
||||
public static void startPowSync(Context ctx) {
|
||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||
Account account = addAccount(ctx, ACCOUNT_POW);
|
||||
|
||||
// Recommend a schedule for automatic synchronization. The system may modify this based
|
||||
// on other scheduled syncs and network utilization.
|
||||
ContentResolver.addPeriodicSync(account, AUTHORITY, new Bundle(), SYNC_FREQUENCY);
|
||||
}
|
||||
|
||||
public static void stopPowSync(Context ctx) {
|
||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||
Account account = addAccount(ctx, ACCOUNT_POW);
|
||||
|
||||
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
|
||||
}
|
||||
|
||||
private static Account addAccount(Context ctx, Account account) {
|
||||
if (AccountManager.get(ctx).addAccountExplicitly(account, null, null)) {
|
||||
// Inform the system that this account supports sync
|
||||
ContentResolver.setIsSyncable(account, AUTHORITY, 1);
|
||||
// Inform the system that this account is eligible for auto sync when the network is up
|
||||
ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
|
||||
}
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
90
app/src/main/java/ch/dissem/apps/abit/util/Preferences.java
Normal file
90
app/src/main/java/ch/dissem/apps/abit/util/Preferences.java
Normal file
@ -0,0 +1,90 @@
|
||||
package ch.dissem.apps.abit.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import ch.dissem.apps.abit.R;
|
||||
import ch.dissem.apps.abit.notification.ErrorNotification;
|
||||
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT;
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
|
||||
|
||||
/**
|
||||
* Created by chrig on 01.12.2015.
|
||||
*/
|
||||
public class Preferences {
|
||||
private static Logger LOG = LoggerFactory.getLogger(Preferences.class);
|
||||
|
||||
public static boolean useTrustedNode(Context ctx) {
|
||||
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
|
||||
return trustedNode == null || trustedNode.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning, this method might do a network call and therefore can't be called from
|
||||
* the UI thread.
|
||||
*/
|
||||
public static InetAddress getTrustedNode(Context ctx) {
|
||||
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
|
||||
if (trustedNode == null) return null;
|
||||
trustedNode = trustedNode.trim();
|
||||
if (trustedNode.isEmpty()) return null;
|
||||
|
||||
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
|
||||
int index = trustedNode.lastIndexOf(':');
|
||||
trustedNode = trustedNode.substring(0, index);
|
||||
}
|
||||
try {
|
||||
return InetAddress.getByName(trustedNode);
|
||||
} catch (UnknownHostException e) {
|
||||
new ErrorNotification(ctx)
|
||||
.setError(R.string.error_invalid_sync_host)
|
||||
.show();
|
||||
LOG.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getTrustedNodePort(Context ctx) {
|
||||
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
|
||||
if (trustedNode == null) return 8444;
|
||||
trustedNode = trustedNode.trim();
|
||||
|
||||
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
|
||||
int index = trustedNode.lastIndexOf(':');
|
||||
String portString = trustedNode.substring(index + 1);
|
||||
try {
|
||||
return Integer.parseInt(portString);
|
||||
} catch (NumberFormatException e) {
|
||||
new ErrorNotification(ctx)
|
||||
.setError(R.string.error_invalid_sync_port, portString)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
return 8444;
|
||||
}
|
||||
|
||||
public static long getTimeoutInSeconds(Context ctx) {
|
||||
String preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT);
|
||||
return preference == null ? 120 : Long.parseLong(preference);
|
||||
}
|
||||
|
||||
public static boolean isServerPOW(Context ctx) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
return preferences.getBoolean(PREFERENCE_SERVER_POW, false);
|
||||
}
|
||||
|
||||
private static String getPreference(Context ctx, String name) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
|
||||
return preferences.getString(name, null);
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="server_pow"
|
||||
android:dependency="@string/trusted_node"
|
||||
android:dependency="trusted_node"
|
||||
android:title="@string/server_pow"
|
||||
android:summary="@string/server_pow_summary"
|
||||
/>
|
||||
|
@ -9,7 +9,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.5.0-beta1'
|
||||
classpath 'com.android.tools.build:gradle:1.5.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
Loading…
Reference in New Issue
Block a user