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