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;
 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        // TODO use a date somewhat in the future to account for period renewal triggers
 88        final long expiration = System.currentTimeMillis();
 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 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                                + System.currentTimeMillis(),
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    @Override
132    public void onUpgrade(
133            final SQLiteDatabase sqLiteDatabase, final int oldVersion, final int newVersion) {}
134
135    public boolean updateEndpoint(
136            final String instance,
137            final String account,
138            final String transport,
139            final String endpoint,
140            final long expiration) {
141        final SQLiteDatabase sqLiteDatabase = getWritableDatabase();
142        sqLiteDatabase.beginTransaction();
143        final String existingEndpoint;
144        try (final Cursor cursor =
145                sqLiteDatabase.query(
146                        "push",
147                        new String[] {"endpoint"},
148                        "instance=?",
149                        new String[] {instance},
150                        null,
151                        null,
152                        null)) {
153            if (cursor != null && cursor.moveToFirst()) {
154                existingEndpoint = cursor.getString(0);
155            } else {
156                existingEndpoint = null;
157            }
158        }
159        final ContentValues contentValues = new ContentValues();
160        contentValues.put("account", account);
161        contentValues.put("transport", transport);
162        contentValues.put("endpoint", endpoint);
163        contentValues.put("expiration", expiration);
164        sqLiteDatabase.update("push", contentValues, "instance=?", new String[] {instance});
165        sqLiteDatabase.setTransactionSuccessful();
166        sqLiteDatabase.endTransaction();
167        return !endpoint.equals(existingEndpoint);
168    }
169
170    public List<PushTarget> getPushTargets(final String account, final String transport) {
171        final ImmutableList.Builder<PushTarget> renewalBuilder = ImmutableList.builder();
172        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
173        try (final Cursor cursor =
174                sqLiteDatabase.query(
175                        "push",
176                        new String[] {"application", "instance"},
177                        "account = ?",
178                        new String[] {account},
179                        null,
180                        null,
181                        null)) {
182            while (cursor != null && cursor.moveToNext()) {
183                renewalBuilder.add(
184                        new PushTarget(
185                                cursor.getString(cursor.getColumnIndexOrThrow("application")),
186                                cursor.getString(cursor.getColumnIndexOrThrow("instance"))));
187            }
188        }
189        return renewalBuilder.build();
190    }
191
192    public boolean deleteInstance(final String instance) {
193        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
194        final int rows = sqLiteDatabase.delete("push", "instance=?", new String[] {instance});
195        return rows >= 1;
196    }
197
198    public boolean deleteApplication(final String application) {
199        final SQLiteDatabase sqLiteDatabase = getReadableDatabase();
200        final int rows = sqLiteDatabase.delete("push", "application=?", new String[] {application});
201        return rows >= 1;
202    }
203
204    public static class ApplicationEndpoint {
205        public final String application;
206        public final String endpoint;
207
208        public ApplicationEndpoint(String application, String endpoint) {
209            this.application = application;
210            this.endpoint = endpoint;
211        }
212    }
213
214    public static class PushTarget {
215        public final String application;
216        public final String instance;
217
218        public PushTarget(final String application, final String instance) {
219            this.application = application;
220            this.instance = instance;
221        }
222
223        @NotNull
224        @Override
225        public String toString() {
226            return MoreObjects.toStringHelper(this)
227                    .add("application", application)
228                    .add("instance", instance)
229                    .toString();
230        }
231
232        @Override
233        public boolean equals(Object o) {
234            if (this == o) return true;
235            if (o == null || getClass() != o.getClass()) return false;
236            PushTarget that = (PushTarget) o;
237            return Objects.equal(application, that.application)
238                    && Objects.equal(instance, that.instance);
239        }
240
241        @Override
242        public int hashCode() {
243            return Objects.hashCode(application, instance);
244        }
245    }
246}