diff --git a/.gitignore b/.gitignore index 19dd660..8c380e6 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,6 @@ build/ # mptv config.json *.db -log/ \ No newline at end of file +log/ +*.m3u +*.m3u8 \ No newline at end of file diff --git a/config.json.example b/config.json.example index b3447e6..99688cc 100644 --- a/config.json.example +++ b/config.json.example @@ -18,12 +18,12 @@ "type": "m3u", "url": "https://example.com/list.m3u", "cookies": null, - "singleCategory": null + "rootCategory": null }, { "type": "m3u-local", "path": "test.m3u8", - "singleCategory": "test" + "rootCategory": "test" } ] } \ No newline at end of file diff --git a/src/main/java/com/mykola2312/mptv/Main.java b/src/main/java/com/mykola2312/mptv/Main.java index 1755c15..f787912 100644 --- a/src/main/java/com/mykola2312/mptv/Main.java +++ b/src/main/java/com/mykola2312/mptv/Main.java @@ -1,18 +1,28 @@ package com.mykola2312.mptv; import com.mykola2312.mptv.config.Config; +import com.mykola2312.mptv.config.SourceItem; import com.mykola2312.mptv.db.DB; +import com.mykola2312.mptv.db.M3ULoader; +import com.mykola2312.mptv.parser.M3U; +import com.mykola2312.mptv.parser.M3UException; +import com.mykola2312.mptv.parser.M3UParser; import com.mykola2312.mptv.ui.MainFrame; import org.apache.commons.cli.*; import org.apache.log4j.Logger; import org.flywaydb.core.Flyway; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; public class Main { private static final Logger logger = Logger.getLogger(Main.class); public static void main(String[] args) { + // parse command line final Options options = new Options(); options.addOption(Option .builder("c") @@ -29,6 +39,7 @@ public class Main { return; } + // load config final String configPath = cmd.hasOption('c') ? cmd.getOptionValue('c') : "config.json"; Config config; try { @@ -45,6 +56,7 @@ public class Main { return; } + // setup db try { DB.setupFromConfig(config.db); } catch (RuntimeException e) { @@ -54,14 +66,49 @@ public class Main { return; } + // migrate db Flyway flyway = new Flyway( Flyway.configure() .dataSource(DB.URL, DB.USER, DB.PASSWORD) + .baselineOnMigrate(true) .load() .getConfiguration() ); flyway.migrate(); + // load sources, start crawlers + for (SourceItem source : config.sources) { + switch (source.type) { + case M3U_LOCAL -> { + try { + if (source.path == null) { + logger.error("m3u local has to have \"path\" variable"); + continue; + } else if (source.rootCategory == null) { + logger.error("source has to have \"rootCategory\""); + continue; + } + + String m3uData = Files.readString(Paths.get(source.path), StandardCharsets.UTF_8); + ArrayList m3u = M3UParser.parse(m3uData); + + M3ULoader.loadAll(m3u, source.rootCategory); + } catch (IOException e) { + logger.error(e); + logger.error(String.format("failed to read local m3u file: %s", e.getMessage())); + } catch (M3UException e) { + logger.error(e); + logger.error(String.format("failed to parse m3u: %s", e.getMessage())); + } + } + + default -> { + logger.error(String.format("source type %s is not implemented yet :(", source.type.name())); + } + } + } + + // initialize ui MainFrame frame = new MainFrame(); frame.create(config.frame); diff --git a/src/main/java/com/mykola2312/mptv/config/SourceItem.java b/src/main/java/com/mykola2312/mptv/config/SourceItem.java index 9bb4e7f..9905a48 100644 --- a/src/main/java/com/mykola2312/mptv/config/SourceItem.java +++ b/src/main/java/com/mykola2312/mptv/config/SourceItem.java @@ -26,5 +26,5 @@ public class SourceItem { public String cookies; @Nullable - public String singleCategory; + public String rootCategory; } diff --git a/src/main/java/com/mykola2312/mptv/db/M3ULoader.java b/src/main/java/com/mykola2312/mptv/db/M3ULoader.java index b4d966a..9f9e8d9 100644 --- a/src/main/java/com/mykola2312/mptv/db/M3ULoader.java +++ b/src/main/java/com/mykola2312/mptv/db/M3ULoader.java @@ -3,43 +3,89 @@ package com.mykola2312.mptv.db; import java.util.ArrayList; import java.util.HashMap; -import org.jooq.*; +import org.jooq.exception.NoDataFoundException; import org.jooq.impl.*; import static com.mykola2312.mptv.tables.Category.*; +import static com.mykola2312.mptv.tables.Channel.*; -import com.mykola2312.mptv.db.pojo.Category; import com.mykola2312.mptv.parser.M3U; public class M3ULoader { - public static void loadAll(ArrayList items) { + private static Integer ensureRootCategory(String rootName) { + try { + return DSL.using(DB.CONFIG) + .select(CATEGORY.ID) + .from(CATEGORY) + .where(CATEGORY.TITLE.eq(rootName)) + .limit(1) + .fetchSingleInto(Integer.class); + } catch (NoDataFoundException e) { + return DSL.using(DB.CONFIG) + .insertInto(CATEGORY, CATEGORY.TITLE) + .values(rootName) + .returningResult(CATEGORY.ID) + .fetchSingleInto(Integer.class); + } + } + + public static void loadAll(ArrayList items, String rootName) { + Integer rootCategoryId = ensureRootCategory(rootName); // cache categories' ids HashMap categories = new HashMap<>(); for (M3U item : items) { // category + Integer categoryId; if (item.groupTitle != null) { - Integer id; - Category category = DSL.using(DB.CONFIG) - .select() - .from(CATEGORY) - .where(CATEGORY.TITLE.eq(item.groupTitle)) - .limit(1) - .fetchOne() - .into(Category.class); - if (category == null) { - id = DSL.using(DB.CONFIG) - .insertInto(CATEGORY, CATEGORY.TITLE) - .values(item.groupTitle) - .returningResult(CATEGORY.ID) - .fetchOne() - .into(Integer.class); - } else { - id = category.id; - } + categoryId = categories.get(item.groupTitle); + if (categoryId == null) { + Integer id; + try { + id = DSL.using(DB.CONFIG) + .select(CATEGORY.ID) + .from(CATEGORY) + .where(CATEGORY.TITLE.eq(item.groupTitle)) + .limit(1) + .fetchSingleInto(Integer.class); + } catch (NoDataFoundException e) { + id = DSL.using(DB.CONFIG) + .insertInto(CATEGORY, CATEGORY.TITLE) + .values(item.groupTitle) + .returningResult(CATEGORY.ID) + .fetchOne() + .into(Integer.class); + } - categories.put(item.groupTitle, id); + categories.put(item.groupTitle, id); + categoryId = id; + } + } else { + categoryId = rootCategoryId; } // channel + try { + Integer channelId = DSL.using(DB.CONFIG) + .select(CHANNEL.ID) + .from(CHANNEL) + .where(CHANNEL.CATEGORY.eq(categoryId) + .and(CHANNEL.TITLE.eq(item.title))) + .limit(1) + .fetchSingleInto(Integer.class); + + DSL.using(DB.CONFIG) + .update(CHANNEL) + .set(CHANNEL.URL, item.url) + .set(CHANNEL.LOGO, item.tvgLogo) + .where(CHANNEL.ID.eq(channelId)) + .execute(); + } catch (NoDataFoundException e) { + DSL.using(DB.CONFIG) + .insertInto(CHANNEL, + CHANNEL.CATEGORY, CHANNEL.TITLE, + CHANNEL.URL, CHANNEL.LOGO) + .values(categoryId, item.title, item.url, item.tvgLogo) + .execute(); + } } } } diff --git a/src/main/resources/db/migration/V001.01__init.sql b/src/main/resources/db/migration/V001.01__init.sql index 120266c..64a74c5 100644 --- a/src/main/resources/db/migration/V001.01__init.sql +++ b/src/main/resources/db/migration/V001.01__init.sql @@ -15,4 +15,4 @@ CREATE TABLE channel ( FOREIGN KEY (category) REFERENCES category(id) ); -CREATE INDEX idx_channel_title ON channel(title); \ No newline at end of file +CREATE INDEX idx_channel_category_title ON channel(category,title); \ No newline at end of file diff --git a/src/test/java/com/mykola2312/mptv/TestConfig.java b/src/test/java/com/mykola2312/mptv/TestConfig.java index 6310b4b..a0f0734 100644 --- a/src/test/java/com/mykola2312/mptv/TestConfig.java +++ b/src/test/java/com/mykola2312/mptv/TestConfig.java @@ -83,12 +83,12 @@ public class TestConfig { assertEquals("https://example.com/list.m3u", m3u.url); assertNull(m3u.path); assertNull(m3u.cookies); - assertNull(m3u.singleCategory); + assertNull(m3u.rootCategory); SourceItem m3uLocal = config.sources.get(1); assertEquals(SourceItem.SourceType.M3U_LOCAL, m3uLocal.type); assertEquals("test.m3u8", m3uLocal.path); - assertEquals("test", m3uLocal.singleCategory); + assertEquals("test", m3uLocal.rootCategory); assertNull(m3uLocal.url); assertNull(m3uLocal.cookies); }