DatabaseBackend.java

  1package eu.siacs.conversations.persistance;
  2
  3import java.util.ArrayList;
  4import java.util.List;
  5import java.util.concurrent.CopyOnWriteArrayList;
  6
  7import eu.siacs.conversations.entities.Account;
  8import eu.siacs.conversations.entities.Contact;
  9import eu.siacs.conversations.entities.Conversation;
 10import eu.siacs.conversations.entities.Message;
 11import eu.siacs.conversations.entities.Roster;
 12import eu.siacs.conversations.xmpp.jid.Jid;
 13
 14import android.content.Context;
 15import android.database.Cursor;
 16import android.database.sqlite.SQLiteCantOpenDatabaseException;
 17import android.database.sqlite.SQLiteDatabase;
 18import android.database.sqlite.SQLiteOpenHelper;
 19
 20public class DatabaseBackend extends SQLiteOpenHelper {
 21
 22	private static DatabaseBackend instance = null;
 23
 24	private static final String DATABASE_NAME = "history";
 25	private static final int DATABASE_VERSION = 9;
 26
 27	private static String CREATE_CONTATCS_STATEMENT = "create table "
 28			+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
 29			+ Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
 30			+ Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
 31			+ Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
 32			+ Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
 33            + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
 34			+ "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
 35			+ Account.TABLENAME + "(" + Account.UUID
 36			+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
 37			+ Contact.JID + ") ON CONFLICT REPLACE);";
 38
 39	private DatabaseBackend(Context context) {
 40		super(context, DATABASE_NAME, null, DATABASE_VERSION);
 41	}
 42
 43	@Override
 44	public void onCreate(SQLiteDatabase db) {
 45		db.execSQL("PRAGMA foreign_keys=ON;");
 46		db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID
 47				+ " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT,"
 48				+ Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT,"
 49				+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
 50				+ " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
 51				+ " TEXT)");
 52		db.execSQL("create table " + Conversation.TABLENAME + " ("
 53				+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
 54				+ " TEXT, " + Conversation.CONTACT + " TEXT, "
 55				+ Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
 56				+ " TEXT, " + Conversation.CREATED + " NUMBER, "
 57				+ Conversation.STATUS + " NUMBER, " + Conversation.MODE
 58				+ " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
 59				+ Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
 60				+ "(" + Account.UUID + ") ON DELETE CASCADE);");
 61		db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
 62				+ " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
 63				+ Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
 64				+ " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
 65				+ Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
 66				+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
 67				+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
 68				+ Message.CONVERSATION + ") REFERENCES "
 69				+ Conversation.TABLENAME + "(" + Conversation.UUID
 70				+ ") ON DELETE CASCADE);");
 71
 72		db.execSQL(CREATE_CONTATCS_STATEMENT);
 73	}
 74
 75	@Override
 76	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 77		if (oldVersion < 2 && newVersion >= 2) {
 78			db.execSQL("update " + Account.TABLENAME + " set "
 79					+ Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
 80		}
 81		if (oldVersion < 3 && newVersion >= 3) {
 82			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 83					+ Message.TYPE + " NUMBER");
 84		}
 85		if (oldVersion < 5 && newVersion >= 5) {
 86			db.execSQL("DROP TABLE " + Contact.TABLENAME);
 87			db.execSQL(CREATE_CONTATCS_STATEMENT);
 88			db.execSQL("UPDATE " + Account.TABLENAME + " SET "
 89					+ Account.ROSTERVERSION + " = NULL");
 90		}
 91		if (oldVersion < 6 && newVersion >= 6) {
 92			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 93					+ Message.TRUE_COUNTERPART + " TEXT");
 94		}
 95		if (oldVersion < 7 && newVersion >= 7) {
 96			db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
 97					+ Message.REMOTE_MSG_ID + " TEXT");
 98			db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
 99					+ Contact.AVATAR + " TEXT");
100			db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
101					+ Account.AVATAR + " TEXT");
102		}
103		if (oldVersion < 8 && newVersion >= 8) {
104			db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
105					+ Conversation.ATTRIBUTES + " TEXT");
106		}
107        if (oldVersion < 9 && newVersion >= 9) {
108            db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
109                    + Contact.LAST_TIME + " NUMBER");
110            db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
111                    + Contact.LAST_PRESENCE + " TEXT");
112        }
113	}
114
115	public static synchronized DatabaseBackend getInstance(Context context) {
116		if (instance == null) {
117			instance = new DatabaseBackend(context);
118		}
119		return instance;
120	}
121
122	public void createConversation(Conversation conversation) {
123		SQLiteDatabase db = this.getWritableDatabase();
124		db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
125	}
126
127	public void createMessage(Message message) {
128		SQLiteDatabase db = this.getWritableDatabase();
129		db.insert(Message.TABLENAME, null, message.getContentValues());
130	}
131
132	public void createAccount(Account account) {
133		SQLiteDatabase db = this.getWritableDatabase();
134		db.insert(Account.TABLENAME, null, account.getContentValues());
135	}
136
137	public void createContact(Contact contact) {
138		SQLiteDatabase db = this.getWritableDatabase();
139		db.insert(Contact.TABLENAME, null, contact.getContentValues());
140	}
141
142	public int getConversationCount() {
143		SQLiteDatabase db = this.getReadableDatabase();
144		Cursor cursor = db.rawQuery("select count(uuid) as count from "
145				+ Conversation.TABLENAME + " where " + Conversation.STATUS
146				+ "=" + Conversation.STATUS_AVAILABLE, null);
147		cursor.moveToFirst();
148		return cursor.getInt(0);
149	}
150
151	public CopyOnWriteArrayList<Conversation> getConversations(int status) {
152		CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
153		SQLiteDatabase db = this.getReadableDatabase();
154		String[] selectionArgs = { Integer.toString(status) };
155		Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
156				+ " where " + Conversation.STATUS + " = ? order by "
157				+ Conversation.CREATED + " desc", selectionArgs);
158		while (cursor.moveToNext()) {
159			list.add(Conversation.fromCursor(cursor));
160		}
161		return list;
162	}
163
164	public ArrayList<Message> getMessages(Conversation conversations, int limit) {
165		return getMessages(conversations, limit, -1);
166	}
167
168	public ArrayList<Message> getMessages(Conversation conversation, int limit,
169			long timestamp) {
170		ArrayList<Message> list = new ArrayList<>();
171		SQLiteDatabase db = this.getReadableDatabase();
172		Cursor cursor;
173		if (timestamp == -1) {
174			String[] selectionArgs = { conversation.getUuid() };
175			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
176					+ "=?", selectionArgs, null, null, Message.TIME_SENT
177					+ " DESC", String.valueOf(limit));
178		} else {
179			String[] selectionArgs = { conversation.getUuid(),
180					Long.toString(timestamp) };
181			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
182					+ "=? and " + Message.TIME_SENT + "<?", selectionArgs,
183					null, null, Message.TIME_SENT + " DESC",
184					String.valueOf(limit));
185		}
186		if (cursor.getCount() > 0) {
187			cursor.moveToLast();
188			do {
189				Message message = Message.fromCursor(cursor);
190				message.setConversation(conversation);
191				list.add(message);
192			} while (cursor.moveToPrevious());
193		}
194		return list;
195	}
196
197	public Conversation findConversation(final Account account, final Jid contactJid) {
198		SQLiteDatabase db = this.getReadableDatabase();
199		String[] selectionArgs = { account.getUuid(), contactJid.toBareJid().toString() + "%" };
200		Cursor cursor = db.query(Conversation.TABLENAME, null,
201				Conversation.ACCOUNT + "=? AND " + Conversation.CONTACTJID
202						+ " like ?", selectionArgs, null, null, null);
203		if (cursor.getCount() == 0)
204			return null;
205		cursor.moveToFirst();
206		return Conversation.fromCursor(cursor);
207	}
208
209	public void updateConversation(Conversation conversation) {
210		SQLiteDatabase db = this.getWritableDatabase();
211		String[] args = { conversation.getUuid() };
212		db.update(Conversation.TABLENAME, conversation.getContentValues(),
213				Conversation.UUID + "=?", args);
214	}
215
216	public List<Account> getAccounts() {
217		List<Account> list = new ArrayList<>();
218		SQLiteDatabase db = this.getReadableDatabase();
219		Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
220				null, null);
221		while (cursor.moveToNext()) {
222			list.add(Account.fromCursor(cursor));
223		}
224		cursor.close();
225		return list;
226	}
227
228	public void updateAccount(Account account) {
229		SQLiteDatabase db = this.getWritableDatabase();
230		String[] args = { account.getUuid() };
231		db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
232				+ "=?", args);
233	}
234
235	public void deleteAccount(Account account) {
236		SQLiteDatabase db = this.getWritableDatabase();
237		String[] args = { account.getUuid() };
238		db.delete(Account.TABLENAME, Account.UUID + "=?", args);
239	}
240
241	public boolean hasEnabledAccounts() {
242		SQLiteDatabase db = this.getReadableDatabase();
243		Cursor cursor = db.rawQuery("select count(" + Account.UUID + ")  from "
244				+ Account.TABLENAME + " where not options & (1 <<1)", null);
245		try {
246			cursor.moveToFirst();
247			int count = cursor.getInt(0);
248			cursor.close();
249			return (count > 0);
250		} catch (SQLiteCantOpenDatabaseException e) {
251			return true; // better safe than sorry
252		}
253	}
254
255	@Override
256	public SQLiteDatabase getWritableDatabase() {
257		SQLiteDatabase db = super.getWritableDatabase();
258		db.execSQL("PRAGMA foreign_keys=ON;");
259		return db;
260	}
261
262	public void updateMessage(Message message) {
263		SQLiteDatabase db = this.getWritableDatabase();
264		String[] args = { message.getUuid() };
265		db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
266				+ "=?", args);
267	}
268
269	public void readRoster(Roster roster) {
270		SQLiteDatabase db = this.getReadableDatabase();
271		Cursor cursor;
272		String args[] = { roster.getAccount().getUuid() };
273		cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?",
274				args, null, null, null);
275		while (cursor.moveToNext()) {
276			roster.initContact(Contact.fromCursor(cursor));
277		}
278		cursor.close();
279	}
280
281	public void writeRoster(final Roster roster) {
282		final Account account = roster.getAccount();
283		final SQLiteDatabase db = this.getWritableDatabase();
284		for (Contact contact : roster.getContacts()) {
285			if (contact.getOption(Contact.Options.IN_ROSTER)) {
286				db.insert(Contact.TABLENAME, null, contact.getContentValues());
287			} else {
288				String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
289				String[] whereArgs = { account.getUuid(), contact.getJid().toString() };
290				db.delete(Contact.TABLENAME, where, whereArgs);
291			}
292		}
293		account.setRosterVersion(roster.getVersion());
294		updateAccount(account);
295	}
296
297	public void deleteMessage(Message message) {
298		SQLiteDatabase db = this.getWritableDatabase();
299		String[] args = { message.getUuid() };
300		db.delete(Message.TABLENAME, Message.UUID + "=?", args);
301	}
302
303	public void deleteMessagesInConversation(Conversation conversation) {
304		SQLiteDatabase db = this.getWritableDatabase();
305		String[] args = { conversation.getUuid() };
306		db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
307	}
308
309	public Conversation findConversationByUuid(String conversationUuid) {
310		SQLiteDatabase db = this.getReadableDatabase();
311		String[] selectionArgs = { conversationUuid };
312		Cursor cursor = db.query(Conversation.TABLENAME, null,
313				Conversation.UUID + "=?", selectionArgs, null, null, null);
314		if (cursor.getCount() == 0) {
315			return null;
316		}
317		cursor.moveToFirst();
318		return Conversation.fromCursor(cursor);
319	}
320
321	public Message findMessageByUuid(String messageUuid) {
322		SQLiteDatabase db = this.getReadableDatabase();
323		String[] selectionArgs = { messageUuid };
324		Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?",
325				selectionArgs, null, null, null);
326		if (cursor.getCount() == 0) {
327			return null;
328		}
329		cursor.moveToFirst();
330		return Message.fromCursor(cursor);
331	}
332
333	public Account findAccountByUuid(String accountUuid) {
334		SQLiteDatabase db = this.getReadableDatabase();
335		String[] selectionArgs = { accountUuid };
336		Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?",
337				selectionArgs, null, null, null);
338		if (cursor.getCount() == 0) {
339			return null;
340		}
341		cursor.moveToFirst();
342		return Account.fromCursor(cursor);
343	}
344
345	public List<Message> getImageMessages(Conversation conversation) {
346		ArrayList<Message> list = new ArrayList<>();
347		SQLiteDatabase db = this.getReadableDatabase();
348		Cursor cursor;
349			String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) };
350			cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
351					+ "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null);
352		if (cursor.getCount() > 0) {
353			cursor.moveToLast();
354			do {
355				Message message = Message.fromCursor(cursor);
356				message.setConversation(conversation);
357				list.add(message);
358			} while (cursor.moveToPrevious());
359		}
360		return list;
361	}
362}