Browse Source

make DatabaseController async

Yohan Simard 3 years ago
parent
commit
1ab27f4f87

+ 5
- 4
src/main/java/fr/insa/clavardator/chat/ChatHistory.java View File

@@ -3,6 +3,7 @@ package fr.insa.clavardator.chat;
3 3
 import fr.insa.clavardator.db.DatabaseController;
4 4
 import fr.insa.clavardator.users.PeerUser;
5 5
 import fr.insa.clavardator.users.UserInformation;
6
+import fr.insa.clavardator.util.ErrorCallback;
6 7
 import fr.insa.clavardator.util.Log;
7 8
 import javafx.application.Platform;
8 9
 import javafx.collections.FXCollections;
@@ -44,12 +45,12 @@ public class ChatHistory {
44 45
 	/**
45 46
 	 * Loads history from database only if it has not previously been loaded
46 47
 	 */
47
-	public void load() {
48
+	public void load(ErrorCallback errorCallback) {
48 49
 		if (!historyLoaded) {
49 50
 			final Date from = new Date();
50 51
 			// Load whole history
51 52
 			from.setTime(0);
52
-			db.getChatHistory(new UserInformation(user), from, new Date(), this::onLoaded);
53
+			db.getChatHistory(new UserInformation(user), from, new Date(), this::onLoaded, errorCallback);
53 54
 		} else {
54 55
 			notifyHistoryLoaded();
55 56
 		}
@@ -77,10 +78,10 @@ public class ChatHistory {
77 78
 	 *
78 79
 	 * @param message The message to add
79 80
 	 */
80
-	public void addMessage(Message message) {
81
+	public void addMessage(Message message, ErrorCallback errorCallback) {
81 82
 		db.addMessage(message, () -> {
82 83
 			Platform.runLater(() -> history.add(message));
83
-		});
84
+		}, errorCallback);
84 85
 	}
85 86
 
86 87
 	public interface HistoryLoadedCallback {

+ 217
- 170
src/main/java/fr/insa/clavardator/db/DatabaseController.java View File

@@ -5,6 +5,7 @@ import fr.insa.clavardator.chat.Message;
5 5
 import fr.insa.clavardator.users.CurrentUser;
6 6
 import fr.insa.clavardator.users.User;
7 7
 import fr.insa.clavardator.users.UserInformation;
8
+import fr.insa.clavardator.util.ErrorCallback;
8 9
 import fr.insa.clavardator.util.Log;
9 10
 import org.intellij.lang.annotations.Language;
10 11
 import org.jetbrains.annotations.Nullable;
@@ -21,7 +22,7 @@ public class DatabaseController {
21 22
 	}
22 23
 
23 24
 	public DatabaseController(boolean test) {
24
-		if(test) {
25
+		if (test) {
25 26
 			connectToTestDb();
26 27
 		} else {
27 28
 			connect();
@@ -31,20 +32,22 @@ public class DatabaseController {
31 32
 	/**
32 33
 	 * Connects to the main database
33 34
 	 */
34
-	public void connect() {
35
+	private void connect() {
35 36
 		connectToDatabase("clavardator");
36 37
 	}
37 38
 
38 39
 	/**
39 40
 	 * Connects to the test database.
40
-	 * @implNote  DO NOT USE OUTSIDE OF TESTS
41
+	 *
42
+	 * @implNote DO NOT USE OUTSIDE OF TESTS
41 43
 	 */
42
-	public void connectToTestDb() {
44
+	private void connectToTestDb() {
43 45
 		connectToDatabase("clavardator_test");
44 46
 	}
45 47
 
46 48
 	/**
47 49
 	 * Connects to the database of the given name
50
+	 *
48 51
 	 * @param dbName The database to connect to
49 52
 	 */
50 53
 	private void connectToDatabase(String dbName) {
@@ -69,127 +72,121 @@ public class DatabaseController {
69 72
 	}
70 73
 
71 74
 	/**
72
-	 * Executes a simple update statement
73
-	 *
74
-	 * @param sqlQuery The query to execute
75
-	 * @throws SQLException SQL error
76
-	 */
77
-	private void executeUpdate(@Language("SQL") String sqlQuery) throws SQLException {
78
-		Statement statement = connection.createStatement();
79
-		final int rowsModified = statement.executeUpdate(sqlQuery);
80
-		Log.v(getClass().getSimpleName(), rowsModified + " rows modified");
81
-		statement.close();
82
-	}
83
-
84
-	/**
85 75
 	 * Creates the table used to store messages
86
-	 * @throws SQLException SQL error
76
+	 *
77
+	 * @param callback      The function to call on success
78
+	 * @param errorCallback The function to call on error
87 79
 	 */
88
-	private void createMessageTable() throws SQLException {
89
-		Log.v(getClass().getSimpleName(), "Creating table message...");
90
-		executeUpdate("CREATE TABLE IF NOT EXISTS message " +
80
+	private void createMessageTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
81
+		@Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS message " +
91 82
 				"(id            INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
92 83
 				" timestamp     DATETIME                          NOT NULL, " +
93 84
 				" sender        INTEGER UNSIGNED                  NOT NULL, " +
94 85
 				" recipient     INTEGER UNSIGNED                  NOT NULL, " +
95 86
 				" text          TEXT, " +
96
-				" file_path     TEXT)");
87
+				" file_path     TEXT)";
88
+
89
+		Log.v(getClass().getSimpleName(), "Creating table message...");
90
+		UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
91
+		executor.start();
97 92
 	}
98 93
 
99 94
 	/**
100 95
 	 * Creates the table used to store users
101
-	 * @throws SQLException SQL error
96
+	 *
97
+	 * @param callback      The function to call on success
98
+	 * @param errorCallback The function to call on error
102 99
 	 */
103
-	private void createUserTable() throws SQLException {
104
-		Log.v(getClass().getSimpleName(), "Creating table user...");
105
-		executeUpdate("CREATE TABLE IF NOT EXISTS user " +
100
+	private void createUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
101
+		@Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS user " +
106 102
 				"(id            INTEGER UNSIGNED PRIMARY KEY NOT NULL," +
107
-				" username      TINYTEXT                     NULLABLE )");
103
+				" username      TINYTEXT                     NULLABLE )";
104
+
105
+		Log.v(getClass().getSimpleName(), "Creating table user...");
106
+		UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
107
+		executor.start();
108 108
 	}
109 109
 
110 110
 	/**
111 111
 	 * Creates all needed tables if non-existent
112
+	 *
113
+	 * @param callback      The function to call on success
114
+	 * @param errorCallback The function to call on error
112 115
 	 */
113
-	public void init(@Nullable FinishCallback callback) {
114
-		try {
115
-			createMessageTable();
116
-			createUserTable();
117
-			if (callback != null) {
118
-				callback.onFinish();
119
-			}
120
-		} catch (SQLException e) {
121
-			e.printStackTrace();
122
-		}
116
+	public void initTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
117
+		createMessageTable(() -> createUserTable(callback, errorCallback), errorCallback);
123 118
 	}
124 119
 
125 120
 	/**
126 121
 	 * Destroys the message table
127
-	 * @throws SQLException SQL error
122
+	 *
123
+	 * @param callback      The function to call on success
124
+	 * @param errorCallback The function to call on error
128 125
 	 */
129
-	private void dropMessageTable() throws SQLException {
126
+	private void dropMessageTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
127
+		@Language("SQL") String sql = "DROP TABLE IF EXISTS message";
130 128
 		Log.v(getClass().getSimpleName(), "Dropping table message...");
131
-		executeUpdate("DROP TABLE IF EXISTS message");
129
+		UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
130
+		executor.start();
132 131
 	}
133 132
 
134 133
 	/**
135 134
 	 * Destroys the user table
136
-	 * @throws SQLException SQL error
135
+	 *
136
+	 * @param callback      The function to call on success
137
+	 * @param errorCallback The function to call on error
137 138
 	 */
138
-	private void dropUserTable() throws SQLException {
139
+	private void dropUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
140
+		@Language("SQL") String sql = "DROP TABLE IF EXISTS user";
139 141
 		Log.v(getClass().getSimpleName(), "Dropping table user...");
140
-		executeUpdate("DROP TABLE IF EXISTS user");
142
+		UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
143
+		executor.start();
141 144
 	}
142 145
 
143 146
 	/**
144 147
 	 * Destroys all tables
148
+	 *
149
+	 * @param callback      The function to call on success
150
+	 * @param errorCallback The function to call on error
145 151
 	 */
146
-	private void dropTables() {
147
-		try {
148
-			dropMessageTable();
149
-			dropUserTable();
150
-		} catch (SQLException e) {
151
-			e.printStackTrace();
152
-		}
152
+	private void dropTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
153
+		dropMessageTable(() -> dropUserTable(callback, errorCallback), errorCallback);
153 154
 	}
154 155
 
155 156
 	/**
156 157
 	 * Destroys and recreates all tables
158
+	 *
159
+	 * @param callback      The function to call on success
160
+	 * @param errorCallback The function to call on error
157 161
 	 */
158
-	public void resetTables() {
159
-		dropTables();
160
-		init(null);
162
+	public void resetTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
163
+		dropTables(() -> initTables(callback, errorCallback), errorCallback);
161 164
 	}
162 165
 
163 166
 
164 167
 	/**
165 168
 	 * Fetches the list of users for which we already have a chat history
166 169
 	 *
167
-	 * @param callback Function called when the request is done
170
+	 * @param callback      Function called when the request is done
171
+	 * @param errorCallback The function to call on error
168 172
 	 */
169
-	public void getAllUsers(UsersCallback callback) {
170
-		try {
171
-			Statement stmt = connection.createStatement();
172
-			String sql = "SELECT * FROM user WHERE id != " + CurrentUser.getInstance().getId();
173
+	public void getAllUsers(UsersCallback callback, ErrorCallback errorCallback) {
174
+		@Language("SQL") String sql = "SELECT * FROM user WHERE id != " + CurrentUser.getInstance().getId();
173 175
 
174
-			Log.v(getClass().getSimpleName(), "Fetching users from db... ");
175
-			ResultSet res = stmt.executeQuery(sql);
176
+		Log.v(getClass().getSimpleName(), "Fetching users from db... ");
176 177
 
178
+		QueryExecutor executor = new QueryExecutor(sql, res -> {
177 179
 			ArrayList<User> userList = new ArrayList<>();
178 180
 			while (res.next()) {
179 181
 				int id = res.getInt("id");
180 182
 				String username = res.getString("username");
181 183
 				userList.add(new User(id, username));
182 184
 			}
183
-			Log.v(getClass().getSimpleName(), userList.size() + " rows fetched");
184
-			res.close();
185
-			stmt.close();
185
+			Log.v(getClass().getSimpleName(), userList.size() + " users fetched");
186 186
 
187
-			if (callback != null) {
188
-				callback.onUsersFetched(userList);
189
-			}
190
-		} catch (SQLException e) {
191
-			e.printStackTrace();
192
-		}
187
+			callback.onUsersFetched(userList);
188
+		}, errorCallback);
189
+		executor.start();
193 190
 	}
194 191
 
195 192
 
@@ -197,33 +194,37 @@ public class DatabaseController {
197 194
 	 * Adds a message to the database for this user.
198 195
 	 * If the user does not exist, we create it.
199 196
 	 *
200
-	 * @param message  The message to add to the database
201
-	 * @param callback Function called when the request is done
197
+	 * @param message       The message to add to the database
198
+	 * @param callback      Function called when the request is done
199
+	 * @param errorCallback The function to call on error
202 200
 	 */
203
-	public void addMessage(Message message, FinishCallback callback) {
204
-		try {
201
+	public void addMessage(Message message, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
202
+		// Insert the correspondent if not already in the database
203
+		Log.v(getClass().getSimpleName(), "Inserting correpondent into db... ");
204
+		addUser(message.getCorrespondent(), () -> {
205
+
206
+			// Handle messages containing a file
207
+			String filePath = "NULL";
205 208
 			// TODO: Handle messages containing files:
206 209
 			//       store file in file system and put the path in filePath
207
-			String filePath = "NULL";
208 210
 			if (message instanceof FileMessage) {
209 211
 				Log.w(getClass().getSimpleName(), "Functionality not implemented: file has not been saved");
210 212
 //				filePath = ((FileMessage) message).getFileName();
211 213
 			}
212 214
 
213
-			// Insert the new message
214
-			Log.v(getClass().getSimpleName(), "Inserting message into db... ");
215
-
215
+			// TODO: do this in the construction of the message instead?
216 216
 			int recipientId;
217 217
 			int senderId;
218
-			if (CurrentUser.getInstance().isLocalId(message.getRecipient().id)) {
219
-				recipientId = CurrentUser.getInstance().getId();
220
-				senderId = message.getSender().id;
221
-			} else {
222
-				senderId = CurrentUser.getInstance().getId();
223
-				recipientId = message.getRecipient().id;
224
-			}
218
+//			if (CurrentUser.getInstance().isLocalId(message.getRecipient().id)) {
219
+//				recipientId = CurrentUser.getInstance().getId();
220
+			senderId = message.getSender().id;
221
+//			} else {
222
+//				senderId = CurrentUser.getInstance().getId();
223
+			recipientId = message.getRecipient().id;
224
+//			}
225 225
 
226
-			executeUpdate("INSERT INTO message " +
226
+			// Insert the new message
227
+			@Language("SQL") String sql = "INSERT INTO message " +
227 228
 					"(timestamp, sender, recipient, text, file_path) " +
228 229
 					"VALUES (" +
229 230
 					message.getDate().getTime() + ", " +
@@ -231,73 +232,57 @@ public class DatabaseController {
231 232
 					recipientId + ", " +
232 233
 					"\"" + message.getText() + "\", " +
233 234
 					filePath +
234
-					")");
235
-
236
-			Log.v(getClass().getSimpleName(), "Inserting correspondent into db... ");
237
-			addUser(message.getCorrespondent());
238
-
239
-
240
-			if (callback != null) {
241
-				callback.onFinish();
242
-			}
243
-		} catch (SQLException e) {
244
-			e.printStackTrace();
245
-		}
246
-	}
235
+					")";
236
+			Log.v(getClass().getSimpleName(), "Inserting message into db... ");
237
+			UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
238
+			executor.start();
247 239
 
248
-	public void addUser(UserInformation user) {
249
-		addUser(user, null);
240
+		}, errorCallback);
250 241
 	}
251 242
 
252 243
 	/**
253 244
 	 * Inserts the given user if not existing
254 245
 	 *
255
-	 * @param user The user information to store
246
+	 * @param user          The user information to store
247
+	 * @param callback      The function to call on success
248
+	 * @param errorCallback The function to call on error
256 249
 	 */
257
-	public void addUser(UserInformation user, @Nullable FinishCallback callback) {
258
-		try {
259
-			Log.v(getClass().getSimpleName(), "Adding user to db: " + user.id + " / " + user.getUsername());
260
-			if (user.getUsername() != null) {
261
-				executeUpdate("INSERT OR IGNORE INTO user " +
262
-						"(id, username) " +
263
-						"VALUES (" + user.id + ", \"" + user.getUsername() + "\")");
264
-			} else {
265
-				executeUpdate("INSERT OR IGNORE INTO user " +
266
-						"(id) " +
267
-						"VALUES (" + user.id + ")");
268
-			}
269
-
270
-			if (callback != null) {
271
-				callback.onFinish();
272
-			}
273
-		} catch (SQLException e) {
274
-			e.printStackTrace();
250
+	public void addUser(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
251
+		@Language("SQL") String sql;
252
+		if (user.getUsername() != null) {
253
+			sql = "INSERT OR IGNORE INTO user (id, username) " +
254
+					"VALUES (" + user.id + ", \"" + user.getUsername() + "\")";
255
+		} else {
256
+			sql = "INSERT OR IGNORE INTO user (id) " +
257
+					"VALUES (" + user.id + ")";
275 258
 		}
259
+
260
+		Log.v(getClass().getSimpleName(), "Adding user to db: " + user.id + " / " + user.getUsername());
261
+		UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
262
+		executor.start();
276 263
 	}
277 264
 
278 265
 	/**
279 266
 	 * Gets the chat history for a given time frame
280 267
 	 *
281
-	 * @param user     the user for which to retrieve the history
282
-	 * @param from     the starting date
283
-	 * @param to       the ending date
284
-	 * @param callback Function called when the request is done
268
+	 * @param user          the user for which to retrieve the history
269
+	 * @param from          the starting date
270
+	 * @param to            the ending date
271
+	 * @param callback      Function called when the request is done
272
+	 * @param errorCallback The function to call on error
285 273
 	 */
286
-	public void getChatHistory(UserInformation user, Date from, Date to, HistoryCallback callback) {
287
-		try {
288
-			Statement stmt = connection.createStatement();
289
-			String sql =
290
-					"SELECT timestamp, s.id AS sender_id, s.username AS sender_username, " +
291
-							"r.id AS recipient_id, r.username AS recipient_username, text, file_path " +
292
-							"FROM message JOIN user AS s ON sender = s.id " +
293
-							"             JOIN user AS r ON recipient = r.id " +
294
-							"WHERE (s.id = " + user.id + " OR r.id = " + user.id + ") AND " +
295
-							"timestamp > " + from.getTime() + " AND timestamp < " + to.getTime() + " " +
296
-							"ORDER BY timestamp";
297
-
298
-			Log.v(getClass().getSimpleName(), "Fetching chat history from db... ");
299
-			ResultSet res = stmt.executeQuery(sql);
300
-
274
+	public void getChatHistory(UserInformation user, Date from, Date to, HistoryCallback callback, ErrorCallback errorCallback) {
275
+		@Language("SQL") String sql =
276
+				"SELECT timestamp, s.id AS sender_id, s.username AS sender_username, " +
277
+						"r.id AS recipient_id, r.username AS recipient_username, text, file_path " +
278
+						"FROM message JOIN user AS s ON sender = s.id " +
279
+						"             JOIN user AS r ON recipient = r.id " +
280
+						"WHERE (s.id = " + user.id + " OR r.id = " + user.id + ") AND " +
281
+						"timestamp > " + from.getTime() + " AND timestamp < " + to.getTime() + " " +
282
+						"ORDER BY timestamp";
283
+
284
+		Log.v(getClass().getSimpleName(), "Fetching chat history from db... ");
285
+		QueryExecutor executor = new QueryExecutor(sql, res -> {
301 286
 			ArrayList<Message> chatHistory = new ArrayList<>();
302 287
 			while (res.next()) {
303 288
 				int sId = res.getInt("sender_id");
@@ -314,49 +299,107 @@ public class DatabaseController {
314 299
 					//  chatHistory.add(new FileMessage(new UserInformation(sId, sUsername), new UserInformation(rId, rUsername), date, text, filePath));
315 300
 				}
316 301
 			}
317
-			Log.v(getClass().getSimpleName(), chatHistory.size() + " rows fetched");
318
-			res.close();
319
-			stmt.close();
320
-			if (callback != null) {
321
-				callback.onHistoryFetched(chatHistory);
322
-			}
323
-		} catch (SQLException e) {
324
-			e.printStackTrace();
325
-		}
326
-	}
302
+			Log.v(getClass().getSimpleName(), chatHistory.size() + " messages fetched");
303
+			callback.onHistoryFetched(chatHistory);
304
+		}, errorCallback);
327 305
 
328
-	public void updateUsername(UserInformation user) {
329
-		try {
330
-			executeUpdate("UPDATE user SET " +
331
-					"username = '" + user.getUsername() + "' where id = " + user.id);
332
-		} catch (SQLException e) {
333
-			e.printStackTrace();
334
-		}
306
+		executor.start();
335 307
 	}
336 308
 
337
-	public void getUsername(int id, UsernameCallback callback) {
338
-		try {
339
-			Statement statement = connection.createStatement();
340
-			String sql = "SELECT username from user where id =" + id;
309
+	public void updateUsername(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
310
+		@Language("SQL") String sql = "UPDATE user SET username = '" + user.getUsername() + "' where id = " + user.id;
311
+		UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
312
+		executor.start();
313
+	}
341 314
 
342
-			Log.v(getClass().getSimpleName(), "Fetching users from db... ");
343
-			ResultSet res = statement.executeQuery(sql);
315
+	public void getUsername(int id, UsernameCallback callback, ErrorCallback errorCallback) {
316
+		@Language("SQL") String sql = "SELECT username from user where id =" + id;
344 317
 
318
+		Log.v(getClass().getSimpleName(), "Fetching username from db... ");
319
+		QueryExecutor executor = new QueryExecutor(sql, res -> {
345 320
 			String username = null;
321
+			int nbRows = 0;
346 322
 			if (res.next()) {
347 323
 				username = res.getString("username");
324
+				nbRows = 1;
348 325
 			}
349
-			Log.v(getClass().getSimpleName(), res.getFetchSize() + " rows fetched");
350
-			res.close();
351
-			statement.close();
352
-			if (callback != null) {
353
-				callback.onUsernameFetched(username);
326
+			Log.v(getClass().getSimpleName(), nbRows + " username fetched");
327
+			callback.onUsernameFetched(username);
328
+		}, errorCallback);
329
+		executor.start();
330
+	}
331
+
332
+	private class UpdateExecutor extends Thread {
333
+		private final String sqlQuery;
334
+		private final UpdateCallback callback;
335
+		private final ErrorCallback errorCallback;
336
+
337
+		/**
338
+		 * Constructs a thread that executes an update on the database
339
+		 *
340
+		 * @param sqlQuery      The query to execute
341
+		 * @param callback      The function to call on success
342
+		 * @param errorCallback The function to call on error
343
+		 */
344
+		public UpdateExecutor(@Language("SQL") String sqlQuery, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
345
+			this.sqlQuery = sqlQuery;
346
+			this.callback = callback;
347
+			this.errorCallback = errorCallback;
348
+		}
349
+
350
+		@Override
351
+		public void run() {
352
+			try {
353
+				Statement statement = connection.createStatement();
354
+				final int rowsModified = statement.executeUpdate(sqlQuery);
355
+				Log.v(getClass().getSimpleName(), rowsModified + " rows modified");
356
+				statement.close();
357
+				if (callback != null) {
358
+					callback.onUpdateExecuted();
359
+				}
360
+			} catch (SQLException e) {
361
+				Log.e(this.getClass().getSimpleName(), "Error executing update: ", e);
362
+				errorCallback.onError(e);
354 363
 			}
355
-		} catch (SQLException e) {
356
-			e.printStackTrace();
357 364
 		}
358 365
 	}
359 366
 
367
+	private class QueryExecutor extends Thread {
368
+		private final String sqlQuery;
369
+		private final QueryCallback callback;
370
+		private final ErrorCallback errorCallback;
371
+
372
+		/**
373
+		 * Constructs a thread that executes an update on the database
374
+		 *
375
+		 * @param sqlQuery      The query to execute
376
+		 * @param callback      The function to call on success
377
+		 * @param errorCallback The function to call on error
378
+		 */
379
+		public QueryExecutor(@Language("SQL") String sqlQuery, @Nullable QueryCallback callback, ErrorCallback errorCallback) {
380
+			this.sqlQuery = sqlQuery;
381
+			this.callback = callback;
382
+			this.errorCallback = errorCallback;
383
+		}
384
+
385
+		@Override
386
+		public void run() {
387
+			try {
388
+				Statement statement = connection.createStatement();
389
+				final ResultSet resultSet = statement.executeQuery(sqlQuery);
390
+				if (callback != null) {
391
+					callback.onQueryExecuted(resultSet);
392
+				}
393
+				resultSet.close();
394
+				statement.close();
395
+			} catch (SQLException e) {
396
+				Log.e(this.getClass().getSimpleName(), "Error executing update: ", e);
397
+				errorCallback.onError(e);
398
+			}
399
+		}
400
+	}
401
+
402
+
360 403
 	public interface UsersCallback {
361 404
 		void onUsersFetched(ArrayList<User> users);
362 405
 	}
@@ -369,7 +412,11 @@ public class DatabaseController {
369 412
 		void onHistoryFetched(ArrayList<Message> history);
370 413
 	}
371 414
 
372
-	public interface FinishCallback {
373
-		void onFinish();
415
+	public interface UpdateCallback {
416
+		void onUpdateExecuted();
417
+	}
418
+
419
+	private interface QueryCallback {
420
+		void onQueryExecuted(ResultSet resultSet) throws SQLException;
374 421
 	}
375 422
 }

+ 11
- 10
src/main/java/fr/insa/clavardator/ui/MainController.java View File

@@ -17,7 +17,6 @@ import javafx.fxml.Initializable;
17 17
 import javafx.scene.layout.StackPane;
18 18
 
19 19
 import java.io.IOException;
20
-import java.net.SocketException;
21 20
 import java.net.URL;
22 21
 import java.util.ResourceBundle;
23 22
 import java.util.Timer;
@@ -262,20 +261,22 @@ public class MainController implements Initializable {
262 261
 		toolbarController.setAboutListener(this::openAboutDialog);
263 262
 	}
264 263
 
264
+	private void onInitError(Exception e) {
265
+		Log.e("INIT", "Error during initialization", e);
266
+		showError();
267
+	}
268
+
265 269
 	/**
266 270
 	 * Creates database if needed, then init current user, and finally load user list
267 271
 	 */
268 272
 	private void initDb() {
269 273
 		final DatabaseController db = new DatabaseController();
270
-		db.init(() -> {
271
-			try {
272
-				Log.v("INIT", "Current user: " + currentUser.getId());
273
-				currentUser.init(() -> userList.retrievedPreviousUsers(this::onHistoryLoaded));
274
-			} catch (SocketException e) {
275
-				Log.e("INIT", "Could not init user", e);
276
-				showError();
277
-			}
278
-		});
274
+		db.initTables(() -> {
275
+			Log.v("INIT", "Current user: " + currentUser.getId());
276
+			currentUser.init(() -> {
277
+				userList.retrievedPreviousUsers(this::onHistoryLoaded, this::onInitError);
278
+			}, this::onInitError);
279
+		}, this::onInitError);
279 280
 	}
280 281
 
281 282
 	/**

+ 2
- 1
src/main/java/fr/insa/clavardator/ui/chat/ChatController.java View File

@@ -7,6 +7,7 @@ import fr.insa.clavardator.ui.LoadingScreenController;
7 7
 import fr.insa.clavardator.ui.NoSelectionModel;
8 8
 import fr.insa.clavardator.users.PeerUser;
9 9
 import fr.insa.clavardator.util.ErrorCallback;
10
+import fr.insa.clavardator.util.Log;
10 11
 import javafx.application.Platform;
11 12
 import javafx.collections.ListChangeListener;
12 13
 import javafx.fxml.FXML;
@@ -73,7 +74,7 @@ public class ChatController implements Initializable {
73 74
 			// Make sure we always have the latest item on screen
74 75
 			scrollToEnd();
75 76
 		});
76
-		history.load();
77
+		history.load(e -> Log.e(getClass().getSimpleName(), "Error while loading message", e)/* TODO: show an error message? */);
77 78
 	}
78 79
 
79 80
 	/**

+ 33
- 26
src/main/java/fr/insa/clavardator/users/CurrentUser.java View File

@@ -2,7 +2,9 @@ package fr.insa.clavardator.users;
2 2
 
3 3
 import fr.insa.clavardator.db.DatabaseController;
4 4
 import fr.insa.clavardator.network.NetUtil;
5
+import fr.insa.clavardator.util.ErrorCallback;
5 6
 import fr.insa.clavardator.util.Log;
7
+import org.jetbrains.annotations.Nullable;
6 8
 
7 9
 import java.net.InetAddress;
8 10
 import java.net.SocketException;
@@ -33,33 +35,38 @@ public class CurrentUser extends User {
33 35
 		return instance;
34 36
 	}
35 37
 
36
-	public void init(InitCallback callback) throws SocketException {
37
-		final List<InetAddress> addresses = NetUtil.listAllLocalAddresses();
38
-		// Save all addresses for this user
39
-		validIds = new ArrayList<>();
40
-		addresses.forEach(address -> validIds.add(getIdFromIp(address)));
41
-		if (validIds.size() > 0) {
42
-			// Set the first id as the main one
43
-			id = validIds.get(0);
44
-		} else {
45
-			throw new SocketException();
38
+	public void init(@Nullable InitCallback callback, ErrorCallback errorCallback) {
39
+		try {
40
+			final List<InetAddress> addresses = NetUtil.listAllLocalAddresses();
41
+			// Save all addresses for this user
42
+			validIds = new ArrayList<>();
43
+			addresses.forEach(address -> validIds.add(getIdFromIp(address)));
44
+			if (validIds.size() > 0) {
45
+				// Set the first id as the main one
46
+				id = validIds.get(0);
47
+			} else {
48
+				errorCallback.onError(new SocketException("No valid IP found"));
49
+				return;
50
+			}
51
+			final DatabaseController db = new DatabaseController();
52
+			// Make sure the user is in the db then set its username
53
+			db.addUser(new UserInformation(this), () -> {
54
+				db.getUsername(id, username -> {
55
+					if (username != null) {
56
+						Log.v(getClass().getSimpleName(), "Last username found : " + username);
57
+						setUsername(username);
58
+					} else {
59
+						Log.v(getClass().getSimpleName(), "No last username found, asking user...");
60
+						setState(State.NONE);
61
+					}
62
+					if (callback != null) {
63
+						callback.onFinish();
64
+					}
65
+				}, errorCallback);
66
+			}, errorCallback);
67
+		} catch (SocketException e) {
68
+			errorCallback.onError(e);
46 69
 		}
47
-		final DatabaseController db = new DatabaseController();
48
-		// Make sure the user is in the db then set its username
49
-		db.addUser(new UserInformation(this), () -> {
50
-			db.getUsername(id, username -> {
51
-				if (username != null) {
52
-					Log.v(getClass().getSimpleName(), "Last username found : " + username);
53
-					setUsername(username);
54
-				} else {
55
-					Log.v(getClass().getSimpleName(), "No last username found, asking user...");
56
-					setState(State.NONE);
57
-				}
58
-				if (callback != null) {
59
-					callback.onFinish();
60
-				}
61
-			});
62
-		});
63 70
 	}
64 71
 
65 72
 	@Override

+ 4
- 5
src/main/java/fr/insa/clavardator/users/PeerUser.java View File

@@ -76,10 +76,7 @@ public class PeerUser extends User implements Comparable<PeerUser> {
76 76
 			Log.v(this.getClass().getSimpleName(),
77 77
 					"Sending message to " + this.getUsername() + " / " + this.getId() + ": " + msg);
78 78
 			final Message message = new Message(CurrentUser.getInstance(), this, new Date(), msg);
79
-			connection.send(
80
-					message,
81
-					() -> history.addMessage(message),
82
-					errorCallback);
79
+			connection.send(message, () -> history.addMessage(message, errorCallback), errorCallback);
83 80
 		} else {
84 81
 			Log.e(this.getClass().getSimpleName(), "Could not send message: connection is not initialized");
85 82
 		}
@@ -175,8 +172,10 @@ public class PeerUser extends User implements Comparable<PeerUser> {
175 172
 						}
176 173
 					} else if (msg instanceof Message) {
177 174
 						assert ((Message) msg).getRecipient().id != id;
175
+						assert CurrentUser.getInstance().isLocalId(((Message) msg).getRecipient().id);
176
+
178 177
 						Log.v(this.getClass().getSimpleName(), "Message text: " + ((Message) msg).getText());
179
-						history.addMessage((Message) msg);
178
+						history.addMessage((Message) msg, errorCallback);
180 179
 					} else if (msg instanceof UsernameTakenException) {
181 180
 						disconnect();
182 181
 						errorCallback.onError(new Exception("Received username already taken message"));

+ 2
- 1
src/main/java/fr/insa/clavardator/users/User.java View File

@@ -1,6 +1,7 @@
1 1
 package fr.insa.clavardator.users;
2 2
 
3 3
 import fr.insa.clavardator.db.DatabaseController;
4
+import fr.insa.clavardator.util.Log;
4 5
 
5 6
 import java.beans.PropertyChangeListener;
6 7
 import java.beans.PropertyChangeSupport;
@@ -44,7 +45,7 @@ public class User implements Serializable {
44 45
 		pcs.firePropertyChange("username", this.username, newUsername);
45 46
 		this.username = newUsername;
46 47
 		final DatabaseController db = new DatabaseController();
47
-		db.updateUsername(new UserInformation(this));
48
+		db.updateUsername(new UserInformation(this), null, e -> Log.e(getClass().getSimpleName(), "Unable to update the username", e));
48 49
 	}
49 50
 
50 51
 	@Override

+ 2
- 3
src/main/java/fr/insa/clavardator/users/UserList.java View File

@@ -7,7 +7,6 @@ import fr.insa.clavardator.util.ErrorCallback;
7 7
 import fr.insa.clavardator.util.Log;
8 8
 
9 9
 import java.beans.PropertyChangeEvent;
10
-import java.util.ArrayList;
11 10
 import java.util.HashMap;
12 11
 import java.util.Map;
13 12
 import java.util.function.Predicate;
@@ -78,11 +77,11 @@ public class UserList {
78 77
 				Throwable::printStackTrace);
79 78
 	}
80 79
 
81
-	public void retrievedPreviousUsers(UserListLoadedCallback onFinish) {
80
+	public void retrievedPreviousUsers(UserListLoadedCallback onFinish, ErrorCallback errorCallback) {
82 81
 		db.getAllUsers(users -> {
83 82
 			users.forEach(user -> createNewUser(user.id, user.getUsername()));
84 83
 			onFinish.onLoaded();
85
-		});
84
+		}, errorCallback);
86 85
 	}
87 86
 
88 87
 	/**

+ 81
- 36
src/test/java/fr/insa/clavardator/DatabaseTest.java View File

@@ -3,56 +3,101 @@ package fr.insa.clavardator;
3 3
 import fr.insa.clavardator.chat.Message;
4 4
 import fr.insa.clavardator.db.DatabaseController;
5 5
 import fr.insa.clavardator.users.UserInformation;
6
+import org.junit.jupiter.api.Assertions;
6 7
 import org.junit.jupiter.api.Test;
7 8
 
9
+import java.time.Duration;
8 10
 import java.util.Date;
11
+import java.util.concurrent.CountDownLatch;
9 12
 
10
-import static org.junit.jupiter.api.Assertions.assertEquals;
13
+import static org.junit.jupiter.api.Assertions.*;
11 14
 
12 15
 public class DatabaseTest {
13 16
 	private final DatabaseController db = new DatabaseController(true);
14 17
 
15 18
 	@Test
16 19
 	void testDB() {
17
-		db.resetTables();
18
-		db.getAllUsers(users -> {
19
-			assertEquals(0, users.size());
20
-		});
20
+		assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
21
+			CountDownLatch latch1 = new CountDownLatch(1);
22
+			db.resetTables(latch1::countDown, Assertions::fail);
23
+			latch1.await();
21 24
 
22
-		db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
23
-			assertEquals(history.size(), 0);
24
-		});
25
+			CountDownLatch latch2 = new CountDownLatch(2);
26
+			db.getAllUsers(users -> {
27
+				assertEquals(0, users.size());
28
+				latch2.countDown();
29
+			}, Assertions::fail);
25 30
 
31
+			db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
32
+				assertEquals(history.size(), 0);
33
+				latch2.countDown();
34
+			}, Assertions::fail);
35
+			latch2.await();
26 36
 
27
-		db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), new Date(), "Coucou Arnaud !"), null);
28
-		db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"), new Date(), "Coucou Yohan !"), null);
29
-		db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), new Date(), "Ça va ?"), null);
30
-		db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"), new Date(), "Ouais et toi ?"), null);
31
-		db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), new Date(), "Super !"), null);
32
-		db.getAllUsers(users -> {
33
-			assertEquals(2, users.size());
34
-			assertEquals(1, users.get(0).getId());
35
-			assertEquals(2, users.get(1).getId());
36
-			assertEquals("Yohan", users.get(0).getUsername());
37
-			assertEquals("Arnaud", users.get(1).getUsername());
38
-		});
39
-		db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
40
-			assertEquals(5, history.size());
41
-			assertEquals("Coucou Arnaud !", history.get(0).getText());
42
-			assertEquals(1, history.get(0).getSender().id);
43
-			assertEquals(2, history.get(0).getRecipient().id);
44
-			assertEquals("Yohan", history.get(0).getSender().getUsername());
45
-			assertEquals("Arnaud", history.get(0).getRecipient().getUsername());
46
-
47
-			assertEquals("Ouais et toi ?", history.get(3).getText());
48
-			assertEquals(2, history.get(3).getSender().id);
49
-			assertEquals(1, history.get(3).getRecipient().id);
50
-			assertEquals("Arnaud", history.get(3).getSender().getUsername());
51
-			assertEquals("Yohan", history.get(3).getRecipient().getUsername());
52
-		});
37
+			CountDownLatch latch8 = new CountDownLatch(1);
38
+			db.addUser(new UserInformation(3, null), latch8::countDown, Assertions::fail);
39
+			latch8.await();
40
+
41
+
42
+			CountDownLatch latch3 = new CountDownLatch(5);
43
+			db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"),
44
+					new Date(1609843556860L), "Coucou Arnaud !"), latch3::countDown, Assertions::fail);
45
+			db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"),
46
+					new Date(1609843556861L), "Coucou Yohan !"), latch3::countDown, Assertions::fail);
47
+			db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"),
48
+					new Date(1609843556862L), "Ça va ?"), latch3::countDown, Assertions::fail);
49
+			db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"),
50
+					new Date(1609843556863L), "Ouais et toi ?"), latch3::countDown, Assertions::fail);
51
+			db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"),
52
+					new Date(1609843556864L), "Super !"), latch3::countDown, Assertions::fail);
53
+			latch3.await();
54
+
55
+			CountDownLatch latch4 = new CountDownLatch(2);
56
+			db.getAllUsers(users -> {
57
+				assertEquals(3, users.size());
58
+				assertEquals(3, users.get(0).getId());
59
+				assertEquals(1, users.get(1).getId());
60
+				assertEquals(2, users.get(2).getId());
61
+				assertNull(users.get(0).getUsername());
62
+				assertEquals("Yohan", users.get(1).getUsername());
63
+				assertEquals("Arnaud", users.get(2).getUsername());
64
+				latch4.countDown();
65
+			}, Assertions::fail);
53 66
 
54
-		db.resetTables();
55
-		db.close();
67
+			db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
68
+				assertEquals(5, history.size());
69
+				assertEquals("Coucou Arnaud !", history.get(0).getText());
70
+				assertEquals(1, history.get(0).getSender().id);
71
+				assertEquals(2, history.get(0).getRecipient().id);
72
+				assertEquals("Yohan", history.get(0).getSender().getUsername());
73
+				assertEquals("Arnaud", history.get(0).getRecipient().getUsername());
74
+
75
+				assertEquals("Ouais et toi ?", history.get(3).getText());
76
+				assertEquals(2, history.get(3).getSender().id);
77
+				assertEquals(1, history.get(3).getRecipient().id);
78
+				assertEquals("Arnaud", history.get(3).getSender().getUsername());
79
+				assertEquals("Yohan", history.get(3).getRecipient().getUsername());
80
+				latch4.countDown();
81
+			}, Assertions::fail);
82
+			latch4.await();
83
+
84
+			CountDownLatch latch5 = new CountDownLatch(1);
85
+			db.updateUsername(new UserInformation(2, "Toto"), latch5::countDown, Assertions::fail);
86
+			latch5.await();
87
+
88
+			CountDownLatch latch6 = new CountDownLatch(1);
89
+			db.getUsername(2, username -> {
90
+				assertEquals("Toto", username);
91
+				latch6.countDown();
92
+			}, Assertions::fail);
93
+			latch6.await();
94
+
95
+			CountDownLatch latch7 = new CountDownLatch(1);
96
+			db.resetTables(latch7::countDown, Assertions::fail);
97
+			latch7.await();
98
+
99
+			db.close();
100
+		});
56 101
 	}
57 102
 
58 103
 

Loading…
Cancel
Save