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