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