UnifiedPushDatabase.java

  1package eu.siacs.conversations.persistance;
  2
  3import android.content.ContentValues;
  4import android.content.Context;
  5import android.database.Cursor;
  6import android.database.sqlite.SQLiteDatabase;
  7import android.database.sqlite.SQLiteOpenHelper;
  8import android.util.Log;
  9
 10import androidx.annotation.Nullable;
 11
 12import com.google.common.base.MoreObjects;
 13import com.google.common.base.Objects;
 14import com.google.common.base.Optional;
 15import com.google.common.collect.ImmutableList;
 16
 17import org.jetbrains.annotations.NotNull;
 18
 19import java.util.ArrayList;
 20import java.util.List;
 21
 22import eu.siacs.conversations.Config;
 23import eu.siacs.conversations.services.UnifiedPushBroker;
 24
 25public class UnifiedPushDatabase extends SQLiteOpenHelper {
 26    private static final String DATABASE_NAME = "unified-push-distributor";
 27    private static final int DATABASE_VERSION = 1;
 28
 29    private static UnifiedPushDatabase instance;
 30
 31    public static UnifiedPushDatabase getInstance(final Context context) {
 32        synchronized (UnifiedPushDatabase.class) {
 33            if (instance == null) {
 34                instance = new UnifiedPushDatabase(context.getApplicationContext());
 35            }
 36            return instance;
 37        }
 38    }
 39
 40    private UnifiedPushDatabase(@Nullable Context context) {
 41        super(context, DATABASE_NAME, null, DATABASE_VERSION);
 42    }
 43
 44    @Override
 45    public void onCreate(final SQLiteDatabase sqLiteDatabase) {
 46        sqLiteDatabase.execSQL(
 47                "CREATE TABLE push (account TEXT, transport TEXT, application TEXT NOT NULL, instance TEXT NOT NULL UNIQUE, endpoint TEXT, expiration NUMBER DEFAULT 0)");
 48    }
 49
 50    public boolean register(final String application, final String instance) {
 51        final SQLiteDatabase sqLiteDatabase = getWritableDatabase();
 52        sqLiteDatabase.beginTransaction();
 53        final Optional<String> existingApplication;
 54        try (final Cursor cursor =
 55                sqLiteDatabase.query(
 56                        "push",
 57                        new String[] {"application"},
 58                        "instance=?",
 59                        new String[] {instance},
 60                        null,
 61                        null,
 62                        null)) {
 63            if (cursor != null && cursor.moveToFirst()) {
 64                existingApplication = Optional.of(cursor.getString(0));
 65            } else {
 66                existingApplication = Optional.absent();
 67            }
 68        }
 69        if (existingApplication.isPresent()) {
 70            sqLiteDatabase.setTransactionSuccessful();
 71            sqLiteDatabase.endTransaction();
 72            return application.equals(existingApplication.get());
 73        }
 74        final ContentValues contentValues = new ContentValues();
 75        contentValues.put("application", application);
 76        contentValues.put("instance", instance);
 77        contentValues.put("expiration", 0);
 78        final long inserted = sqLiteDatabase.insert("push", null, contentValues);
 79        if (inserted > 0) {
 80            Log.d(Config.LOGTAG, "inserted new application/instance tuple into unified push db");
 81        }
 82        sqLiteDatabase.setTransactionSuccessful();
 83        sqLiteDatabase.endTransaction();
 84        return true;
 85    }
 86
 87    public List<PushTarget> getRenewals(final String account, final String transport) {
 88        final ImmutableList.Builder<PushTarget> renewalBuilder = ImmutableList.builder();
 89        final long expiration = System.currentTimeMillis() + UnifiedPushBroker.TIME_TO_RENEW;
 90        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
 91        try (final Cursor cursor =
 92                sqLiteDatabase.query(
 93                        "push",
 94                        new String[] {"application", "instance"},
 95                        "account <> ? OR transport <> ? OR expiration < " + expiration,
 96                        new String[] {account, transport},
 97                        null,
 98                        null,
 99                        null)) {
100            while (cursor != null && cursor.moveToNext()) {
101                renewalBuilder.add(
102                        new PushTarget(
103                                cursor.getString(cursor.getColumnIndexOrThrow("application")),
104                                cursor.getString(cursor.getColumnIndexOrThrow("instance"))));
105            }
106        }
107        return renewalBuilder.build();
108    }
109
110    public ApplicationEndpoint getEndpoint(
111            final String account, final String transport, final String instance) {
112        final long expiration = System.currentTimeMillis() + UnifiedPushBroker.TIME_TO_RENEW;
113        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
114        try (final Cursor cursor =
115                sqLiteDatabase.query(
116                        "push",
117                        new String[] {"application", "endpoint"},
118                        "account = ? AND transport = ? AND instance = ? AND endpoint IS NOT NULL AND expiration >= "
119                                + expiration,
120                        new String[] {account, transport, instance},
121                        null,
122                        null,
123                        null)) {
124            if (cursor != null && cursor.moveToFirst()) {
125                return new ApplicationEndpoint(
126                        cursor.getString(cursor.getColumnIndexOrThrow("application")),
127                        cursor.getString(cursor.getColumnIndexOrThrow("endpoint")));
128            }
129        }
130        return null;
131    }
132
133    public List<PushTarget> deletePushTargets() {
134        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
135        final ImmutableList.Builder<PushTarget> builder = new ImmutableList.Builder<>();
136        try (final Cursor cursor = sqLiteDatabase.query("push",new String[]{"application","instance"},null,null,null,null,null)) {
137            if (cursor != null && cursor.moveToFirst()) {
138                builder.add(new PushTarget(
139                        cursor.getString(cursor.getColumnIndexOrThrow("application")),
140                        cursor.getString(cursor.getColumnIndexOrThrow("instance"))));
141            }
142        } catch (final Exception e) {
143            Log.d(Config.LOGTAG,"unable to retrieve push targets",e);
144            return builder.build();
145        }
146        sqLiteDatabase.delete("push",null,null);
147        return builder.build();
148    }
149
150    public boolean hasEndpoints(final UnifiedPushBroker.Transport transport) {
151        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
152        try (final Cursor cursor =
153                sqLiteDatabase.rawQuery(
154                        "SELECT EXISTS(SELECT endpoint FROM push WHERE account = ? AND transport = ?)",
155                        new String[] {
156                            transport.account.getUuid(), transport.transport.toEscapedString()
157                        })) {
158            if (cursor != null && cursor.moveToFirst()) {
159                return cursor.getInt(0) > 0;
160            }
161        }
162        return false;
163    }
164
165    @Override
166    public void onUpgrade(
167            final SQLiteDatabase sqLiteDatabase, final int oldVersion, final int newVersion) {}
168
169    public boolean updateEndpoint(
170            final String instance,
171            final String account,
172            final String transport,
173            final String endpoint,
174            final long expiration) {
175        final SQLiteDatabase sqLiteDatabase = getWritableDatabase();
176        sqLiteDatabase.beginTransaction();
177        final String existingEndpoint;
178        try (final Cursor cursor =
179                sqLiteDatabase.query(
180                        "push",
181                        new String[] {"endpoint"},
182                        "instance=?",
183                        new String[] {instance},
184                        null,
185                        null,
186                        null)) {
187            if (cursor != null && cursor.moveToFirst()) {
188                existingEndpoint = cursor.getString(0);
189            } else {
190                existingEndpoint = null;
191            }
192        }
193        final ContentValues contentValues = new ContentValues();
194        contentValues.put("account", account);
195        contentValues.put("transport", transport);
196        contentValues.put("endpoint", endpoint);
197        contentValues.put("expiration", expiration);
198        sqLiteDatabase.update("push", contentValues, "instance=?", new String[] {instance});
199        sqLiteDatabase.setTransactionSuccessful();
200        sqLiteDatabase.endTransaction();
201        return !endpoint.equals(existingEndpoint);
202    }
203
204    public List<PushTarget> getPushTargets(final String account, final String transport) {
205        final ImmutableList.Builder<PushTarget> renewalBuilder = ImmutableList.builder();
206        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
207        try (final Cursor cursor =
208                sqLiteDatabase.query(
209                        "push",
210                        new String[] {"application", "instance"},
211                        "account = ?",
212                        new String[] {account},
213                        null,
214                        null,
215                        null)) {
216            while (cursor != null && cursor.moveToNext()) {
217                renewalBuilder.add(
218                        new PushTarget(
219                                cursor.getString(cursor.getColumnIndexOrThrow("application")),
220                                cursor.getString(cursor.getColumnIndexOrThrow("instance"))));
221            }
222        }
223        return renewalBuilder.build();
224    }
225
226    public boolean deleteInstance(final String instance) {
227        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
228        final int rows = sqLiteDatabase.delete("push", "instance=?", new String[] {instance});
229        return rows >= 1;
230    }
231
232    public boolean deleteApplication(final String application) {
233        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
234        final int rows = sqLiteDatabase.delete("push", "application=?", new String[] {application});
235        return rows >= 1;
236    }
237
238    public static class ApplicationEndpoint {
239        public final String application;
240        public final String endpoint;
241
242        public ApplicationEndpoint(String application, String endpoint) {
243            this.application = application;
244            this.endpoint = endpoint;
245        }
246    }
247
248    public static class PushTarget {
249        public final String application;
250        public final String instance;
251
252        public PushTarget(final String application, final String instance) {
253            this.application = application;
254            this.instance = instance;
255        }
256
257        @NotNull
258        @Override
259        public String toString() {
260            return MoreObjects.toStringHelper(this)
261                    .add("application", application)
262                    .add("instance", instance)
263                    .toString();
264        }
265
266        @Override
267        public boolean equals(Object o) {
268            if (this == o) return true;
269            if (o == null || getClass() != o.getClass()) return false;
270            PushTarget that = (PushTarget) o;
271            return Objects.equal(application, that.application)
272                    && Objects.equal(instance, that.instance);
273        }
274
275        @Override
276        public int hashCode() {
277            return Objects.hashCode(application, instance);
278        }
279    }
280}