Added a way to update and setup languages

Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
Louis Vallat 2020-10-27 21:38:19 +01:00
parent ee08b6b29c
commit 8f1ae7a5fe
6 changed files with 210 additions and 42 deletions

View File

@ -1,9 +1,9 @@
DROP TABLE IF EXISTS application, film, language, subtitle_line, subtitle; DROP TABLE IF EXISTS properties, films, languages, subtitle_lines, subtitles;
/** /**
Store some information on the application. Store some information on the application.
*/ */
CREATE TABLE IF NOT EXISTS application CREATE TABLE IF NOT EXISTS properties
( (
id int GENERATED ALWAYS AS IDENTITY, id int GENERATED ALWAYS AS IDENTITY,
app_key text NOT NULL, app_key text NOT NULL,
@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS application
/** /**
Film Film
*/ */
CREATE TABLE IF NOT EXISTS film CREATE TABLE IF NOT EXISTS films
( (
id int GENERATED ALWAYS AS IDENTITY, id int GENERATED ALWAYS AS IDENTITY,
imdb_id varchar(10) NOT NULL, imdb_id varchar(10) NOT NULL,
@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS film
/** /**
Available languages Available languages
*/ */
CREATE TABLE IF NOT EXISTS language CREATE TABLE IF NOT EXISTS languages
( (
id int GENERATED ALWAYS AS IDENTITY, id int GENERATED ALWAYS AS IDENTITY,
alpha3_b char(3) NOT NULL, alpha3_b char(3) NOT NULL,
@ -36,13 +36,14 @@ CREATE TABLE IF NOT EXISTS language
alpha2 char(2), alpha2 char(2),
english text NOT NULL, english text NOT NULL,
french text NOT NULL, french text NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id),
UNIQUE (alpha3_b)
); );
/** /**
Subtitles Subtitles
*/ */
CREATE TABLE IF NOT EXISTS subtitle CREATE TABLE IF NOT EXISTS subtitles
( (
id int GENERATED ALWAYS AS IDENTITY, id int GENERATED ALWAYS AS IDENTITY,
film_id int NOT NULL, film_id int NOT NULL,
@ -52,15 +53,15 @@ CREATE TABLE IF NOT EXISTS subtitle
UNIQUE (film_id, language_id), UNIQUE (film_id, language_id),
PRIMARY KEY (id), PRIMARY KEY (id),
FOREIGN KEY (film_id) FOREIGN KEY (film_id)
REFERENCES film (id), REFERENCES films (id),
FOREIGN KEY (language_id) FOREIGN KEY (language_id)
REFERENCES language (id) REFERENCES languages (id)
); );
/** /**
Subtitle lines Subtitle lines
*/ */
CREATE TABLE IF NOT EXISTS subtitle_line CREATE TABLE IF NOT EXISTS subtitle_lines
( (
id int GENERATED ALWAYS AS IDENTITY, id int GENERATED ALWAYS AS IDENTITY,
subtitle_id int NOT NULL, subtitle_id int NOT NULL,
@ -68,5 +69,5 @@ CREATE TABLE IF NOT EXISTS subtitle_line
time_code text NOT NULL, time_code text NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
FOREIGN KEY (subtitle_id) FOREIGN KEY (subtitle_id)
REFERENCES subtitle (id) REFERENCES subtitles (id)
); );

View File

@ -34,6 +34,7 @@ dependencies {
implementation 'com.discord4j:discord4j-core:3.1.1' implementation 'com.discord4j:discord4j-core:3.1.1'
implementation 'com.github.wtekiela:opensub4j:0.3.0' implementation 'com.github.wtekiela:opensub4j:0.3.0'
implementation 'org.apache.commons:commons-lang3:3.11' implementation 'org.apache.commons:commons-lang3:3.11'
implementation 'org.apache.commons:commons-csv:1.8'
implementation 'ch.qos.logback:logback-classic:1.2.3' implementation 'ch.qos.logback:logback-classic:1.2.3'
implementation 'org.postgresql:postgresql:42.2.18.jre7' implementation 'org.postgresql:postgresql:42.2.18.jre7'
} }

View File

@ -43,6 +43,7 @@ public class MovieQuoteBot {
public static void main(String[] args) { public static void main(String[] args) {
DBManager.testConnection(); DBManager.testConnection();
DBManager.initDatabase();
// TODO: FIX CRASH ON LOGIN IF OS IS IN MAINTENANCE OR BROKEN // TODO: FIX CRASH ON LOGIN IF OS IS IN MAINTENANCE OR BROKEN
OpenSubtitles.login( OpenSubtitles.login(
System.getenv(OS_USERNAME_ENVIRONMENT), System.getenv(OS_USERNAME_ENVIRONMENT),

View File

@ -4,11 +4,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import xyz.vallat.louis.MovieQuoteBot; import xyz.vallat.louis.MovieQuoteBot;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import static xyz.vallat.louis.database.LanguageManager.importLanguageIfNeeded;
import static xyz.vallat.louis.database.LanguageManager.initializeLanguages;
public final class DBManager { public final class DBManager {
public static final String DB_USERNAME_ENVIRONMENT = "DB_USERNAME"; public static final String DB_USERNAME_ENVIRONMENT = "DB_USERNAME";
@ -17,7 +22,6 @@ public final class DBManager {
public static final String DB_HOST_ENVIRONMENT = "DB_HOST"; public static final String DB_HOST_ENVIRONMENT = "DB_HOST";
public static final String DB_NAME_ENVIRONMENT = "DB_NAME"; public static final String DB_NAME_ENVIRONMENT = "DB_NAME";
private static final Logger logger = LoggerFactory.getLogger(MovieQuoteBot.class.getCanonicalName()); private static final Logger logger = LoggerFactory.getLogger(MovieQuoteBot.class.getCanonicalName());
private static final String DRIVER_CLASS = "org.postgresql.Driver";
private static final String CONNECTION_PREFIX = "postgresql"; private static final String CONNECTION_PREFIX = "postgresql";
private static final String USERNAME = System.getenv(DB_USERNAME_ENVIRONMENT); private static final String USERNAME = System.getenv(DB_USERNAME_ENVIRONMENT);
private static final String PASSWORD = System.getenv(DB_PASSWORD_ENVIRONMENT); private static final String PASSWORD = System.getenv(DB_PASSWORD_ENVIRONMENT);
@ -32,10 +36,9 @@ public final class DBManager {
// TODO: EXIT CODES AS ENUM // TODO: EXIT CODES AS ENUM
private static Connection getConnection() { private static Connection getConnection() {
try { try {
Class.forName(DRIVER_CLASS);
return DriverManager.getConnection("jdbc:" + CONNECTION_PREFIX + "://" + HOST + ":" + PORT + "/" return DriverManager.getConnection("jdbc:" + CONNECTION_PREFIX + "://" + HOST + ":" + PORT + "/"
+ DATABASE, USERNAME, PASSWORD); + DATABASE, USERNAME, PASSWORD);
} catch (ClassNotFoundException | SQLException e) { } catch (SQLException e) {
logger.error("Could not connect to database. Reason: {}.", e.getMessage()); logger.error("Could not connect to database. Reason: {}.", e.getMessage());
System.exit(4); System.exit(4);
} }
@ -52,21 +55,24 @@ public final class DBManager {
logger.debug("Initializing database if not done yet."); logger.debug("Initializing database if not done yet.");
Connection connection = getConnection(); Connection connection = getConnection();
try { try {
initializeApplication(connection); initializeProperties(connection);
initializeLanguages(connection);
initializeFilm(connection); initializeFilm(connection);
initializeSubtitle(connection); initializeSubtitle(connection);
initializeSubtitleLine(connection); initializeSubtitleLine(connection);
initializeLanguages(connection); importLanguageIfNeeded(connection);
} catch (SQLException e) { } catch (SQLException e) {
logger.error("An error happened while initializing the database. Reason: {}", e.getMessage()); logger.error("An error happened while initializing the database. Reason: {}", e.getMessage());
System.exit(5); System.exit(5);
} catch (NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
} }
} }
private static void initializeApplication(Connection connection) throws SQLException { private static void initializeProperties(Connection connection) throws SQLException {
logger.debug("Creating application table."); logger.debug("Creating properties table.");
try (Statement stmt = connection.createStatement()) { try (Statement stmt = connection.createStatement()) {
String query = "CREATE TABLE IF NOT EXISTS application\n" + String query = "CREATE TABLE IF NOT EXISTS properties\n" +
"(\n" + "(\n" +
" id int GENERATED ALWAYS AS IDENTITY,\n" + " id int GENERATED ALWAYS AS IDENTITY,\n" +
" app_key text NOT NULL,\n" + " app_key text NOT NULL,\n" +
@ -81,7 +87,7 @@ public final class DBManager {
private static void initializeFilm(Connection connection) throws SQLException { private static void initializeFilm(Connection connection) throws SQLException {
logger.debug("Creating film table."); logger.debug("Creating film table.");
try (Statement stmt = connection.createStatement()) { try (Statement stmt = connection.createStatement()) {
String query = "CREATE TABLE IF NOT EXISTS film\n" + String query = "CREATE TABLE IF NOT EXISTS films\n" +
"(\n" + "(\n" +
" id int GENERATED ALWAYS AS IDENTITY,\n" + " id int GENERATED ALWAYS AS IDENTITY,\n" +
" imdb_id varchar(10) NOT NULL,\n" + " imdb_id varchar(10) NOT NULL,\n" +
@ -97,7 +103,7 @@ public final class DBManager {
private static void initializeSubtitle(Connection connection) throws SQLException { private static void initializeSubtitle(Connection connection) throws SQLException {
logger.debug("Creating subtitle table."); logger.debug("Creating subtitle table.");
try (Statement stmt = connection.createStatement()) { try (Statement stmt = connection.createStatement()) {
String query = "CREATE TABLE IF NOT EXISTS subtitle\n" + String query = "CREATE TABLE IF NOT EXISTS subtitles\n" +
"(\n" + "(\n" +
" id int GENERATED ALWAYS AS IDENTITY,\n" + " id int GENERATED ALWAYS AS IDENTITY,\n" +
" film_id int NOT NULL,\n" + " film_id int NOT NULL,\n" +
@ -107,9 +113,9 @@ public final class DBManager {
" UNIQUE (film_id, language_id),\n" + " UNIQUE (film_id, language_id),\n" +
" PRIMARY KEY (id),\n" + " PRIMARY KEY (id),\n" +
" FOREIGN KEY (film_id)\n" + " FOREIGN KEY (film_id)\n" +
" REFERENCES Film (id),\n" + " REFERENCES films (id),\n" +
" FOREIGN KEY (language_id)\n" + " FOREIGN KEY (language_id)\n" +
" REFERENCES language (id)\n" + " REFERENCES languages (id)\n" +
");"; ");";
stmt.executeUpdate(query); stmt.executeUpdate(query);
} }
@ -118,7 +124,7 @@ public final class DBManager {
private static void initializeSubtitleLine(Connection connection) throws SQLException { private static void initializeSubtitleLine(Connection connection) throws SQLException {
logger.debug("Creating subtitle_line table."); logger.debug("Creating subtitle_line table.");
try (Statement stmt = connection.createStatement()) { try (Statement stmt = connection.createStatement()) {
String query = "CREATE TABLE IF NOT EXISTS subtitle_line\n" + String query = "CREATE TABLE IF NOT EXISTS subtitle_lines\n" +
"(\n" + "(\n" +
" id int GENERATED ALWAYS AS IDENTITY,\n" + " id int GENERATED ALWAYS AS IDENTITY,\n" +
" subtitle_id int NOT NULL,\n" + " subtitle_id int NOT NULL,\n" +
@ -126,24 +132,7 @@ public final class DBManager {
" time_code text NOT NULL,\n" + " time_code text NOT NULL,\n" +
" PRIMARY KEY (id),\n" + " PRIMARY KEY (id),\n" +
" FOREIGN KEY (subtitle_id)\n" + " FOREIGN KEY (subtitle_id)\n" +
" REFERENCES subtitle (id)\n" + " REFERENCES subtitles (id)\n" +
");";
stmt.executeUpdate(query);
}
}
private static void initializeLanguages(Connection connection) throws SQLException {
logger.debug("Creating languages table.");
try (Statement stmt = connection.createStatement()) {
String query = "CREATE TABLE IF NOT EXISTS language\n" +
"(\n" +
" id int GENERATED ALWAYS AS IDENTITY,\n" +
" alpha3_b char(3) NOT NULL,\n" +
" alpha3_t char(3),\n" +
" alpha2 char(2),\n" +
" english text NOT NULL,\n" +
" french text NOT NULL,\n" +
" PRIMARY KEY (id)\n" +
");"; ");";
stmt.executeUpdate(query); stmt.executeUpdate(query);
} }

View File

@ -0,0 +1,176 @@
package xyz.vallat.louis.database;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public final class LanguageManager {
private static final Logger logger = LoggerFactory.getLogger(LanguageManager.class.getCanonicalName());
private static final String HASH_KEY = "languages_hash";
private static final String LANGUAGES_FILE_NAME = "language-codes-full_csv.csv";
private LanguageManager() {
}
public static void importLanguageIfNeeded(Connection connection) throws SQLException, IOException, NoSuchAlgorithmException {
logger.debug("Checking if we need to import languages again.");
String storedHash = getStoredHash(connection);
logger.debug("Stored hash was '{}'.", storedHash);
String actualHash = getActualHash();
logger.debug("Actual hash is '{}'.", actualHash);
logger.info("Importing new language file.");
if (!storedHash.equals(actualHash)) importLanguageFile(connection);
logger.debug("Saving new hash in database.");
saveNewHash(connection, actualHash);
}
private static void saveNewHash(Connection connection, String actualHash) throws SQLException {
String query;
if (doesPropertyExist(connection, HASH_KEY)) query = "UPDATE properties SET app_value = ? WHERE app_key = ?;";
else query = "INSERT INTO properties(app_value, app_key) VALUES (?, ?)";
try (PreparedStatement stmt = connection.prepareStatement(query)) {
stmt.setString(1, actualHash);
stmt.setString(2, HASH_KEY);
stmt.execute();
}
}
private static boolean doesPropertyExist(Connection connection, String key) throws SQLException {
String query = "SELECT id FROM properties WHERE app_key=?;";
try (PreparedStatement stmt = connection.prepareStatement(query)){
stmt.setString(1, key);
stmt.execute();
return stmt.getResultSet().next();
}
}
private static void importLanguageFile(Connection connection) throws IOException, SQLException {
logger.debug("Reading and parsing CSV file.");
connection.setAutoCommit(false);
InputStream csvFile = LanguageManager.class.getClassLoader().getResourceAsStream(LANGUAGES_FILE_NAME);
assert csvFile != null;
CSVParser csvParser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(new InputStreamReader(csvFile));
for (CSVRecord record : csvParser)
importSanitizedLangInDatabase(connection,
record.get("alpha3-b"),
record.get("alpha3-t"),
record.get("alpha2"),
record.get("English"),
record.get("French")
);
try {
logger.debug("Committing changes.");
connection.commit();
} catch (SQLException e) {
logger.debug("An error occurred while committing the transaction. Rolling back. " +
"Reason: {}", e.getMessage());
connection.rollback();
} finally {
connection.setAutoCommit(true);
}
}
private static void importSanitizedLangInDatabase(Connection connection, String alpha3b, String alpha3t,
String alpha2b, String english, String french) throws SQLException {
if (alpha3b.length() > 3) {
for (String newAlpha3b : alpha3b.split(String.valueOf(alpha3b.charAt(3))))
importSanitizedLangInDatabase(connection, newAlpha3b, alpha3t, alpha2b, english, french);
} else if (alpha3t.length() > 3) {
for (String newAlpha3t : alpha3t.split(String.valueOf(alpha3t.charAt(3))))
importSanitizedLangInDatabase(connection, alpha3t, newAlpha3t, alpha2b, english, french);
} else if (alpha2b.length() > 2) {
for (String newAlpha2b : alpha2b.split(String.valueOf(alpha2b.charAt(2))))
importSanitizedLangInDatabase(connection, alpha3b, alpha3t, newAlpha2b, english, french);
} else if (!isInDatabase(connection, alpha3b)) {
insertLang(connection, alpha3b, alpha3t, alpha2b, english, french);
}
}
private static boolean isInDatabase(Connection connection, String alpha3b) throws SQLException {
String query = "SELECT id FROM languages WHERE alpha3_b = ?;";
try (PreparedStatement stmt = connection.prepareStatement(query)){
stmt.setString(1, alpha3b);
stmt.execute();
return stmt.getResultSet().next();
}
}
private static void insertLang(Connection connection, String alpha3b, String alpha3t,
String alpha2b, String english, String french) throws SQLException {
String insert = "INSERT INTO languages(alpha3_b, alpha3_t, alpha2, english, french) VALUES(?, ?, ?, ?, ?);";
try (PreparedStatement stmt = connection.prepareStatement(insert)) {
stmt.setString(1, alpha3b);
stmt.setString(2, alpha3t);
stmt.setString(3, alpha2b);
stmt.setString(4, english);
stmt.setString(5, french);
stmt.executeUpdate();
}
}
private static String getStoredHash(Connection connection) throws SQLException {
logger.debug("Getting current hash");
String query = "SELECT app_value FROM properties WHERE app_key=?;";
try (PreparedStatement stmt = connection.prepareStatement(query)) {
stmt.setString(1, HASH_KEY);
stmt.execute();
if (stmt.getResultSet().next()) return stmt.getResultSet().getString(1);
else return "";
}
}
private static String getActualHash() throws NoSuchAlgorithmException, IOException {
logger.debug("Computing the actual language source file's hash.");
return getFileChecksum(MessageDigest.getInstance("MD5"));
}
private static String getFileChecksum(MessageDigest digest) throws IOException {
try (InputStream is = LanguageManager.class.getClassLoader().getResourceAsStream(LANGUAGES_FILE_NAME)) {
assert is != null;
byte[] byteArray = new byte[1024];
int bytesCount;
while ((bytesCount = is.read(byteArray)) != -1) {
digest.update(byteArray, 0, bytesCount);
}
byte[] bytes = digest.digest();
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes)
sb.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
return sb.toString();
}
}
public static void initializeLanguages(Connection connection) throws SQLException {
logger.debug("Creating language table.");
try (Statement stmt = connection.createStatement()) {
String query = "CREATE TABLE IF NOT EXISTS languages\n" +
"(\n" +
" id int GENERATED ALWAYS AS IDENTITY,\n" +
" alpha3_b char(3) NOT NULL,\n" +
" alpha3_t char(3),\n" +
" alpha2 char(2),\n" +
" english text NOT NULL,\n" +
" french text NOT NULL,\n" +
" PRIMARY KEY (id),\n" +
" UNIQUE (alpha3_b)\n" +
");";
stmt.executeUpdate(query);
}
}
}

View File

@ -35,7 +35,7 @@
<!-- Here you can set the base logger level. If DEBUG is too chatty for you, you can use INFO --> <!-- Here you can set the base logger level. If DEBUG is too chatty for you, you can use INFO -->
<!-- Possible options are: ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF --> <!-- Possible options are: ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF -->
<root level="INFO"> <root level="DEBUG">
<appender-ref ref="CONSOLE"/> <appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/> <appender-ref ref="FILE"/>
</root> </root>