]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/Database.java
a55a8eed0051a0d39571ea17a834527876032207
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / Database.java
1 package org.asamk.signal.manager.storage;
2
3 import com.zaxxer.hikari.HikariConfig;
4 import com.zaxxer.hikari.HikariDataSource;
5
6 import org.slf4j.Logger;
7 import org.sqlite.SQLiteConfig;
8
9 import java.io.File;
10 import java.sql.Connection;
11 import java.sql.SQLException;
12 import java.util.function.Function;
13
14 public abstract class Database implements AutoCloseable {
15
16 private final Logger logger;
17 private final long databaseVersion;
18 private final HikariDataSource dataSource;
19
20 protected Database(final Logger logger, final long databaseVersion, final HikariDataSource dataSource) {
21 this.logger = logger;
22 this.databaseVersion = databaseVersion;
23 this.dataSource = dataSource;
24 }
25
26 public static <T extends Database> T initDatabase(
27 File databaseFile, Function<HikariDataSource, T> newDatabase
28 ) throws SQLException {
29 HikariDataSource dataSource = null;
30
31 try {
32 dataSource = getHikariDataSource(databaseFile.getAbsolutePath());
33
34 final var result = newDatabase.apply(dataSource);
35 result.initDb();
36 dataSource = null;
37 return result;
38 } finally {
39 if (dataSource != null) {
40 dataSource.close();
41 }
42 }
43 }
44
45 public final Connection getConnection() throws SQLException {
46 return dataSource.getConnection();
47 }
48
49 @Override
50 public void close() throws SQLException {
51 dataSource.close();
52 }
53
54 protected final void initDb() throws SQLException {
55 try (final var connection = dataSource.getConnection()) {
56 connection.setAutoCommit(false);
57 final var userVersion = getUserVersion(connection);
58 logger.trace("Current database version: {} Program database version: {}", userVersion, databaseVersion);
59
60 if (userVersion == 0) {
61 createDatabase(connection);
62 setUserVersion(connection, databaseVersion);
63 } else if (userVersion > databaseVersion) {
64 logger.error("Database has been updated by a newer signal-cli version");
65 throw new SQLException("Database has been updated by a newer signal-cli version");
66 } else if (userVersion < databaseVersion) {
67 upgradeDatabase(connection, userVersion);
68 setUserVersion(connection, databaseVersion);
69 }
70 connection.commit();
71 }
72 }
73
74 protected abstract void createDatabase(final Connection connection) throws SQLException;
75
76 protected abstract void upgradeDatabase(final Connection connection, long oldVersion) throws SQLException;
77
78 private static long getUserVersion(final Connection connection) throws SQLException {
79 try (final var statement = connection.createStatement()) {
80 final var resultSet = statement.executeQuery("PRAGMA user_version");
81 return resultSet.getLong(1);
82 }
83 }
84
85 private static void setUserVersion(final Connection connection, long userVersion) throws SQLException {
86 try (final var statement = connection.createStatement()) {
87 statement.executeUpdate("PRAGMA user_version = " + userVersion);
88 }
89 }
90
91 private static HikariDataSource getHikariDataSource(final String databaseFile) {
92 final var sqliteConfig = new SQLiteConfig();
93 sqliteConfig.setBusyTimeout(10_000);
94 sqliteConfig.setTransactionMode(SQLiteConfig.TransactionMode.IMMEDIATE);
95
96 HikariConfig config = new HikariConfig();
97 config.setJdbcUrl("jdbc:sqlite:" + databaseFile);
98 config.setDataSourceProperties(sqliteConfig.toProperties());
99 config.setMinimumIdle(1);
100 config.setConnectionInitSql("PRAGMA foreign_keys=ON");
101 return new HikariDataSource(config);
102 }
103 }