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