]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/Database.java
5099662ef21e74bedb19641be9da76b221136a7e
[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,
28 Function<HikariDataSource, T> newDatabase
29 ) throws SQLException {
30 HikariDataSource dataSource = null;
31
32 try {
33 dataSource = getHikariDataSource(databaseFile.getAbsolutePath());
34
35 final var result = newDatabase.apply(dataSource);
36 result.initDb();
37 dataSource = null;
38 return result;
39 } finally {
40 if (dataSource != null) {
41 dataSource.close();
42 }
43 }
44 }
45
46 public final Connection getConnection() throws SQLException {
47 return dataSource.getConnection();
48 }
49
50 @Override
51 public void close() {
52 dataSource.close();
53 }
54
55 protected final void initDb() throws SQLException {
56 try (final var connection = dataSource.getConnection()) {
57 connection.setAutoCommit(false);
58 final var userVersion = getUserVersion(connection);
59 logger.trace("Current database version: {} Program database version: {}", userVersion, databaseVersion);
60
61 if (userVersion == 0) {
62 createDatabase(connection);
63 setUserVersion(connection, databaseVersion);
64 } else if (userVersion > databaseVersion) {
65 logger.error("Database has been updated by a newer signal-cli version");
66 throw new SQLException("Database has been updated by a newer signal-cli version");
67 } else if (userVersion < databaseVersion) {
68 upgradeDatabase(connection, userVersion);
69 setUserVersion(connection, databaseVersion);
70 }
71 connection.commit();
72 }
73 }
74
75 protected abstract void createDatabase(final Connection connection) throws SQLException;
76
77 protected abstract void upgradeDatabase(final Connection connection, long oldVersion) throws SQLException;
78
79 private static long getUserVersion(final Connection connection) throws SQLException {
80 try (final var statement = connection.createStatement()) {
81 final var resultSet = statement.executeQuery("PRAGMA user_version");
82 return resultSet.getLong(1);
83 }
84 }
85
86 private static void setUserVersion(final Connection connection, long userVersion) throws SQLException {
87 try (final var statement = connection.createStatement()) {
88 statement.executeUpdate("PRAGMA user_version = " + userVersion);
89 }
90 }
91
92 private static HikariDataSource getHikariDataSource(final String databaseFile) {
93 final var sqliteConfig = new SQLiteConfig();
94 sqliteConfig.setBusyTimeout(60_000);
95 sqliteConfig.setTransactionMode(SQLiteConfig.TransactionMode.IMMEDIATE);
96
97 HikariConfig config = new HikariConfig();
98 config.setJdbcUrl("jdbc:sqlite:" + databaseFile + "?foreign_keys=ON&journal_mode=wal");
99 config.setDataSourceProperties(sqliteConfig.toProperties());
100 config.setMinimumIdle(1);
101 return new HikariDataSource(config);
102 }
103 }