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