package telegram.files; import cn.hutool.core.lang.Version; import cn.hutool.core.util.StrUtil; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; import io.vertx.core.AbstractVerticle; import io.vertx.core.Future; import io.vertx.core.Promise; import io.vertx.jdbcclient.JDBCConnectOptions; import io.vertx.jdbcclient.JDBCPool; import io.vertx.sqlclient.PoolOptions; import io.vertx.sqlclient.SqlConnection; import org.jooq.lambda.tuple.Tuple; import telegram.files.repository.*; import telegram.files.repository.impl.FileRepositoryImpl; import telegram.files.repository.impl.SettingRepositoryImpl; import telegram.files.repository.impl.StatisticRepositoryImpl; import telegram.files.repository.impl.TelegramRepositoryImpl; import java.io.File; import java.util.List; public class DataVerticle extends AbstractVerticle { private static final Log log = LogFactory.get(); public static JDBCPool pool; public static FileRepository fileRepository; public static TelegramRepository telegramRepository; public static SettingRepository settingRepository; public static StatisticRepository statisticRepository; public void start(Promise stopPromise) { pool = JDBCPool.pool(vertx, new JDBCConnectOptions() .setJdbcUrl("jdbc:sqlite:%s?journal_mode=WAL&busy_timeout=30000&synchronous=NORMAL&cache_size=-2000".formatted(getDataPath())), new PoolOptions() .setMaxSize(8) .setName("pool-tf") .setIdleTimeout(300000) .setPoolCleanerPeriod(300000) ); settingRepository = new SettingRepositoryImpl(pool); telegramRepository = new TelegramRepositoryImpl(pool); fileRepository = new FileRepositoryImpl(pool); statisticRepository = new StatisticRepositoryImpl(pool); List definitions = List.of( new SettingRecord.SettingRecordDefinition(), new TelegramRecord.TelegramRecordDefinition(), new FileRecord.FileRecordDefinition(), new StatisticRecord.StatisticRecordDefinition() ); pool.getConnection() .compose(conn -> isCompletelyNewInitialization(conn).map(isNew -> Tuple.tuple(conn, isNew))) .compose(tuple -> Future.all(definitions.stream().map(d -> d.createTable(tuple.v1)).toList()).map(tuple)) .compose(tuple -> settingRepository.getByKey(SettingKey.version).map(tuple::concat)) .compose(tuple -> { if (tuple.v2) return Future.succeededFuture(); SqlConnection conn = tuple.v1; Version version = tuple.v3 == null ? new Version("0.0.0") : tuple.v3; return Future.all(definitions.stream().map(d -> d.migrate(conn, version, new Version(Start.VERSION))).toList()); }) .compose(r -> settingRepository.createOrUpdate(SettingKey.version.name(), Start.VERSION)) .onSuccess(r -> { log.info("Database initialized"); stopPromise.complete(); }) .onFailure(err -> { log.error("Failed to initialize database: %s".formatted(err.getMessage())); stopPromise.fail(err); }); } @Override public void stop(Promise stopPromise) throws Exception { if (pool != null) { pool.close().onComplete(r -> { if (r.succeeded()) { log.debug("Data verticle stopped!"); } else { log.error("Failed to close data verticle: %s".formatted(r.cause().getMessage())); } stopPromise.complete(); }); } } public static String getDataPath() { String dataPath = System.getenv("DATA_PATH"); dataPath = StrUtil.blankToDefault(dataPath, "data.db"); dataPath = Config.APP_ROOT + File.separator + dataPath; return dataPath; } private Future isCompletelyNewInitialization(SqlConnection conn) { return conn.query(""" SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name; """) .execute() .map(rs -> rs.size() == 0); } }