diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a126d9af..affdaf97 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -3,7 +3,7 @@ name: Java CI with Maven -on: [push, pull_request] +on: [ push, pull_request ] jobs: build: @@ -11,18 +11,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up JDK 16 - uses: actions/setup-java@v3 - with: - java-version: '16' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -B package --file pom.xml - - name: Upload jar - if: success() - uses: actions/upload-artifact@v3 - with: - name: multisubdownloader.jar - path: "/home/runner/work/SubTools/SubTools/MultiSubDownloader/target/multisubdownloader*.jar" \ No newline at end of file + - uses: actions/checkout@v4 + - name: Set up JDK 24 + uses: actions/setup-java@v4 + with: + java-version: '24' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml + - name: Upload jar + if: success() + uses: actions/upload-artifact@v4 + with: + name: multisubdownloader.jar + path: "/home/runner/work/SubTools/SubTools/MultiSubDownloader/target/multisubdownloader*.jar" \ No newline at end of file diff --git a/MultiSubDownloader/pom.xml b/MultiSubDownloader/pom.xml index a8026fcd..11d3f7dc 100644 --- a/MultiSubDownloader/pom.xml +++ b/MultiSubDownloader/pom.xml @@ -1,228 +1,318 @@ - - org.lodder.subtools - subtools - 1.5.1-SNAPSHOT - + + org.lodder.subtools + subtools + 1.5.1-SNAPSHOT + - 4.0.0 - multisubdownloader - MultiSubDownloader + 4.0.0 + multisubdownloader + MultiSubDownloader - - - d-maven - http://d-maven.googlecode.com/svn/trunk/repo - - + + + - - ${maven.build.timestamp} - + + + org.lodder.subtools + sublibrary + + + commons-cli + commons-cli + + + com.miglayout + miglayout-swing + + + org.jsoup + jsoup + + + org.reflections + reflections + + + ch.qos.logback + logback-classic + + + com.squareup.okhttp3 + logging-interceptor + + + com.google.code.gson + gson + + + io.gsonfire + gson-fire + + + com.fasterxml.jackson.core + jackson-databind + + + systems.manifold + manifold-ext-rt + + + systems.manifold + manifold-props-rt + + + systems.manifold + manifold-params-rt + + + systems.manifold + manifold-science + + + org.projectlombok + lombok + provided + + + org.openapitools + jackson-databind-nullable + + + net.jodah + typetools + + + org.jooq + joor + + + name.falgout.jeffrey + throwing-streams + + + com.pivovarit + throwing-function + + + jakarta.annotation + jakarta.annotation-api + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + src/main/resources + true + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-generated-sources + generate-sources + + add-source + + + + ${project.build.directory}/generated-source/openapi/src/main/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -Xplugin:Manifold + + + + systems.manifold + manifold-ext + ${manifold.version} + + + systems.manifold + manifold-props + ${manifold.version} + + + systems.manifold + manifold-params + ${manifold.version} + + + systems.manifold + manifold-strings + ${manifold.version} + + + + + + org.codehaus.mojo + exec-maven-plugin + + org.lodder.subtools.multisubdownloader.App + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.lodder.subtools.multisubdownloader.App + + + + + + maven-assembly-plugin + + + jar-with-dependencies + + + + true + true + lib/ + org.lodder.subtools.multisubdownloader.App + + + + + + make-assembly + package + + single + + + + + + org.openapitools + openapi-generator-maven-plugin + + + gestdown + + generate + + + java + ${project.basedir}/src/main/resources/gestdown/gestdown-swagger.json + ${project.build.directory}/generated-source/openapi + org.gestdown.model + org.gestdown.api + org.gestdown.invoker + org.gestdown + + true + Subtitles,TvShows + AbstractOpenApiSchema,EpisodeDto,EpisodeWithSubtitlesDto,ErrorResponse,SearchRequest,ShowDto,ShowSearchResponse,SubtitleDto,SubtitleSearchResponse,TvShowSubtitleResponse,WrongFormatResponse + false + + integer=int,int=int + false + true + is + false + + + java8-localdatetime + true + - - - org.lodder.subtools - sublibrary - - - commons-cli - commons-cli - - - com.miglayout - miglayout-swing - - - org.jsoup - jsoup - - - org.reflections - reflections - - - ch.qos.logback - logback-classic - - - com.squareup.okhttp3 - logging-interceptor - - - com.google.code.gson - gson - - - io.gsonfire - gson-fire - - - com.fasterxml.jackson.core - jackson-databind - - - org.projectlombok - lombok - provided - - - org.openapitools - jackson-databind-nullable - - - net.jodah - typetools - - - org.jooq - joor - - - name.falgout.jeffrey - throwing-streams - - - com.pivovarit - throwing-function - - - org.mockito - mockito-core - test - - - org.assertj - assertj-core - test - - - org.junit.jupiter - junit-jupiter-api - test - - - - - - src/main/resources - true - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.codehaus.mojo - exec-maven-plugin - - org.lodder.subtools.multisubdownloader.App - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.lodder.subtools.multisubdownloader.App - - - - - - maven-assembly-plugin - - - jar-with-dependencies - - - - true - true - lib/ - org.lodder.subtools.multisubdownloader.App - - - - - - make-assembly - package - - single - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.openapitools - openapi-generator-maven-plugin - - - - generate - - - java - ${project.basedir}/src/main/resources/gestdown/gestdown-swagger.json - ${project.build.directory}/generated-source/openapi - org.gestdown.model - org.gestdown.api - org.gestdown.invoker - org.gestdown - - true - Subtitles,TvShows - AbstractOpenApiSchema,EpisodeDto,EpisodeWithSubtitlesDto,ErrorResponse,SearchRequest,ShowDto,ShowSearchResponse,SubtitleDto,SubtitleSearchResponse,TvShowSubtitleResponse,WrongFormatResponse - false - - integer=int,int=int - false - true - is - false - - - java8-localdatetime - - - - OffsetDateTime=Instant - LocalDateTime=Instant - - - java.time.OffsetDateTime=java.time.Instant - java.time.LocalDateTime=java.time.Instant - - - - - - - - org.codehaus.mojo - properties-maven-plugin - - - generate-resources - - write-project-properties - - - ${project.build.outputDirectory}/properties-from-pom.properties - - - - - - + + OffsetDateTime=Instant + LocalDateTime=Instant + + + java.time.OffsetDateTime=java.time.Instant + java.time.LocalDateTime=java.time.Instant + + + + + opensubtitles + + generate + + + java + ${project.basedir}/src/main/resources/opensubtitles/open-api.json + ${project.build.directory}/generated-source/openapi + org.opensubtitles.model + org.opensubtitles.api + org.opensubtitles.invoker + org.opensubtitles + + + Authentication,Download,Subtitles + false + + integer=int,int=int + false + true + is + false + + true + + + + + + + org.codehaus.mojo + properties-maven-plugin + + ${project.build.outputDirectory}/properties-from-pom.properties + + + build.timestamp + ${maven.build.timestamp} + + + + + + generate-resources + + write-project-properties + + + + + + \ No newline at end of file diff --git a/MultiSubDownloader/src/main/java/extensions/java/awt/Component/ComponentExt.java b/MultiSubDownloader/src/main/java/extensions/java/awt/Component/ComponentExt.java new file mode 100644 index 00000000..c086a3bc --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/java/awt/Component/ComponentExt.java @@ -0,0 +1,82 @@ +package extensions.java.awt.Component; + +import java.awt.*; +import java.awt.event.MouseListener; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; +import org.jspecify.annotations.Nullable; + +@UtilityClass +@Extension +public class ComponentExt { + + public static void setRecursive(@This Component component, Consumer consumer) { + setRecursive(component, consumer, _ -> true); + } + + public static void setRecursive(@This Component component, Consumer consumer, + Predicate condition) { + if (component != null) { + consumer.accept(component); + if (component instanceof Container container && condition.test(container)) { + container.getComponents().forEach(child -> setRecursive(child, consumer, condition)); + } + } + } + + public static @Self Component mouseListener(@This Component component, @Nullable MouseListener listener) { + component.addMouseListener(listener); + return component; + } + + public static @Self Component addTo(@This Component child, Container parent) { + parent.addComponent(child); + return child; + } + + public static @Self Component addTo(@This Component child, Container parent, Object constraints) { + parent.addComponent(child, constraints); + return child; + } + + public static @Self Component withSize(@This Component component, Dimension size) { + component.size = size; + component.minimumSize = size; + component.maximumSize = size; + component.preferredSize = size; + return component; + } + + public static @Self Component minSize(@This Component component, Dimension minSize) { + component.minimumSize = minSize; + return component; + } + + public static @Self Component minSize(@This Component component, int minWidth, int minHeight) { + return component.minSize(new Dimension(minWidth, minHeight)); + } + + public static @Self Component maxSize(@This Component component, Dimension maximumSize) { + component.maximumSize = maximumSize; + return component; + } + + public static @Self Component maxSize(@This Component component, int maxWidth, int maxHeight) { + return component.maxSize(new Dimension(maxWidth, maxHeight)); + } + + public static @Self Component font(@This Component component, Font font) { + component.setFont(font); + return component; + } + + public static @Self Component focusable(@This Component component, boolean focusable) { + component.setFocusable(focusable); + return component; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/java/awt/Container/ContainerExt.java b/MultiSubDownloader/src/main/java/extensions/java/awt/Container/ContainerExt.java new file mode 100644 index 00000000..ba02ec56 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/java/awt/Container/ContainerExt.java @@ -0,0 +1,33 @@ +package extensions.java.awt.Container; + +import java.awt.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class ContainerExt { + + public static @Self Container addComponent(@This Container component, Component child) { + component.add(child); + return component; + } + + public static @Self Container addComponent(@This Container component, Component child, Object constraints) { + component.add(child, constraints); + return component; + } + + public static @Self Container addComponent(@This Container component, Object constraints, Component child) { + component.add(child, constraints); + return component; + } + + public static @Self Container layout(@This Container container, LayoutManager mgr) { + container.setLayout(mgr); + return container; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/java/awt/GridBagConstraints/GridBagConstraintsExt.java b/MultiSubDownloader/src/main/java/extensions/java/awt/GridBagConstraints/GridBagConstraintsExt.java new file mode 100644 index 00000000..88f0fb3d --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/java/awt/GridBagConstraints/GridBagConstraintsExt.java @@ -0,0 +1,33 @@ +package extensions.java.awt.GridBagConstraints; + +import java.awt.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@Extension +@UtilityClass +public class GridBagConstraintsExt { + + public static @Self GridBagConstraints insets(@This GridBagConstraints gridBagConstraints, Insets insets) { + gridBagConstraints.insets = insets; + return gridBagConstraints; + } + + public static @Self GridBagConstraints fill(@This GridBagConstraints gridBagConstraints, int fill) { + gridBagConstraints.fill = fill; + return gridBagConstraints; + } + + public static @Self GridBagConstraints gridx(@This GridBagConstraints gridBagConstraints, int gridx) { + gridBagConstraints.gridx = gridx; + return gridBagConstraints; + } + + public static @Self GridBagConstraints gridy(@This GridBagConstraints gridBagConstraints, int gridy) { + gridBagConstraints.gridy = gridy; + return gridBagConstraints; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/java/awt/GridBagLayout/GridBagLayoutExt.java b/MultiSubDownloader/src/main/java/extensions/java/awt/GridBagLayout/GridBagLayoutExt.java new file mode 100644 index 00000000..616d8aa0 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/java/awt/GridBagLayout/GridBagLayoutExt.java @@ -0,0 +1,33 @@ +package extensions.java.awt.GridBagLayout; + +import java.awt.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@Extension +@UtilityClass +public class GridBagLayoutExt { + + public static @Self GridBagLayout columnWidths(@This GridBagLayout gridBagLayout, int[] columnWidths) { + gridBagLayout.columnWidths = columnWidths; + return gridBagLayout; + } + + public static @Self GridBagLayout rowHeights(@This GridBagLayout gridBagLayout, int[] rowHeights) { + gridBagLayout.rowHeights = rowHeights; + return gridBagLayout; + } + + public static @Self GridBagLayout columnWeights(@This GridBagLayout gridBagLayout, double[] columnWeights) { + gridBagLayout.columnWeights = columnWeights; + return gridBagLayout; + } + + public static @Self GridBagLayout rowWeights(@This GridBagLayout gridBagLayout, double[] rowWeights) { + gridBagLayout.rowWeights = rowWeights; + return gridBagLayout; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/AbstractButton/AbstractButtonExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/AbstractButton/AbstractButtonExt.java new file mode 100644 index 00000000..76fea9c5 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/AbstractButton/AbstractButtonExt.java @@ -0,0 +1,46 @@ +package extensions.javax.swing.AbstractButton; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.util.function.Consumer; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; +import org.lodder.subtools.sublibrary.util.function.BooleanConsumer; + +@UtilityClass +@Extension +public class AbstractButtonExt { + + public static @Self AbstractButton actionListener(@This AbstractButton abstractButton, ActionListener listener) { + abstractButton.addActionListener(listener); + return abstractButton; + } + + public static @Self AbstractButton actionListener(@This AbstractButton abstractButton, Runnable listener) { + return abstractButton.actionListener(_ -> listener.run()); + } + + public static @Self AbstractButton actionListenerSelf(@This AbstractButton abstractButton, + Consumer selfConsumerListener) { + return abstractButton.actionListener(_ -> selfConsumerListener.accept(abstractButton)); + } + + public static @Self AbstractButton selectedListener(@This AbstractButton abstractButton, + BooleanConsumer selectedConsumer) { + return abstractButton.actionListener(_ -> selectedConsumer.accept(abstractButton.isSelected())); + } + + public static @Self AbstractButton actionCommand(@This AbstractButton abstractButton, String actionCommand) { + abstractButton.setActionCommand(actionCommand); + return abstractButton; + } + + public static @Self AbstractButton withMargin(@This AbstractButton abstractButton, Insets insets) { + abstractButton.setMargin(insets); + return abstractButton; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JButton/JButtonExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JButton/JButtonExt.java new file mode 100644 index 00000000..ad34b8be --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JButton/JButtonExt.java @@ -0,0 +1,18 @@ +package extensions.javax.swing.JButton; + +import javax.swing.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class JButtonExt { + + public static @Self JButton defaultButtonFor(@This JButton abstractButton, JRootPane rootPane) { + rootPane.setDefaultButton(abstractButton); + return abstractButton; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JCheckBox/JCheckBoxExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JCheckBox/JCheckBoxExt.java new file mode 100644 index 00000000..93d69f59 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JCheckBox/JCheckBoxExt.java @@ -0,0 +1,29 @@ +package extensions.javax.swing.JCheckBox; + +import javax.swing.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; +import org.lodder.subtools.sublibrary.util.function.BooleanConsumer; + +@Extension +@UtilityClass +public class JCheckBoxExt { + + public static @Self JCheckBox addCheckedChangeListener(@This JCheckBox checkBox, BooleanConsumer... listeners) { + checkBox.addItemListener(e -> listeners.forEach(lis -> lis.accept(((JCheckBox) e.getSource()).isSelected()))); + return checkBox; + } + + public static @Self JCheckBox visible(@This JCheckBox checkBox, boolean visible) { + checkBox.setVisible(visible); + return checkBox; + } + + public static @Self JCheckBox selected(@This JCheckBox checkBox, boolean selected) { + checkBox.setSelected(selected); + return checkBox; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JComboBox/JComboBoxExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JComboBox/JComboBoxExt.java new file mode 100644 index 00000000..e92af081 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JComboBox/JComboBoxExt.java @@ -0,0 +1,80 @@ +package extensions.javax.swing.JComboBox; + +import javax.swing.*; +import java.awt.event.ItemEvent; +import java.util.Collection; +import java.util.function.Consumer; +import java.util.function.Function; + +import com.google.common.collect.Iterables; +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; +import manifold.ext.rt.api.ThisClass; +import org.jspecify.annotations.Nullable; +import org.lodder.subtools.multisubdownloader.gui.ToStringListCellRenderer; + +@UtilityClass +@Extension +public class JComboBoxExt { + + public static JComboBox create(@ThisClass Class> thisClass, E... values) { + return new JComboBox<>(values); + } + + public static JComboBox create(@ThisClass Class> thisClass, Collection items) { + return new JComboBox<>(Iterables.toArray(items, (Class) items.iterator().next().getClass())); + } + + public static E getSelectedValue(@This JComboBox comboBox) { + return (E) comboBox.getSelectedItem(); + } + + public static @Self JComboBox renderer(@This JComboBox comboBox, ListCellRenderer renderer) { + comboBox.setRenderer(renderer); + return comboBox; + } + + public static @Self JComboBox toStringRenderer(@This JComboBox comboBox, + Function toStringRenderer) { + return comboBox.renderer(ToStringListCellRenderer.of(comboBox.getRenderer(), toStringRenderer)); + } + + public static @Self JComboBox toMessageStringRenderer(@This JComboBox comboBox, + Function toStringRenderer) { + return comboBox.renderer(ToStringListCellRenderer.ofMessage(comboBox.getRenderer(), toStringRenderer)); + } + + public static @Self JComboBox itemListener(@This JComboBox comboBox, Runnable itemListener) { + comboBox.addItemListener(_ -> itemListener.run()); + return comboBox; + } + + // public static @Self JComboBox itemListener(@This JComboBox comboBox, Consumer itemListener) { + // comboBox.addItemListener(event -> itemListener.accept((E) event.getItem())); + // return comboBox; + // } + + public static @Self JComboBox itemListener(@This JComboBox comboBox, Consumer itemListener) { + comboBox.addItemListener(itemListener::accept); + return comboBox; + } + + + public static @Self JComboBox selectedValue(@This JComboBox comboBox, @Nullable E item) { + comboBox.setSelectedItem(item); + return comboBox; + } + + public static @Self JComboBox selectedItemConsumer(@This JComboBox comboBox, Consumer actionListener) { + //noinspection unchecked + comboBox.addActionListener(ae -> actionListener.accept(((JComboBox) (ae.getSource())).getSelectedValue())); + return comboBox; + } + + public static @Self JComboBox model(@This JComboBox comboBox, ComboBoxModel model) { + comboBox.setModel(model); + return comboBox; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JComponent/JComponentExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JComponent/JComponentExt.java new file mode 100644 index 00000000..bbf70bad --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JComponent/JComponentExt.java @@ -0,0 +1,72 @@ +package extensions.javax.swing.JComponent; + +import javax.swing.*; +import javax.swing.border.*; +import java.awt.*; + +import extensions.java.awt.Component.ComponentExt; +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; +import org.jspecify.annotations.Nullable; + +@UtilityClass +@Extension +public class JComponentExt { + + public static @Self JComponent enabled(@This JComponent component, boolean enabled) { + component.setEnabled(enabled); + return component; + } + + public static @Self JComponent enabled(@This JComponent component) { + return component.enabled(true); + } + + public static @Self JComponent disabled(@This JComponent component) { + return component.enabled(false); + } + + public static @Self JComponent toolTipText(@This JComponent component, String text) { + component.setToolTipText(text); + return component; + } + + public static @Self JComponent hidden(@This JComponent component) { + return component.visible(false); + } + + public static @Self JComponent visible(@This JComponent component) { + return component.visible(true); + } + + public static @Self JComponent visible(@This JComponent component, boolean visible) { + component.setVisible(visible); + return component; + } + + public static @Self JComponent background(@This JComponent component, @Nullable Color background) { + component.background = background; + return component; + } + + public static void setEnabledRecursive(@This JComponent component, boolean enabled) { + ComponentExt.setRecursive(component, c -> c.setEnabled(enabled)); + } + + public static @Self JComponent enabledRecursive(@This JComponent component, boolean enabled) { + setEnabledRecursive(component, enabled); + return component; + } + + public static @Self JComponent withToolTipText(@This JComponent component, String text) { + component.setToolTipText(text); + return component; + } + + public static @Self JComponent border(@This JComponent component, Border border) { + component.setBorder(border); + return component; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JProgressBar/JProgressBarExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JProgressBar/JProgressBarExt.java new file mode 100644 index 00000000..7589f8e1 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JProgressBar/JProgressBarExt.java @@ -0,0 +1,19 @@ +package extensions.javax.swing.JProgressBar; + +import javax.swing.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + + +@UtilityClass +@Extension +public class JProgressBarExt { + + public static @Self JProgressBar indeterminate(@This JProgressBar progressBar, boolean indeterminate) { + progressBar.setIndeterminate(indeterminate); + return progressBar; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JScrollPane/JScrollPaneExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JScrollPane/JScrollPaneExt.java new file mode 100644 index 00000000..2e36b5de --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JScrollPane/JScrollPaneExt.java @@ -0,0 +1,20 @@ +package extensions.javax.swing.JScrollPane; + +import javax.swing.*; +import java.awt.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class JScrollPaneExt { + + public static @Self JScrollPane viewportView(@This JScrollPane scrollPane, Component view) { + scrollPane.setViewportView(view); + return scrollPane; + } + +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JSlider/JSliderExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JSlider/JSliderExt.java new file mode 100644 index 00000000..edb2cf4f --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JSlider/JSliderExt.java @@ -0,0 +1,23 @@ +package extensions.javax.swing.JSlider; + +import javax.swing.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class JSliderExt { + + public static @Self JSlider minimum(@This JSlider slider, int minimum) { + slider.setMinimum(minimum); + return slider; + } + + public static @Self JSlider maximum(@This JSlider slider, int maximum) { + slider.setMaximum(maximum); + return slider; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JTabbedPane/TabbedPaneExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTabbedPane/TabbedPaneExt.java new file mode 100644 index 00000000..47753797 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTabbedPane/TabbedPaneExt.java @@ -0,0 +1,43 @@ +package extensions.javax.swing.JTabbedPane; + +import javax.swing.*; +import java.awt.*; +import java.util.function.Consumer; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class TabbedPaneExt { + + public static @Self JTabbedPane tabLayoutPolicy(@This JTabbedPane tabbedPane, int tabLayoutPolicy) { + tabbedPane.tabLayoutPolicy = tabLayoutPolicy; + return tabbedPane; + } + + // public static @Self JTabbedPane changeListener(@This JTabbedPane tabbedPane, ChangeListener changeListener) { + // tabbedPane.addChangeListener(changeListener); + // return tabbedPane; + // } + + public static @Self JTabbedPane changeListener(@This JTabbedPane tabbedPane, + Consumer changeListener) { + tabbedPane.addChangeListener(_ -> changeListener.accept(tabbedPane)); + return tabbedPane; + } + + public static @Self JTabbedPane withTab(@This JTabbedPane tabbedPane, + String title, Component component) { + tabbedPane.addTab(title, null, component, null); + return tabbedPane; + } + + public static @Self JTabbedPane withTab(@This JTabbedPane tabbedPane, + String title, Icon icon, Component component, String tip) { + tabbedPane.addTab(title, icon, component, tip); + return tabbedPane; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JTable/JTableExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTable/JTableExt.java new file mode 100644 index 00000000..29a034fa --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTable/JTableExt.java @@ -0,0 +1,37 @@ +package extensions.javax.swing.JTable; + +import javax.swing.*; +import javax.swing.table.*; +import java.util.function.Function; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@Extension +@UtilityClass +public class JTableExt { + + public static @Self JTable model(@This JTable jTable, TableModel dataModel) { + jTable.setModel(dataModel); + return jTable; + } + + public static @Self JTable rowSorter(@This JTable jTable, RowSorter sorter) { + jTable.setRowSorter(sorter); + return jTable; + } + + public static @Self JTable rowSorter(@This JTable jTable, + Function> sorter) { + jTable.setRowSorter(sorter.apply(jTable.getModel())); + return jTable; + } + + public static @Self JTable autoResizeMode(@This JTable jTable, + int autoResizeMode) { + jTable.setAutoResizeMode(autoResizeMode); + return jTable; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JTextArea/JTextAreaExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTextArea/JTextAreaExt.java new file mode 100644 index 00000000..ddf74549 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTextArea/JTextAreaExt.java @@ -0,0 +1,18 @@ +package extensions.javax.swing.JTextArea; + +import javax.swing.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class JTextAreaExt { + + public static @Self JTextArea autoScrolls(@This JTextArea textArea, boolean autoScrolls) { + textArea.setAutoscrolls(autoScrolls); + return textArea; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/JTextField/JTextFieldExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTextField/JTextFieldExt.java new file mode 100644 index 00000000..14e22efb --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/JTextField/JTextFieldExt.java @@ -0,0 +1,18 @@ +package extensions.javax.swing.JTextField; + +import javax.swing.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class JTextFieldExt { + + public static @Self JTextField columns(@This JTextField textField, int columns) { + textField.setColumns(columns); + return textField; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/javax/swing/text/JTextComponent/JTextComponentExt.java b/MultiSubDownloader/src/main/java/extensions/javax/swing/text/JTextComponent/JTextComponentExt.java new file mode 100644 index 00000000..29bb6851 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/javax/swing/text/JTextComponent/JTextComponentExt.java @@ -0,0 +1,32 @@ +package extensions.javax.swing.text.JTextComponent; + +import javax.swing.event.*; +import javax.swing.text.*; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class JTextComponentExt { + + public static @Self JTextComponent editable(@This JTextComponent textComponent, boolean editable) { + textComponent.setEditable(editable); + return textComponent; + } + + public static @Self JTextComponent editable(@This JTextComponent textComponent) { + return textComponent.editable(true); + } + + public static @Self JTextComponent notEditable(@This JTextComponent textComponent) { + return textComponent.editable(false); + } + + public static @Self JTextComponent documentListener(@This JTextComponent textComponent, DocumentListener listener) { + textComponent.getDocument().addDocumentListener(listener); + return textComponent; + } +} diff --git a/MultiSubDownloader/src/main/java/extensions/org/apache/commons/cli/CommandLine/CommandLineExt.java b/MultiSubDownloader/src/main/java/extensions/org/apache/commons/cli/CommandLine/CommandLineExt.java new file mode 100644 index 00000000..48af06f4 --- /dev/null +++ b/MultiSubDownloader/src/main/java/extensions/org/apache/commons/cli/CommandLine/CommandLineExt.java @@ -0,0 +1,19 @@ +package extensions.org.apache.commons.cli.CommandLine; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import org.apache.commons.cli.CommandLine; +import org.lodder.subtools.multisubdownloader.cli.CliOption; + +@Extension +@UtilityClass +public class CommandLineExt { + public static boolean hasCliOption(@This CommandLine line, CliOption cliOption) { + return line.hasOption(cliOption.value); + } + + public static String getCliOptionValue(@This CommandLine line, CliOption cliOption) { + return line.getOptionValue(cliOption.value); + } +} \ No newline at end of file diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/App.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/App.java index c5773d48..e25a693a 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/App.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/App.java @@ -1,13 +1,13 @@ package org.lodder.subtools.multisubdownloader; +import static manifold.science.util.UnitConstants.*; + import javax.swing.*; import java.awt.*; import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.prefs.Preferences; import ch.qos.logback.classic.Level; @@ -25,24 +25,23 @@ import org.lodder.subtools.multisubdownloader.gui.Splash; import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; -import org.lodder.subtools.multisubdownloader.util.CLIExtension; import org.lodder.subtools.sublibrary.ConfigProperties; +import org.lodder.subtools.sublibrary.ConfigProperties.Property; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.cache.DiskCache; import org.lodder.subtools.sublibrary.cache.InMemoryCache; -import org.lodder.subtools.sublibrary.cache.SerializableDiskCache; import org.lodder.subtools.sublibrary.util.http.HttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@ExtensionMethod({ CLIExtension.class, Files.class }) +@ExtensionMethod({Files.class}) public class App { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + private static SettingsControl prefCtrl; private static Splash splash; - private static final Logger LOGGER = LoggerFactory.getLogger(App.class); public static void main(String[] args) throws ReflectiveOperationException, UnsupportedLookAndFeelException { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); @@ -59,19 +58,19 @@ public static void main(String[] args) throws ReflectiveOperationException, Unsu } if (!line.hasCliOption(CliOption.NO_GUI)) { - splash = new Splash(); - splash.showSplash(); + splash = new Splash().showSplash(); } Preferences preferences = Preferences.userRoot(); - preferences.putBoolean("speedy", line.hasCliOption(CliOption.SPEEDY)); - preferences.putBoolean("confirmProviderMapping", line.hasCliOption(CliOption.CONFIRM_PROVIDER_MAPPING)); + preferences.putBoolean(CliOption.SPEEDY.value, line.hasCliOption(CliOption.SPEEDY)); + preferences.putBoolean(CliOption.CONFIRM_PROVIDER_MAPPING.value, + line.hasCliOption(CliOption.CONFIRM_PROVIDER_MAPPING)); final Container app = new Container(); final Manager manager = createManager(!line.hasCliOption(CliOption.NO_GUI)); prefCtrl = new SettingsControl(manager); - Messages.setLanguage(prefCtrl.getSettings().getLanguage()); - Bootstrapper bootstrapper = new Bootstrapper(app, prefCtrl.getSettings(), preferences, manager); + Messages.language = prefCtrl.settings.language; + Bootstrapper bootstrapper = new Bootstrapper(app, prefCtrl.settings, preferences, manager); if (line.hasCliOption(CliOption.TRACE)) { setLogLevel(Level.ALL); @@ -80,7 +79,7 @@ public static void main(String[] args) throws ReflectiveOperationException, Unsu } if (line.hasCliOption(CliOption.NO_GUI)) { - bootstrapper.initialize(new UserInteractionHandlerCLI(prefCtrl.getSettings())); + bootstrapper.initialize(new UserInteractionHandlerCLI(prefCtrl.settings)); CLI cmd = new CLI(prefCtrl, app); /* Defined here so there is output on console */ @@ -89,7 +88,7 @@ public static void main(String[] args) throws ReflectiveOperationException, Unsu try { cmd.setUp(line); if (line.hasCliOption(CliOption.HELP)) { - formatter.printHelp(ConfigProperties.getInstance().getProperty("name"), getCLIOptions()); + formatter.printHelp(ConfigProperties.getProperty(Property.NAME), getCLIOptions()); return; } } catch (CliException e) { @@ -101,7 +100,7 @@ public static void main(String[] args) throws ReflectiveOperationException, Unsu /* Defined here so there is output in the splash */ importPreferences(line); - bootstrapper.initialize(new UserInteractionHandlerGUI(prefCtrl.getSettings(), null)); + bootstrapper.initialize(new UserInteractionHandlerGUI(prefCtrl.settings, null)); EventQueue.invokeLater(() -> { try { JFrame window = new GUI(prefCtrl, app); @@ -114,20 +113,19 @@ public static void main(String[] args) throws ReflectiveOperationException, Unsu }); } new Thread(() -> { - SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) app.make("SubtitleProviderStore"); - List providerNames = subtitleProviderStore.getAllProviders().stream().map(SubtitleProvider::getProviderName) + List providerNames = + app.makeSubtitleProviderStore().getAllProviders().stream().map(SubtitleProvider::getProviderName) .map(providerName -> providerName.contains("-") ? providerName.split("-")[0] : providerName) .map(providerName -> providerName + "-").toList(); - manager.clearExpiredCacheBuilder() - .cacheType(CacheType.DISK) - .keyFilter((String key) -> providerNames.stream().noneMatch(key::startsWith)) - .clear(); + manager.getCache(CacheType.DISK, key -> providerNames.stream().noneMatch(key::startsWith)) + .clearExpiredCache(); }).start(); } private static void setLogLevel(Level level) { - ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + ch.qos.logback.classic.Logger root = + (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); root.setLevel(level); } @@ -147,27 +145,29 @@ private static void importPreferences(CommandLine line) { public static Options getCLIOptions() { Options options = new Options(); - Arrays.stream(CliOption.values()).forEach( - cliOption -> options.addOption(cliOption.getValue(), cliOption.getLongValue(), cliOption.isHasArg(), cliOption.getDescription())); + CliOption.values().forEach(cliOption -> options.addOption(cliOption.value, cliOption.longValue, + cliOption.hasArg, cliOption.description)); return options; } private static Manager createManager(boolean useGui) { if (splash != null) { - splash.setProgressMsg(Messages.getString("App.Starting")); + splash.progressMsg = Messages.getText("App.Starting"); } DiskCache diskCache = - SerializableDiskCache.cacheBuilder().keyType(String.class).valueType(Serializable.class) - .timeToLive(TimeUnit.SECONDS.convert(500, TimeUnit.DAYS)) - .maxItems(2500) - .build(); - - InMemoryCache inMemoryCache = - InMemoryCache.builder().keyType(String.class).valueType(Serializable.class) - .timeToLive(TimeUnit.SECONDS.convert(10, TimeUnit.MINUTES)) - .timerInterval(100L) - .maxItems(500) - .build(); + new DiskCache<>( + String.class, + Serializable.class, + 500 day, + 2500); + + InMemoryCache inMemoryCache = + new InMemoryCache<>( + String.class, + String.class, + 10 min, + 100 ms, + 500); return new Manager(new HttpClient(), inMemoryCache, diskCache); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/CLI.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/CLI.java index 0ba21335..40480cbd 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/CLI.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/CLI.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; @@ -13,7 +12,7 @@ import org.lodder.subtools.multisubdownloader.actions.UserInteractionHandlerAction; import org.lodder.subtools.multisubdownloader.cli.CliOption; import org.lodder.subtools.multisubdownloader.cli.actions.CliSearchAction; -import org.lodder.subtools.multisubdownloader.cli.progress.CLIFileindexerProgress; +import org.lodder.subtools.multisubdownloader.cli.progress.CLIFileIndexerProgress; import org.lodder.subtools.multisubdownloader.cli.progress.CLISearchProgress; import org.lodder.subtools.multisubdownloader.exceptions.CliException; import org.lodder.subtools.multisubdownloader.exceptions.SearchSetupException; @@ -23,8 +22,6 @@ import org.lodder.subtools.multisubdownloader.lib.control.subtitles.SubtitleFiltering; import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.multisubdownloader.settings.model.Settings; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; -import org.lodder.subtools.multisubdownloader.util.CLIExtension; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.ManagerException; @@ -33,16 +30,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ CLIExtension.class }) public class CLI { private static final Logger LOGGER = LoggerFactory.getLogger(CLI.class); private final Container app; private final Settings settings; - private final Manager manager; private boolean recursive = false; private Language language; private boolean force = false; @@ -57,17 +50,17 @@ public class CLI { public CLI(SettingsControl settingControl, Container app) { this.app = app; this.settings = settingControl.getSettings(); - this.manager = (Manager) this.app.make("Manager"); + Manager manager = app.makeManager(); checkUpdate(manager); UserInteractionHandlerCLI userInteractionHandler = new UserInteractionHandlerCLI(settings); userInteractionHandlerAction = new UserInteractionHandlerAction(settings, userInteractionHandler); - downloadAction = new DownloadAction(settings, (Manager) this.app.make("Manager"), userInteractionHandler); + downloadAction = new DownloadAction(settings, manager, userInteractionHandler); } private void checkUpdate(Manager manager) { UpdateAvailableGithub u = new UpdateAvailableGithub(manager, settings); - if (u.shouldCheckForNewUpdate(settings.getUpdateCheckPeriod()) && u.isNewVersionAvailable()) { - System.out.println(Messages.getString("UpdateAppAvailable") + ": " + u.getLatestDownloadUrl()); + if (u.shouldCheckForNewUpdate(settings.updateCheckPeriod) && u.isNewVersionAvailable()) { + System.out.println(Messages.getText("UpdateAppAvailable") + ": " + u.getLatestDownloadUrl()); } } @@ -80,7 +73,7 @@ public void setUp(CommandLine line) throws CliException { this.subtitleSelection = line.hasCliOption(CliOption.SELECTION); this.verboseProgress = line.hasCliOption(CliOption.VERBOSE_PROGRESS); this.dryRun = line.hasCliOption(CliOption.DRY_RUN); - Messages.setLanguage(language); + Messages.language = language; } public void run() { @@ -95,30 +88,28 @@ public void download(List releases) { try { this.download(release); } catch (Exception e) { - LOGGER.error("Error while downloading subtitle for %s (%s)".formatted(release.getReleaseDescription(), e.getMessage()), e); + LOGGER.error("Error while downloading subtitle for ${release.releaseDescription} (%${e.getMessage()})", + e); } } } public void search() { try { - CliSearchAction - .createWithSettings(settings) - .manager(manager) - .subtitleProviderStore((SubtitleProviderStore) app.make("SubtitleProviderStore")) - .indexingProgressListener(new CLIFileindexerProgress().verbose(verboseProgress)) - .searchProgressListener(new CLISearchProgress().verbose(verboseProgress)) - .cli(this) - .fileListAction(new FileListAction(this.settings)) - .language(language) - .releaseFactory(new ReleaseFactory(this.settings, (Manager) app.make("Manager"))) - .filtering(new SubtitleFiltering(this.settings)) - .folders(folders) - .recursive(recursive) - .overwriteSubtitles(force) - .build() - /* CLI has no benefit of running this in a separate Thread */ - .run(); + new CliSearchAction(settings:settings, + subtitleProviderStore:app.makeSubtitleProviderStore(), + indexingProgressListener:new CLIFileIndexerProgress().verbose(verboseProgress), + searchProgressListener:new CLISearchProgress().verbose(verboseProgress), + cli:this, + fileListAction:new FileListAction(this.settings), + language:language, + releaseFactory:new ReleaseFactory(this.settings, app.makeManager()), + filtering:new SubtitleFiltering(this.settings), + folders:folders, + recursive:recursive, + overwriteSubtitles:force) + /* CLI has no benefit of running this in a separate Thread */ + .run(); } catch (SearchSetupException e) { LOGGER.error("executeArgs: search (%s)".formatted(e.getMessage()), e); } @@ -129,20 +120,21 @@ private void download(Release release) { if (downloadAll) { selection = release.getMatchingSubs(); if (!selection.isEmpty()) { - System.out.println("Downloading ALL found subtitles for release: " + release.getFileName()); + System.out.println("Downloading ALL found subtitles for release: ${release.fileName}"); } } else { selection = userInteractionHandlerAction.subtitleSelection(release, subtitleSelection, dryRun); } if (selection.isEmpty()) { - System.out.println("No subtitles found for: " + release.getFileName()); + System.out.println("No subtitles found for: ${release.fileName}"); } else { IntStream.range(0, selection.size()).forEach(j -> { - System.out.println("Downloading subtitle: " + release.getMatchingSubs().get(0).getFileName()); + System.out.println("Downloading subtitle: " + release.matchingSubs.get(j).fileName); try { - downloadAction.download(release, release.getMatchingSubs().get(j), selection.size() == 1 ? null : j + 1); + downloadAction.download(release, release.matchingSubs.get(j), selection.size() == 1 ? null : j + 1); } catch (IOException | ManagerException e) { - LOGGER.error("Error while downloading subtitle for %s (%s)".formatted(release.getReleaseDescription(), e.getMessage()), e); + LOGGER.error( + "Error while downloading subtitle for ${release.releaseDescription} (${e.getMessage()})", e); } }); } @@ -152,15 +144,17 @@ private List getFolders(CommandLine line) { if (line.hasCliOption(CliOption.FOLDER)) { return List.of(Path.of(line.getCliOptionValue(CliOption.FOLDER))); } else { - return new ArrayList<>(this.settings.getDefaultFolders()); + return new ArrayList<>(this.settings.defaultFolders); } } private Language getLanguage(CommandLine line) throws CliException { if (line.hasCliOption(CliOption.LANGUAGE)) { String languageString = line.getCliOptionValue(CliOption.LANGUAGE); - return Arrays.stream(Language.values()).filter(lang -> lang.name().equalsIgnoreCase(languageString)).findAny() - .orElseThrow(() -> new CliException(Messages.getString("App.NoValidLanguage"))); + return Language.values().stream() + .filter(lang -> lang.name().equalsIgnoreCase(languageString)) + .findAny() + .orElseThrow(() -> new CliException(Messages.getText("App.NoValidLanguage"))); } else { return Language.ENGLISH; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/GUI.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/GUI.java index 8d377771..f36590ba 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/GUI.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/GUI.java @@ -1,8 +1,13 @@ package org.lodder.subtools.multisubdownloader; +import static org.lodder.subtools.multisubdownloader.Messages.*; +import static org.lodder.subtools.multisubdownloader.gui.extra.table.SearchColumnName.*; + import javax.swing.*; -import javax.swing.event.*; -import javax.swing.table.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.event.MouseListener; @@ -17,9 +22,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; -import lombok.experimental.ExtensionMethod; import org.lodder.subtools.multisubdownloader.framework.Container; -import org.lodder.subtools.multisubdownloader.framework.event.Emitter; import org.lodder.subtools.multisubdownloader.gui.Menu; import org.lodder.subtools.multisubdownloader.gui.actions.search.FileGuiSearchAction; import org.lodder.subtools.multisubdownloader.gui.actions.search.TextGuiSearchAction; @@ -49,10 +52,11 @@ import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.multisubdownloader.settings.model.ScreenSettings; import org.lodder.subtools.multisubdownloader.settings.model.Settings; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.multisubdownloader.util.ExportImport; import org.lodder.subtools.multisubdownloader.util.PropertiesReader; +import org.lodder.subtools.multisubdownloader.util.PropertiesReader.PomProperty; import org.lodder.subtools.sublibrary.ConfigProperties; +import org.lodder.subtools.sublibrary.ConfigProperties.Property; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.ManagerException; @@ -61,27 +65,21 @@ import org.lodder.subtools.sublibrary.exception.SubtitlesProviderException; import org.lodder.subtools.sublibrary.model.Subtitle; import org.lodder.subtools.sublibrary.model.VideoType; -import org.lodder.subtools.sublibrary.util.FileUtils; -import org.lodder.subtools.sublibrary.util.StringUtil; -import org.lodder.subtools.sublibrary.util.TriConsumer; +import org.lodder.subtools.sublibrary.util.function.TriConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@ExtensionMethod({ FileUtils.class }) public class GUI extends JFrame implements PropertyChangeListener { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private final Container app; private final Manager manager; private final Settings settings; private final UserInteractionHandlerGUI userInteractionHandler; private final SettingsControl settingsControl; private ProgressDialog progressDialog; - private MyPopupMenu popupMenu; private SearchPanel pnlSearchFile; private SearchPanel pnlSearchText; - private LoggingPanel pnlLogging; private SearchFileInputPanel pnlSearchFileInput; private Menu menuBar; private IndexingProgressDialog fileIndexerProgressDialog; @@ -93,10 +91,10 @@ public class GUI extends JFrame implements PropertyChangeListener { */ public GUI(final SettingsControl settingsControl, Container app) { this.app = app; - this.manager = (Manager) this.app.make("Manager"); - this.settings = (Settings) this.app.make("Settings"); - this.userInteractionHandler = new UserInteractionHandlerGUI(settingsControl.getSettings(), this); - setTitle(ConfigProperties.getInstance().getProperty("name")); + this.manager = app.makeManager(); + this.settings = app.makeSettings(); + this.userInteractionHandler = new UserInteractionHandlerGUI(settingsControl.settings, this); + setTitle(ConfigProperties.getProperty(Property.NAME)); /* * setIconImage(Toolkit.getDefaultToolkit().getImage( * getClass().getResource("/resources/Bierdopje_bigger.png"))); @@ -104,8 +102,8 @@ public GUI(final SettingsControl settingsControl, Container app) { this.settingsControl = settingsControl; initialize(); restoreScreenSettings(); - pnlSearchFile.getResultPanel().disableButtons(); - pnlSearchText.getResultPanel().disableButtons(); + pnlSearchFile.resultPanel.disableButtons(); + pnlSearchText.resultPanel.disableButtons(); new Thread(() -> checkUpdate(false)).start(); initPopupMenu(); } @@ -113,26 +111,27 @@ public GUI(final SettingsControl settingsControl, Container app) { public void redraw() { close(); // setVisible(false); - getContentPane().removeAll(); + contentPane.removeAll(); initialize(); } private void checkUpdate(final boolean forceUpdateCheck) { UpdateAvailableGithub u = new UpdateAvailableGithub(manager, settings); - Optional updateUrl = (forceUpdateCheck && u.isNewVersionAvailable()) - || (!forceUpdateCheck && u.shouldCheckForNewUpdate(settingsControl.getSettings().getUpdateCheckPeriod()) - && u.isNewVersionAvailable()) ? u.getLatestDownloadUrl() : Optional.empty(); + Optional updateUrl = (forceUpdateCheck && u.isNewVersionAvailable()) || + (!forceUpdateCheck && u.shouldCheckForNewUpdate(settingsControl.settings.updateCheckPeriod) && + u.isNewVersionAvailable()) ? u.getLatestDownloadUrl() : Optional.empty(); if (updateUrl.isPresent()) { final JEditorPane editorPane = new JEditorPane(); editorPane.setPreferredSize(new Dimension(800, 50)); editorPane.setEditable(false); editorPane.setContentType("text/html"); - editorPane.setText("" + Messages.getString("UpdateAppAvailable") + "!:
" + updateUrl.get() + ""); + editorPane.setText("" + getText("UpdateAppAvailable") + "!:
" + + updateUrl.get() + ""); editorPane.addHyperlinkListener(hyperlinkEvent -> { - if (hyperlinkEvent.getEventType() == HyperlinkEvent.EventType.ACTIVATED && Desktop.isDesktopSupported()) { + if (hyperlinkEvent.getEventType() == HyperlinkEvent.EventType.ACTIVATED && + Desktop.isDesktopSupported()) { try { Desktop.getDesktop().browse(hyperlinkEvent.getURL().toURI()); } catch (Exception e) { @@ -140,10 +139,11 @@ private void checkUpdate(final boolean forceUpdateCheck) { } } }); - JOptionPane.showMessageDialog(this, editorPane, ConfigProperties.getInstance().getProperty("name"), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(this, editorPane, ConfigProperties.getProperty(Property.NAME), + JOptionPane.INFORMATION_MESSAGE); } else if (forceUpdateCheck) { - JOptionPane.showMessageDialog(this, Messages.getString("MainWindow.NoUpdateAvailable"), - ConfigProperties.getInstance().getProperty("name"), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(this, getText("MainWindow.NoUpdateAvailable"), + ConfigProperties.getProperty(Property.NAME), JOptionPane.INFORMATION_MESSAGE); } } @@ -152,7 +152,7 @@ private void checkUpdate(final boolean forceUpdateCheck) { * Initialize the contents of the frame. */ private void initialize() { - MemoryFolderChooser.getInstance().setMemory(settingsControl.getSettings().getLastOutputDir()); + MemoryFolderChooser.getInstance().memory = settingsControl.settings.lastOutputDir; addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -162,99 +162,97 @@ public void windowClosing(WindowEvent e) { setBounds(100, 100, 925, 680); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); final GridBagLayout gridBagLayout = new GridBagLayout(); - gridBagLayout.columnWidths = new int[] { 448, 0 }; - gridBagLayout.rowHeights = new int[] { 0, 125, 15, 0 }; - gridBagLayout.columnWeights = new double[] { 1.0, Double.MIN_VALUE }; - gridBagLayout.rowWeights = new double[] { 1.0, 1.0, 0.0, Double.MIN_VALUE }; - getContentPane().setLayout(gridBagLayout); + gridBagLayout.columnWidths = new int[]{ 448, 0 }; + gridBagLayout.rowHeights = new int[]{ 0, 125, 15, 0 }; + gridBagLayout.columnWeights = new double[]{ 1.0, Double.MIN_VALUE }; + gridBagLayout.rowWeights = new double[]{ 1.0, 1.0, 0.0, Double.MIN_VALUE }; + contentPane.setLayout(gridBagLayout); JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP); - GridBagConstraints gbc_tabbedPane = new GridBagConstraints(); - gbc_tabbedPane.insets = new Insets(0, 0, 5, 0); - gbc_tabbedPane.fill = GridBagConstraints.BOTH; - gbc_tabbedPane.gridx = 0; - gbc_tabbedPane.gridy = 0; - getContentPane().add(tabbedPane, gbc_tabbedPane); + GridBagConstraints gbcTabbedPane = new GridBagConstraints(); + gbcTabbedPane.insets = new Insets(0, 0, 5, 0); + gbcTabbedPane.fill = GridBagConstraints.BOTH; + gbcTabbedPane.gridx = 0; + gbcTabbedPane.gridy = 0; + contentPane.add(tabbedPane, gbcTabbedPane); createFileSearchPanel(); - tabbedPane.addTab(Messages.getString("MainWindow.SearchOnFile"), null, pnlSearchFile, null); + tabbedPane.addTab(getText("MainWindow.SearchOnFile"), null, pnlSearchFile, null); createTextSearchPanel(); - tabbedPane.addTab(Messages.getString("MainWindow.SearchOnName"), null, pnlSearchText, null); + tabbedPane.addTab(getText("MainWindow.SearchOnName"), null, pnlSearchText, null); - pnlLogging = new LoggingPanel(); - final GridBagConstraints gbc_pnlLogging = new GridBagConstraints(); - gbc_pnlLogging.fill = GridBagConstraints.BOTH; - gbc_pnlLogging.insets = new Insets(0, 0, 5, 0); - gbc_pnlLogging.gridx = 0; - gbc_pnlLogging.gridy = 1; - getContentPane().add(pnlLogging, gbc_pnlLogging); + LoggingPanel pnlLogging = new LoggingPanel(); + final GridBagConstraints gbcPnlLogging = new GridBagConstraints(); + gbcPnlLogging.fill = GridBagConstraints.BOTH; + gbcPnlLogging.insets = new Insets(0, 0, 5, 0); + gbcPnlLogging.gridx = 0; + gbcPnlLogging.gridy = 1; + contentPane.add(pnlLogging, gbcPnlLogging); StatusLabel lblStatus = new StatusLabel(""); StatusMessenger.instance.addListener(lblStatus); - final GridBagConstraints gbc_lblStatus = new GridBagConstraints(); - gbc_lblStatus.anchor = GridBagConstraints.SOUTHWEST; - gbc_lblStatus.gridx = 0; - gbc_lblStatus.gridy = 2; - getContentPane().add(lblStatus, gbc_lblStatus); + final GridBagConstraints gbcLblStatus = new GridBagConstraints(); + gbcLblStatus.anchor = GridBagConstraints.SOUTHWEST; + gbcLblStatus.gridx = 0; + gbcLblStatus.gridy = 2; + contentPane.add(lblStatus, gbcLblStatus); - createMenu(); + createMenu(pnlLogging); setJMenuBar(menuBar); } - private void createMenu() { - Settings settings = settingsControl.getSettings(); - BiConsumer visibilityFunction = pnlSearchFile.getResultPanel().getTable()::setColumnVisibility; + private void createMenu(LoggingPanel pnlLogging) { + Settings settings = settingsControl.settings; + BiConsumer visibilityFunction = + pnlSearchFile.resultPanel.getTable()::setColumnVisibility; BiConsumer showRenameDialog = - (videoType, title) -> new RenameDialog(self(), settings, videoType, title, manager, userInteractionHandler).setVisible(true); + (videoType, title) -> new RenameDialog(self(), settings, videoType, title, manager, + userInteractionHandler).setVisible(true); ExportImport exportImport = new ExportImport(manager, settingsControl, userInteractionHandler, this); menuBar = new Menu() - .withShowOnlyFound(settings.isOptionsShowOnlyFound()) - .withFileQuitAction(this::close) - .withViewFilenameAction(() -> visibilityFunction.accept(SearchColumnName.FILENAME, menuBar.isViewFilenameSelected())) - .withViewTypeAction(() -> visibilityFunction.accept(SearchColumnName.TYPE, menuBar.isViewTypeSelected())) - .withViewTitleAction(() -> visibilityFunction.accept(SearchColumnName.TITLE, menuBar.isViewTitleSelected())) - .withViewSeasonAction(() -> visibilityFunction.accept(SearchColumnName.SEASON, menuBar.isViewSeasonSelected())) - .withViewEpisodeAction(() -> visibilityFunction.accept(SearchColumnName.EPISODE, menuBar.isViewEpisodeSelected())) - .withViewShowOnlyFoundAction(() -> { - settings.setOptionsShowOnlyFound(menuBar.isShowOnlyFound()); - ((VideoTableModel) pnlSearchFile.getResultPanel().getTable().getModel()).setShowOnlyFound(menuBar.isShowOnlyFound()); - }) - .withViewClearLogAction(() -> pnlLogging.setLogText("")) - .withEditRenameTVAction(() -> showRenameDialog.accept(VideoType.EPISODE, Messages.getString("Menu.RenameSerie"))) - .withEditRenameMovieAction(() -> showRenameDialog.accept(VideoType.MOVIE, Messages.getString("Menu.RenameMovie"))) - .withEditPreferencesAction( - () -> new PreferenceDialog(self(), settingsControl, (Emitter) app.make("EventEmitter"), manager, userInteractionHandler) - .setVisible(true)) - .withTranslateShowNamesAction(this::showTranslateShowNames) - .withExportTranslationsAction(() -> exportImport.exportSettings(ExportImport.SettingsType.SERIE_MAPPING)) - .withImportTranslationsAction(() -> exportImport.importSettings(ExportImport.SettingsType.SERIE_MAPPING)) - .withExportPreferencesAction(() -> exportImport.exportSettings(ExportImport.SettingsType.PREFERENCES)) - .withImportPreferencesAction(() -> exportImport.importSettings(ExportImport.SettingsType.PREFERENCES)) - .withCheckUpdateAction(() -> checkUpdate(true)) - .withAboutAction(this::showAbout); + .withShowOnlyFound(settings.optionsShowOnlyFound) + .withFileQuitAction(this::close) + .withViewFilenameAction(() -> visibilityFunction.accept(FILENAME, menuBar.isViewFilenameSelected())) + .withViewTypeAction(() -> visibilityFunction.accept(TYPE, menuBar.isViewTypeSelected())) + .withViewTitleAction(() -> visibilityFunction.accept(TITLE, menuBar.isViewTitleSelected())) + .withViewSeasonAction(() -> visibilityFunction.accept(SEASON, menuBar.isViewSeasonSelected())) + .withViewEpisodeAction(() -> visibilityFunction.accept(EPISODE, menuBar.isViewEpisodeSelected())) + .withViewShowOnlyFoundAction(() -> { + settings.optionsShowOnlyFound = menuBar.isShowOnlyFound(); + ((VideoTableModel) pnlSearchFile.resultPanel.getTable().getModel()) + .setShowOnlyFound(menuBar.isShowOnlyFound()); + }) + .withViewClearLogAction(() -> pnlLogging.setLogText("")) + .withEditRenameTVAction(() -> showRenameDialog.accept(VideoType.EPISODE, getText("Menu.RenameSerie"))) + .withEditRenameMovieAction(() -> showRenameDialog.accept(VideoType.MOVIE, getText("Menu.RenameMovie"))) + .withEditPreferencesAction( + () -> new PreferenceDialog(self(), settingsControl, app.makeEventEmitter(), manager, + userInteractionHandler).setVisible(true)) + .withTranslateShowNamesAction(this::showTranslateShowNames) + .withExportTranslationsAction(() -> exportImport.exportSettings(ExportImport.SettingsType.SERIE_MAPPING)) + .withImportTranslationsAction(() -> exportImport.importSettings(ExportImport.SettingsType.SERIE_MAPPING)) + .withExportPreferencesAction(() -> exportImport.exportSettings(ExportImport.SettingsType.PREFERENCES)) + .withImportPreferencesAction(() -> exportImport.importSettings(ExportImport.SettingsType.PREFERENCES)) + .withCheckUpdateAction(() -> checkUpdate(true)) + .withAboutAction(this::showAbout); } private void createTextSearchPanel() { - Settings settings = this.settingsControl.getSettings(); + Settings settings = this.settingsControl.settings; /* resolve the SubtitleProviderStore from the Container */ - SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) this.app.make("SubtitleProviderStore"); ResultPanel resultPanel = new ResultPanel(); SearchTextInputPanel pnlSearchTextInput = new SearchTextInputPanel(); pnlSearchText = new SearchPanel<>(pnlSearchTextInput, resultPanel); - pnlSearchTextInput.setSelectedlanguage(settings.getSubtitleLanguage() == null ? Language.DUTCH : settings.getSubtitleLanguage()); + pnlSearchTextInput.setSelectedLanguage( + settings.subtitleLanguage == null ? Language.DUTCH : settings.subtitleLanguage); resultPanel.showSelectFoundSubtitlesButton(); resultPanel.setTable(createSubtitleTable()); - resultPanel.setDownloadAction(arg -> downloadText()); - - TextGuiSearchAction searchAction = TextGuiSearchAction.createWithSettings(settings) - .manager(manager) - .subtitleProviderStore(subtitleProviderStore) - .mainWindow(this) - .searchPanel(pnlSearchText) - .releaseFactory(new ReleaseFactory(settings, (Manager) app.make("Manager"))) - .build(); + resultPanel.setDownloadAction(_ -> downloadText()); + + TextGuiSearchAction searchAction = new TextGuiSearchAction(settings, app.makeSubtitleProviderStore(), + this, pnlSearchText, new ReleaseFactory(settings, app.makeManager())); pnlSearchTextInput.addSearchAction(searchAction); } @@ -268,35 +266,28 @@ private CustomTable createSubtitleTable() { } private void createFileSearchPanel() { - Settings settings = this.settingsControl.getSettings(); + Settings settings = this.settingsControl.settings; ResultPanel resultPanel = new ResultPanel(); pnlSearchFileInput = new SearchFileInputPanel(); - pnlSearchFileInput.setRecursiveSelected(settings.isOptionRecursive()); - pnlSearchFileInput.setSelectedlanguage(settings.getSubtitleLanguage() == null ? Language.DUTCH : settings.getSubtitleLanguage()); + pnlSearchFileInput.setRecursiveSelected(settings.optionRecursive); + pnlSearchFileInput.setSelectedLanguage( + settings.subtitleLanguage == null ? Language.DUTCH : settings.subtitleLanguage); pnlSearchFile = new SearchPanel<>(pnlSearchFileInput, resultPanel); resultPanel.setTable(createVideoTable()); - FileGuiSearchAction searchAction = FileGuiSearchAction - .createWithSettings(settings) - .manager(manager) - .subtitleProviderStore((SubtitleProviderStore) this.app.make("SubtitleProviderStore")) - .mainWindow(this) - .searchPanel(pnlSearchFile) - .releaseFactory(new ReleaseFactory(settings, (Manager) app.make("Manager"))) - .build(); + FileGuiSearchAction searchAction = new FileGuiSearchAction(settings, app.makeSubtitleProviderStore(), this, + pnlSearchFile, new ReleaseFactory(settings, app.makeManager())); - pnlSearchFileInput.addSelectFolderAction(arg -> selectIncomingFolder()); + pnlSearchFileInput.addSelectFolderAction(_ -> selectIncomingFolder()); pnlSearchFileInput.addSearchAction(searchAction); - resultPanel.setDownloadAction(arg -> download()); - resultPanel.setMoveAction(arg -> { - final int response = - JOptionPane.showConfirmDialog( - self(), - Messages.getString("MainWindow.OnlyMoveToLibraryStructure"), Messages.getString("App.Confirm"), //$NON-NLS-2$ - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + resultPanel.setDownloadAction(_ -> download()); + resultPanel.setMoveAction(_ -> { + final int response = JOptionPane.showConfirmDialog(self(), getText("MainWindow.OnlyMoveToLibraryStructure"), + getText("App.Confirm"), //$NON-NLS-2$ + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { rename(); } @@ -307,8 +298,8 @@ private CustomTable createVideoTable() { CustomTable customTable = new CustomTable(); VideoTableModel videoTableModel = VideoTableModel.getDefaultVideoTableModel(); customTable.setModel(videoTableModel); - videoTableModel.setShowOnlyFound(settingsControl.getSettings().isOptionsShowOnlyFound()); - videoTableModel.setUserInteractionHandler(userInteractionHandler); + videoTableModel.setShowOnlyFound(settingsControl.settings.optionsShowOnlyFound); + videoTableModel.userInteractionHandler = userInteractionHandler; final RowSorter sorter = new TableRowSorter<>(customTable.getModel()); customTable.setRowSorter(sorter); customTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); @@ -330,30 +321,32 @@ private CustomTable createVideoTable() { } private void restoreScreenSettings() { - CustomTable customTable = pnlSearchFile.getResultPanel().getTable(); - TriConsumer> visibilityConsumer = (searchColumn, hidden, setVisibleConsumer) -> { - setVisibleConsumer.accept(!hidden); - customTable.setColumnVisibility(searchColumn, !hidden); - }; - - ScreenSettings screenSettings = settingsControl.getSettings().getScreenSettings(); - - visibilityConsumer.accept(SearchColumnName.EPISODE, screenSettings.isHideEpisode(), menuBar::withViewEpisodeSelected); - visibilityConsumer.accept(SearchColumnName.FILENAME, screenSettings.isHideFilename(), menuBar::withViewFileNameSelected); - visibilityConsumer.accept(SearchColumnName.SEASON, screenSettings.isHideSeason(), menuBar::withViewSeasonSelected); - visibilityConsumer.accept(SearchColumnName.TYPE, screenSettings.isHideType(), menuBar::withViewTypeSelected); - visibilityConsumer.accept(SearchColumnName.TITLE, screenSettings.isHideTitle(), menuBar::withViewTitleSelected); + CustomTable customTable = pnlSearchFile.resultPanel.getTable(); + TriConsumer> visibilityConsumer = + (searchColumn, hidden, setVisibleConsumer) -> { + setVisibleConsumer.accept(!hidden); + customTable.setColumnVisibility(searchColumn, !hidden); + }; + + ScreenSettings screenSettings = settingsControl.settings.screenSettings; + + visibilityConsumer.accept(SearchColumnName.EPISODE, screenSettings.hideEpisode, + menuBar::withViewEpisodeSelected); + visibilityConsumer.accept(FILENAME, screenSettings.hideFilename, menuBar::withViewFileNameSelected); + visibilityConsumer.accept(SearchColumnName.SEASON, screenSettings.hideSeason, menuBar::withViewSeasonSelected); + visibilityConsumer.accept(SearchColumnName.TYPE, screenSettings.hideType, menuBar::withViewTypeSelected); + visibilityConsumer.accept(SearchColumnName.TITLE, screenSettings.hideTitle, menuBar::withViewTitleSelected); } private void initPopupMenu() { - popupMenu = new MyPopupMenu(); - JMenuItem menuItem = new JMenuItem(Messages.getString("App.Copy")); - menuItem.addActionListener(arg0 -> { + MyPopupMenu popupMenu = new MyPopupMenu(); + JMenuItem menuItem = new JMenuItem(getText("App.Copy")); + menuItem.addActionListener(_ -> { final CustomTable t = (CustomTable) popupMenu.getInvoker(); final DefaultTableModel model = (DefaultTableModel) t.getModel(); - int col = t.columnAtPoint(popupMenu.getClickLocation()); - int row = t.rowAtPoint(popupMenu.getClickLocation()); + int col = t.columnAtPoint(popupMenu.clickLocation); + int row = t.rowAtPoint(popupMenu.clickLocation); try { StringSelection selection = new StringSelection((String) model.getValueAt(row, col)); @@ -366,8 +359,8 @@ private void initPopupMenu() { // add the listener to the jtable MouseListener popupListener = new PopupListener(popupMenu); // add the listener specifically to the header - CustomTable customTable = pnlSearchFile.getResultPanel().getTable(); - CustomTable subtitleTable = pnlSearchText.getResultPanel().getTable(); + CustomTable customTable = pnlSearchFile.resultPanel.getTable(); + CustomTable subtitleTable = pnlSearchText.resultPanel.getTable(); customTable.addMouseListener(popupListener); customTable.getTableHeader().addMouseListener(popupListener); subtitleTable.addMouseListener(popupListener); @@ -375,79 +368,79 @@ private void initPopupMenu() { } protected void showTranslateShowNames() { - final MappingEpisodeNameDialog tDialog = new MappingEpisodeNameDialog(this, settingsControl, (Manager) this.app.make("Manager"), - (SubtitleProviderStore) this.app.make("SubtitleProviderStore"), userInteractionHandler); + final MappingEpisodeNameDialog tDialog = new MappingEpisodeNameDialog(this, app.makeManager(), + app.makeSubtitleProviderStore(), userInteractionHandler); tDialog.setVisible(true); } private void showAbout() { - String version = ConfigProperties.getInstance().getProperty(Messages.getString("MainWindow.Version")); - StringBuilder sb = new StringBuilder(); - sb.append(Messages.getString("MainWindow.CurrentVersion")).append(": ").append(version); + String version = ConfigProperties.getProperty(Property.VERSION); + String currentVersionText = getText("MainWindow.CurrentVersion"); + String buildTimestamp = PropertiesReader.getProperty(PomProperty.BUILD_TIMESTAMP); + String text = "$currentVersionText: $version"; if (version.contains("-SNAPSHOT")) { - sb.append(" (%s)".formatted(PropertiesReader.getProperty("build.timestamp"))); + text += " ($buildTimestamp)"; } - JOptionPane.showConfirmDialog(this, sb.toString(), ConfigProperties.getInstance().getProperty("name"), JOptionPane.CLOSED_OPTION); + JOptionPane.showConfirmDialog(this, text, ConfigProperties.getProperty(Property.NAME), + JOptionPane.DEFAULT_OPTION); } protected void rename() { - CustomTable customTable = pnlSearchFile.getResultPanel().getTable(); + CustomTable customTable = pnlSearchFile.resultPanel.getTable(); RenameWorker renameWorker = - new RenameWorker(customTable, settingsControl.getSettings(), (Manager) this.app.make("Manager"), userInteractionHandler); + new RenameWorker(customTable, settingsControl.settings, app.makeManager(), userInteractionHandler); renameWorker.addPropertyChangeListener(this); - pnlSearchFile.getResultPanel().enableButtons(); + pnlSearchFile.resultPanel.enableButtons(); progressDialog = new ProgressDialog(this, renameWorker); progressDialog.setVisible(true); renameWorker.execute(); } private void download() { - CustomTable customTable = pnlSearchFile.getResultPanel().getTable(); - DownloadWorker downloadWorker = new DownloadWorker(customTable, settingsControl.getSettings(), (Manager) this.app.make("Manager"), this); + CustomTable customTable = pnlSearchFile.resultPanel.getTable(); + DownloadWorker downloadWorker = + new DownloadWorker(customTable, settingsControl.settings, app.makeManager(), this); downloadWorker.addPropertyChangeListener(this); - pnlSearchFile.getResultPanel().disableButtons(); + pnlSearchFile.resultPanel.disableButtons(); progressDialog = new ProgressDialog(this, downloadWorker); progressDialog.setVisible(true); downloadWorker.execute(); } private void downloadText() { - MemoryFolderChooser.getInstance().selectDirectory(getContentPane(), Messages.getString("MainWindow.SelectFolder")) - .ifPresent(path -> { - CustomTable subtitleTable = pnlSearchText.getResultPanel().getTable(); - final VideoTableModel model = (VideoTableModel) subtitleTable.getModel(); - for (int i = 0; i < model.getRowCount(); i++) { - if ((Boolean) model.getValueAt(i, subtitleTable.getColumnIdByName(SearchColumnName.SELECT))) { - final Subtitle subtitle = (Subtitle) model.getValueAt(i, subtitleTable.getColumnIdByName(SearchColumnName.OBJECT)); - String filename = ""; - if (!subtitle.getFileName().endsWith(".srt")) { - filename = subtitle.getFileName() + ".srt"; - } - if (OsCheck.getOperatingSystemType() == OSType.Windows) { - filename = StringUtil.removeIllegalWindowsChars(filename); - } + MemoryFolderChooser.getInstance() + .selectDirectory(contentPane, getText("MainWindow.SelectFolder")) + .ifPresent(path -> { + CustomTable subtitleTable = pnlSearchText.resultPanel.getTable(); + final VideoTableModel model = (VideoTableModel) subtitleTable.getModel(); + for (int i = 0; i < model.getRowCount(); i++) { + if ((Boolean) model.getValueAt(i, subtitleTable.getColumnIdByName(SearchColumnName.SELECT))) { + final Subtitle subtitle = (Subtitle) model.getValueAt(i, + subtitleTable.getColumnIdByName(SearchColumnName.OBJECT)); + String filename = ""; + if (!subtitle.fileName.endsWith(".srt")) { + filename = subtitle.fileName + ".srt"; + } + if (OsCheck.operatingSystemType == OSType.WINDOWS) { + filename = filename.removeIllegalWindowsChars(); + } - try { - if (subtitle.getSourceLocation() == Subtitle.SourceLocation.FILE) { - subtitle.getFile().copyToDir(path); - } else { - Manager manager = (Manager) this.app.make("Manager"); - String url = - subtitle.getSourceLocation() == Subtitle.SourceLocation.URL ? subtitle.getUrl() - : subtitle.getUrlSupplier().get(); - manager.store(url, path.resolve(filename)); - } - } catch (IOException | ManagerException e) { - LOGGER.error("downloadText", e); - } catch (SubtitlesProviderException e) { - LOGGER.error("Error while getting url for [%s] for subtitle provider [%s] (%s)".formatted(filename, - e.getSubtitleProvider(), e.getMessage()), e); - throw new RuntimeException(e); + try { + switch (subtitle.downloadSource.sourceLocation) { + case FILE -> subtitle.downloadSource.file.copyToDir(path); + case URL, URL_SUPPLIER -> app.makeManager() + .store(subtitle.downloadSource.getValue(), path.resolve(filename)); } + } catch (IOException | ManagerException e) { + LOGGER.error("downloadText", e); + } catch (SubtitlesProviderException e) { + LOGGER.error("Error while getting url for [%s] for subtitle provider [%s] (%s)" + .formatted(filename, e.getSubtitleProvider(), e.getMessage()), e); + throw new RuntimeException(e); } } - }); - + } + }); } protected GUI self() { @@ -455,52 +448,55 @@ protected GUI self() { } public void showErrorMessage(String message) { - JOptionPane.showConfirmDialog(this, message, ConfigProperties.getInstance().getProperty("name"), JOptionPane.CLOSED_OPTION, - JOptionPane.ERROR_MESSAGE); + JOptionPane.showConfirmDialog(this, message, ConfigProperties.getProperty(Property.NAME), + JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE); } private void selectIncomingFolder() { - MemoryFolderChooser.getInstance().selectDirectory(self(), Messages.getString("MainWindow.SelectFolder")) - .map(Path::toAbsolutePath).map(Path::toString).ifPresent(pnlSearchFileInput::setIncomingPath); + MemoryFolderChooser.getInstance() + .selectDirectory(self(), getText("MainWindow.SelectFolder")) + .map(Path::toAbsolutePath) + .map(Path::toString) + .ifPresent(pnlSearchFileInput::setIncomingPath); } @Override public void propertyChange(PropertyChangeEvent event) { if (event.getSource() instanceof DownloadWorker downloadWorker) { if (downloadWorker.isDone()) { - pnlSearchFile.getResultPanel().enableButtons(); + pnlSearchFile.resultPanel.enableButtons(); progressDialog.setVisible(false); } else { final int progress = downloadWorker.getProgress(); progressDialog.updateProgress(progress); - StatusMessenger.instance.message(Messages.getString("MainWindow.StatusDownload")); + StatusMessenger.instance.message(getText("MainWindow.StatusDownload")); } } else if (event.getSource() instanceof RenameWorker renameWorker) { if (renameWorker.isDone()) { - pnlSearchFile.getResultPanel().enableButtons(); + pnlSearchFile.resultPanel.enableButtons(); progressDialog.setVisible(false); } else { final int progress = renameWorker.getProgress(); progressDialog.updateProgress(progress); - StatusMessenger.instance.message(Messages.getString("MainWindow.StatusRename")); + StatusMessenger.instance.message(getText("MainWindow.StatusRename")); } } } private void close() { - settingsControl.getSettings().setOptionRecursive(pnlSearchFileInput.isRecursiveSelected()); - settingsControl.getSettings().setSubtitleLanguage(pnlSearchFileInput.getSelectedLanguage()); + settingsControl.settings.optionRecursive = pnlSearchFileInput.isRecursiveSelected(); + settingsControl.settings.subtitleLanguage = pnlSearchFileInput.getSelectedLanguage(); storeScreenSettings(); settingsControl.store(); } private void storeScreenSettings() { - CustomTable customTable = pnlSearchFile.getResultPanel().getTable(); - settingsControl.getSettings().getScreenSettings().setHideEpisode(customTable.isHideColumn(SearchColumnName.EPISODE)); - settingsControl.getSettings().getScreenSettings().setHideFilename(customTable.isHideColumn(SearchColumnName.FILENAME)); - settingsControl.getSettings().getScreenSettings().setHideSeason(customTable.isHideColumn(SearchColumnName.SEASON)); - settingsControl.getSettings().getScreenSettings().setHideTitle(customTable.isHideColumn(SearchColumnName.TITLE)); - settingsControl.getSettings().getScreenSettings().setHideType(customTable.isHideColumn(SearchColumnName.TYPE)); + CustomTable customTable = pnlSearchFile.resultPanel.getTable(); + settingsControl.settings.screenSettings.hideEpisode = customTable.isHideColumn(SearchColumnName.EPISODE); + settingsControl.settings.screenSettings.hideFilename = customTable.isHideColumn(FILENAME); + settingsControl.settings.screenSettings.hideSeason = customTable.isHideColumn(SearchColumnName.SEASON); + settingsControl.settings.screenSettings.hideTitle = customTable.isHideColumn(SearchColumnName.TITLE); + settingsControl.settings.screenSettings.hideType = customTable.isHideColumn(SearchColumnName.TYPE); } public ProgressDialog setProgressDialog(Cancelable worker) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UpdateAvailableGithub.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UpdateAvailableGithub.java index 4cb427ca..4c45fb1f 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UpdateAvailableGithub.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UpdateAvailableGithub.java @@ -1,6 +1,7 @@ package org.lodder.subtools.multisubdownloader; import static java.time.temporal.ChronoUnit.*; +import static org.lodder.subtools.sublibrary.PageContentParams.*; import java.time.Instant; import java.time.LocalDate; @@ -11,27 +12,30 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; import org.jsoup.nodes.Element; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.settings.model.UpdateCheckPeriod; import org.lodder.subtools.multisubdownloader.util.PropertiesReader; +import org.lodder.subtools.multisubdownloader.util.PropertiesReader.PomProperty; import org.lodder.subtools.sublibrary.ConfigProperties; +import org.lodder.subtools.sublibrary.ConfigProperties.Property; import org.lodder.subtools.sublibrary.Manager; -import org.lodder.subtools.sublibrary.Manager.ValueBuilderIsPresentIntf; +import org.lodder.subtools.sublibrary.Manager.CacheKey; +import org.lodder.subtools.sublibrary.Manager.Value; +import org.lodder.subtools.sublibrary.PageContentParams; import org.lodder.subtools.sublibrary.cache.CacheType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.RequiredArgsConstructor; - @RequiredArgsConstructor public class UpdateAvailableGithub { private static final Logger LOGGER = LoggerFactory.getLogger(UpdateAvailableGithub.class); - private final static String DOMAIN = "https://github.com"; - private final static String REPO_URI = "/phdelodder/SubTools"; - private final static String REPO_URL = DOMAIN + REPO_URI; + private static final String DOMAIN = "https://github.com"; + private static final String REPO_URI = "/phdelodder/SubTools"; + private static final String REPO_URL = DOMAIN + REPO_URI; private final Manager manager; private final Settings settings; @@ -52,109 +56,114 @@ public boolean shouldCheckForNewUpdate(UpdateCheckPeriod updateCheckPeriod) { } public Optional getLatestDownloadUrl() { - return switch (settings.getUpdateType()) { + return switch (settings.updateType) { case STABLE -> getUrlLatestNewStableGithubRelease(); case NIGHTLY -> getUrlLatestNewNightlyGithubRelease(); }; } public boolean isNewVersionAvailable() { - return switch (settings.getUpdateType()) { + return switch (settings.updateType) { case STABLE -> getUrlLatestNewStableGithubRelease().isPresent(); case NIGHTLY -> getUrlLatestNewNightlyGithubRelease().isPresent(); }; } private Optional getUrlLatestNewStableGithubRelease() { - return manager.valueBuilder() - .cacheType(CacheType.MEMORY) - .key("GitHub-update") - .optionalSupplier(() -> { - try { - String currentVersion = getVersion(); - Element element = manager.getPageContentBuilder().url(REPO_URL + "/releases") - .userAgent(null) - .cacheType(CacheType.NONE) - .getAsJsoupDocument() - .selectFirst("#repo-content-turbo-frame .box a[href='" + REPO_URI + "/releases/latest']"); - Pattern versionPattern = Pattern.compile("[0-9]*\\.[0-9]\\.[0-9]"); - String versionText = element.parent().selectFirst("a").text(); - Matcher matcher = versionPattern.matcher(versionText); - matcher.find(); - String version = matcher.group(); - if (isFinalVersion(currentVersion) && compareVersions(version, currentVersion) <= 0) { - return Optional.empty(); - } - String versionBlockUrl = REPO_URL + "/releases/expanded_assets/" + versionText; - Element artifactElement = manager.getPageContentBuilder().url(versionBlockUrl) - .userAgent(null) - .cacheType(CacheType.NONE) - .getAsJsoupDocument() - .selectFirst(".Box-row a[href$='.jar']"); - String url = DOMAIN + artifactElement.attr("href"); - updateLastUpdateCheck(); - return Optional.of(url); - } catch (Exception e) { - LOGGER.error(Messages.getString("LoggingPanel.UpdateCheckFailed")); + return manager.getCache(CacheType.MEMORY, "GitHub-update") + .getOptional(() -> { + try { + String currentVersion = getVersion(); + Element element = + manager.getAsJsoupDocument(PageContentParams.params( + url:"$REPO_URL/releases", + cacheType:CacheType.NONE, + userAgent:null)) + .selectFirstByCss("#repo-content-turbo-frame .box a[href='$REPO_URI/releases/latest']"); + Pattern versionPattern = Pattern.compile("\\d*\\.\\d\\.\\d"); + String versionText = element.parent().selectFirstByTag("a").text(); + Matcher matcher = versionPattern.matcher(versionText); + matcher.find(); + String version = matcher.group(); + if (isFinalVersion(currentVersion) && compareVersions(version, currentVersion) <= 0) { return Optional.empty(); } - }).getOptional(); + String versionBlockUrl = REPO_URL + "/releases/expanded_assets/" + versionText; + Element artifactElement = manager.getAsJsoupDocument( + PageContentParams.params(url:versionBlockUrl, userAgent:null)) + .selectFirstByCss(".Box-row a[href$='.jar']"); + String url = DOMAIN + artifactElement.attr("href"); + updateLastUpdateCheck(); + return Optional.of(url); + } catch (Exception e) { + if (LOGGER.isTraceEnabled) { + LOGGER.trace(Messages.getText("LoggingPanel.UpdateCheckFailed"), e); + } else { + LOGGER.error(Messages.getText("LoggingPanel.UpdateCheckFailed")); + } + return Optional.empty(); + } + }); } private Optional getUrlLatestNewNightlyGithubRelease() { - return manager.valueBuilder() - .cacheType(CacheType.MEMORY) - .key("GitHub-update-nightly") - .optionalSupplier(() -> { - try { - LocalDateTime buildTista = getBuildTista(); - - Element rowElement = manager.getPageContentBuilder().url(REPO_URL + "/actions?query=branch%3Amaster") - .userAgent(null) - .cacheType(CacheType.MEMORY) - .getAsJsoupDocument() - .selectFirst("#partial-actions-workflow-runs .Box-row"); - LocalDateTime nightlyBuildTista = - zonedDateTimeStringToLocalDateTime(rowElement.selectFirst(".d-inline relative-time").attr("datetime")); - if (nightlyBuildTista.isBefore(buildTista)) { - return Optional.empty(); - } - String url = "https://nightly.link" + rowElement.selectFirst(".Link--primary").attr("href"); - String downloadUrl = manager.getPageContentBuilder().url(url).cacheType(CacheType.MEMORY).getAsJsoupDocument() - .selectFirst("table td a").attr("href"); - updateLastUpdateCheck(); - return Optional.of(downloadUrl); - } catch (Exception e) { - LOGGER.error(Messages.getString("LoggingPanel.UpdateCheckFailed")); + return manager.getCache(CacheType.MEMORY, "GitHub-update-nightly") + .getOptional(() -> { + try { + LocalDateTime buildTista = getBuildTista(); + + Element rowElement = + manager.getAsJsoupDocument(PageContentParams.params( + url:"$REPO_URL/actions?query=branch%3Amaster", + cacheType:CacheType.MEMORY, + userAgent:null)) + .selectFirstByCss("#partial-actions-workflow-runs .Box-row"); + LocalDateTime nightlyBuildTista = zonedDateTimeStringToLocalDateTime( + rowElement.selectFirstByCss(".d-inline relative-time").attr("datetime")); + if (nightlyBuildTista.isBefore(buildTista)) { return Optional.empty(); } - }).getOptional(); + String url = + "https://nightly.link" + rowElement.selectFirstByCss(".Link--primary").attr("href"); + String downloadUrl = manager.getAsJsoupDocument(params(url, CacheType.MEMORY)) + .selectFirstByCss("table td a") + .attr("href"); + updateLastUpdateCheck(); + return Optional.of(downloadUrl); + } catch (Exception e) { + if (LOGGER.isTraceEnabled) { + LOGGER.trace(Messages.getText("LoggingPanel.UpdateCheckFailed"), e); + } else { + LOGGER.error(Messages.getText("LoggingPanel.UpdateCheckFailed")); + } + return Optional.empty(); + } + }); } private LocalDateTime getBuildTista() { - String timestamp = PropertiesReader.getProperty("build.timestamp"); + String timestamp = PropertiesReader.getProperty(PomProperty.BUILD_TIMESTAMP); return zonedDateTimeStringToLocalDateTime(timestamp); } private String getVersion() { - return ConfigProperties.getInstance().getProperty("version"); + return ConfigProperties.getProperty(Property.VERSION); } private boolean isFinalVersion(String version) { return !version.contains("-SNAPSHOT"); } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static ValueBuilderIsPresentIntf getUpdateLastUpdateCheckBuilder(Manager manager) { - return (ValueBuilderIsPresentIntf) manager.valueBuilder().cacheType(CacheType.DISK).key("LastUpdateCheck"); + private CacheKey getUpdateLastUpdateCheckCache() { + return manager.getCache(CacheType.DISK, "LastUpdateCheck"); } private void updateLastUpdateCheck() { - getUpdateLastUpdateCheckBuilder(manager).value(LocalDate.now()).store(); + getUpdateLastUpdateCheckCache().store(Value.of(LocalDate.now())); } private LocalDate getLastUpdateCheck() { - return getUpdateLastUpdateCheckBuilder(manager).valueSupplier(() -> LocalDate.MIN).get(); + return getUpdateLastUpdateCheckCache().get(() -> LocalDate.MIN); } private LocalDateTime zonedDateTimeStringToLocalDateTime(String dateString) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandler.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandler.java index d366f1d4..8f6ca21c 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandler.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandler.java @@ -8,13 +8,13 @@ public interface UserInteractionHandler extends org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler { default List getAutomaticSelection(List subtitles) { - List shortlist = !getSettings().isOptionsMinAutomaticSelection() ? subtitles : + List shortlist = !settings.optionsMinAutomaticSelection ? subtitles : subtitles.stream() - .filter(subtitle -> subtitle.getScore() >= getSettings().getOptionsMinAutomaticSelectionValue()) + .filter(subtitle -> subtitle.score >= settings.optionsMinAutomaticSelectionValue) .toList(); - if (getSettings().isOptionsDefaultSelection()) { - List defaultSelectionsFound = getSettings().getOptionsDefaultSelectionQualityList().stream() - .flatMap(q -> shortlist.stream().filter(subtitle -> q.isTypeForValue(subtitle.getQuality()))) + if (settings.optionsDefaultSelection) { + List defaultSelectionsFound = settings.optionsDefaultSelectionQualityList.stream() + .flatMap(q -> shortlist.stream().filter(subtitle -> q.isTypeForValue(subtitle.quality))) .distinct().toList(); if (!defaultSelectionsFound.isEmpty()) { return defaultSelectionsFound; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerCLI.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerCLI.java index 8e393db3..61ee0fa6 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerCLI.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerCLI.java @@ -1,5 +1,6 @@ package org.lodder.subtools.multisubdownloader; +import static org.lodder.subtools.multisubdownloader.Messages.*; import static org.lodder.subtools.multisubdownloader.gui.extra.table.SubtitleTableColumnName.*; import java.util.Comparator; @@ -7,63 +8,52 @@ import java.util.function.Function; import java.util.stream.Stream; +import extensions.org.codehaus.plexus.components.interactivity.Prompter.PrompterExt; import org.codehaus.plexus.components.interactivity.DefaultInputHandler; import org.codehaus.plexus.components.interactivity.DefaultOutputHandler; import org.codehaus.plexus.components.interactivity.DefaultPrompter; import org.codehaus.plexus.components.interactivity.Prompter; -import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException; -import org.joor.Reflect; import org.lodder.subtools.multisubdownloader.gui.extra.table.SubtitleTableColumnName; import org.lodder.subtools.sublibrary.data.UserInteractionSettingsIntf; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -import org.lodder.subtools.sublibrary.util.prompter.ColumnDisplayer; -import org.lodder.subtools.sublibrary.util.prompter.PrompterUtil; -import org.lodder.subtools.sublibrary.util.prompter.TableDisplayer; public class UserInteractionHandlerCLI extends org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandlerCLI - implements UserInteractionHandler { + implements UserInteractionHandler { private final Prompter prompter; public UserInteractionHandlerCLI(UserInteractionSettingsIntf settings) { super(settings); - DefaultOutputHandler defaultOutputHandler = new DefaultOutputHandler(); - DefaultInputHandler defaultInputHandler = new DefaultInputHandler(); - try { - defaultOutputHandler.initialize(); - defaultInputHandler.initialize(); - } catch (InitializationException e) { - throw new RuntimeException(e); - } - prompter = Reflect.on(new DefaultPrompter()) - .set("outputHandler", defaultOutputHandler) - .set("inputHandler", defaultInputHandler) - .get(); + prompter = new DefaultPrompter(new DefaultOutputHandler(), new DefaultInputHandler()); } @Override public List selectSubtitles(Release release) { - System.out.printf("\n%s : %s%n", Messages.getString("SelectDialog.SelectCorrectSubtitleThisRelease"), release.getFileName()); - return PrompterUtil - .getElementsFromList(release.getMatchingSubs()) - .displayAsTable(createTableDisplayer()) - .message(Messages.getString("SelectDialog.EnterListSelectedSubtitles")) - .sort(Comparator.comparing(Subtitle::getScore)) - .includeNull() - .prompt(prompter); + System.out.printf("\n%s : %s%n", getText("SelectDialog.SelectCorrectSubtitleThisRelease"), + release.fileName); + return prompter.promptValuesFromList( + getText("SelectDialog.EnterListSelectedSubtitles"), + release.getMatchingSubs(), + Subtitle::getFileName, + true, + createTableDisplayer(), + Comparator.comparing(Subtitle::getScore)); } - private ColumnDisplayer createSubtitleDisplayer(SubtitleTableColumnName column, Function toStringMapper) { - return new ColumnDisplayer<>(column.getColumnName(), (Subtitle s) -> String.valueOf(toStringMapper.apply(s))); - } - - private TableDisplayer createTableDisplayer() { - return new TableDisplayer<>(Stream.of(SCORE, FILENAME, RELEASEGROUP, QUALITY, SOURCE, UPLOADER, HEARINGIMPAIRED) - .map(stcn -> createSubtitleDisplayer(stcn, stcn.getValueFunction())).toList()); + private PrompterExt.ColumnDisplayer createSubtitleDisplayer(SubtitleTableColumnName column, + Function toStringMapper) { + return new PrompterExt.ColumnDisplayer<>(column.columnName, + subtitle -> String.valueOf(toStringMapper.apply(subtitle))); } @Override public void dryRunOutput(Release release) { createTableDisplayer().display(release.getMatchingSubs()); } + + private PrompterExt.TableDisplayer createTableDisplayer() { + return new PrompterExt.TableDisplayer<>( + Stream.of(SCORE, FILENAME, RELEASEGROUP, QUALITY, SOURCE, UPLOADER, HEARINGIMPAIRED) + .map(stcn -> createSubtitleDisplayer(stcn, stcn.valueFunction)).toList()); + } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerGUI.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerGUI.java index e590e366..da3e7cee 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerGUI.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/UserInteractionHandlerGUI.java @@ -1,18 +1,15 @@ package org.lodder.subtools.multisubdownloader; +import javax.swing.*; import java.util.List; -import javax.swing.JFrame; - import org.lodder.subtools.multisubdownloader.gui.dialog.SelectDialog; import org.lodder.subtools.sublibrary.data.UserInteractionSettingsIntf; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -import lombok.Getter; - -@Getter -public class UserInteractionHandlerGUI extends org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandlerGUI implements UserInteractionHandler { +public class UserInteractionHandlerGUI extends org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandlerGUI + implements UserInteractionHandler { public UserInteractionHandlerGUI(UserInteractionSettingsIntf settings, JFrame frame) { super(settings, frame); @@ -20,14 +17,13 @@ public UserInteractionHandlerGUI(UserInteractionSettingsIntf settings, JFrame fr @Override public List selectSubtitles(Release release) { - List selection = new SelectDialog(getFrame(), release.getMatchingSubs(), release).getSelection(); - return selection.stream().map(i -> release.getMatchingSubs().get(i)).toList(); + List selection = new SelectDialog(frame, release.getMatchingSubs(), release).getSelection(); + return selection.stream().map(release.getMatchingSubs()::get).toList(); } @Override public void dryRunOutput(Release release) { // TODO Auto-generated method stub - } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/CleanAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/CleanAction.java index a778af81..9910362e 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/CleanAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/CleanAction.java @@ -6,53 +6,52 @@ import java.nio.file.StandardCopyOption; import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.experimental.ExtensionMethod; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.settings.model.LibrarySettings; import org.lodder.subtools.sublibrary.model.Release; -import org.lodder.subtools.sublibrary.util.FileUtils; -import org.lodder.subtools.sublibrary.util.StreamExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ StringUtils.class, FileUtils.class, Files.class, StreamExtension.class }) +@RequiredArgsConstructor +@ExtensionMethod({ Files.class }) public class CleanAction { private static final Logger LOGGER = LoggerFactory.getLogger(CleanAction.class); + private static final String SAMPLE_DIR_NAME = "sample"; + private static final Set FILE_FILTERS = Set.of("nfo", "jpg", "sfv", "srr", "srs", "nzb", "torrent", "txt"); private final LibrarySettings librarySettings; - private final Set fileFilters = Set.of("nfo", "jpg", "sfv", "srr", "srs", "nzb", "torrent", "txt"); - private final static String sampleDirName = "sample"; - - public CleanAction(LibrarySettings librarySettings) { - this.librarySettings = librarySettings; - } public void cleanUpFiles(Release release, Path destination, String videoFileName) throws IOException { - LOGGER.trace("cleanUpFiles: LibraryOtherFileAction {}", librarySettings.getLibraryOtherFileAction()); + LOGGER.trace("cleanUpFiles: LibraryOtherFileAction {}", librarySettings.otherFileAction); if (!destination.isDirectory()) { throw new IllegalArgumentException("Destination [%s] is not a folder".formatted(destination)); } release.getPath().list().asThrowingStream(IOException.class) - .filter(p -> (p.isDirectory() && p.fileNameContainsIgnoreCase(sampleDirName)) - || (p.isRegularFile() && fileFilters.contains(p.getExtension()))) - .forEach(p -> { - switch (librarySettings.getLibraryOtherFileAction()) { - case MOVE -> move(p, destination); - case MOVEANDRENAME -> moveAndRename(p, destination, videoFileName); - case REMOVE -> delete(p); - case RENAME -> rename(p, destination, videoFileName); - case NOTHING -> {} - default -> {} + .filter(p -> (p.isDirectory() && p.fileNameContainsIgnoreCase(SAMPLE_DIR_NAME)) + || (p.isRegularFile() && FILE_FILTERS.contains(p.getExtension()))) + .forEach(p -> { + switch (librarySettings.otherFileAction) { + case MOVE -> move(p, destination); + case MOVEANDRENAME -> moveAndRename(p, destination, videoFileName); + case REMOVE -> delete(p); + case RENAME -> rename(p, destination, videoFileName); + case NOTHING -> { + } + default -> { } - }); + } + }); } private void rename(Path path, Path destinationFolder, String videoFileName) throws IOException { if (path.isRegularFile()) { - String fileName = path.fileNameContainsIgnoreCase(sampleDirName) ? sampleDirName : StringUtils.substringBeforeLast(videoFileName, "."); + String fileName = + path.fileNameContainsIgnoreCase(SAMPLE_DIR_NAME) ? SAMPLE_DIR_NAME : + StringUtils.substringBeforeLast(videoFileName, "."); String extension = path.getExtension(); if (!extension.isBlank()) { extension = "." + extension; @@ -64,12 +63,14 @@ private void rename(Path path, Path destinationFolder, String videoFileName) thr } private void delete(Path path) throws IOException { - FileUtils.delete(path); + path.deletePath(); } private void moveAndRename(Path path, Path destinationFolder, String videoFileName) throws IOException { if (path.isRegularFile()) { - String fileName = path.fileNameContainsIgnoreCase(sampleDirName) ? sampleDirName : StringUtils.substringBeforeLast(videoFileName, "."); + String fileName = + path.fileNameContainsIgnoreCase(SAMPLE_DIR_NAME) ? SAMPLE_DIR_NAME : + StringUtils.substringBeforeLast(videoFileName, "."); String extension = path.getExtension(); if (!extension.isBlank()) { extension = "." + extension; @@ -81,7 +82,6 @@ private void moveAndRename(Path path, Path destinationFolder, String videoFileNa } private void move(Path origin, Path destinationFolder) throws IOException { - FileUtils.moveToDir(origin, destinationFolder, StandardCopyOption.REPLACE_EXISTING); + origin.moveToDir(destinationFolder, StandardCopyOption.REPLACE_EXISTING); } - } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/DownloadAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/DownloadAction.java index 51e9d507..f9a6de7f 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/DownloadAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/DownloadAction.java @@ -4,6 +4,9 @@ import java.nio.file.Files; import java.nio.file.Path; +import lombok.RequiredArgsConstructor; +import lombok.experimental.ExtensionMethod; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.lib.library.FilenameLibraryBuilder; import org.lodder.subtools.multisubdownloader.lib.library.LibraryActionType; import org.lodder.subtools.multisubdownloader.lib.library.LibraryOtherFileActionType; @@ -17,14 +20,10 @@ import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.RequiredArgsConstructor; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ FileUtils.class, Files.class }) +@ExtensionMethod({ Files.class }) @RequiredArgsConstructor public class DownloadAction { @@ -34,22 +33,20 @@ public class DownloadAction { private final Manager manager; private final UserInteractionHandler userInteractionHandler; - public void download(Release release, Subtitle subtitle, Integer version) throws IOException, ManagerException { - switch (release.getVideoType()) { - case EPISODE -> download(release, subtitle, settings.getEpisodeLibrarySettings(), version); - case MOVIE -> download(release, subtitle, settings.getMovieLibrarySettings(), version); - default -> throw new IllegalArgumentException("Unexpected value: " + release.getVideoType()); + public void download(Release release, Subtitle subtitle, Integer version=0) throws IOException, + ManagerException { + LOGGER.info("Downloading subtitle: [{}] for release: [{}]", subtitle.fileName, release.fileName); + switch (release.videoType) { + case EPISODE -> download(release, subtitle, settings.episodeLibrarySettings, version); + case MOVIE -> download(release, subtitle, settings.movieLibrarySettings, version); + default -> throw new IllegalArgumentException("Unexpected value: " + release.videoType); } } - public void download(Release release, Subtitle subtitle) throws IOException, ManagerException { - LOGGER.info("Downloading subtitle: [{}] for release: [{}]", subtitle.getFileName(), release.getFileName()); - download(release, subtitle, 0); - } - - private void download(Release release, Subtitle subtitle, LibrarySettings librarySettings, Integer version) - throws IOException, ManagerException { - LOGGER.trace("cleanUpFiles: LibraryAction {}", librarySettings.getLibraryAction()); + private void download(Release release, Subtitle subtitle, LibrarySettings librarySettings, + @Nullable Integer version) + throws IOException, ManagerException { + LOGGER.trace("cleanUpFiles: LibraryAction {}", librarySettings.action); Path path = PathLibraryBuilder.fromSettings(librarySettings, manager, userInteractionHandler).build(release); if (!path.exists()) { LOGGER.debug("Download creating folder [{}] ", path.toAbsolutePath()); @@ -60,31 +57,34 @@ private void download(Release release, Subtitle subtitle, LibrarySettings librar } } - FilenameLibraryBuilder filenameLibraryBuilder = FilenameLibraryBuilder.fromSettings(librarySettings, manager, userInteractionHandler); + FilenameLibraryBuilder filenameLibraryBuilder = + FilenameLibraryBuilder.fromSettings(librarySettings, manager, userInteractionHandler); String videoFileName = filenameLibraryBuilder.build(release).toString(); String subFileName = filenameLibraryBuilder.buildSubtitle(release, subtitle, videoFileName, version); Path subFile = path.resolve(subFileName); - boolean success; - if (subtitle.getSourceLocation() == Subtitle.SourceLocation.FILE) { - subtitle.getFile().copyToDir(path); - success = true; - } else { - String url; - try { - url = subtitle.getSourceLocation() == Subtitle.SourceLocation.URL ? subtitle.getUrl() : subtitle.getUrlSupplier().get(); - success = manager.store(url, subFile); - LOGGER.debug("doDownload file was [{}] ", success); - } catch (SubtitlesProviderException e) { - LOGGER.error("Error while getting url for [%s] for subtitle provider [%s] (%s)".formatted(release.getReleaseDescription(), - e.getSubtitleProvider(), e.getMessage()), e); - throw new RuntimeException(e); + boolean success = switch (subtitle.downloadSource.sourceLocation) { + case FILE -> { + subtitle.downloadSource.file.copyToDir(path); + yield true; } - } + case URL, URL_SUPPLIER -> { + try { + String url = subtitle.downloadSource.getValue(); + boolean result = manager.store(url, subFile); + LOGGER.debug("doDownload file was [{}] ", result); + yield result; + } catch (SubtitlesProviderException e) { + LOGGER.error("Error while getting url for [${release.releaseDescription}] " + + "for subtitle provider [${e.subtitleProvider}] (${e.getMessage()})", e); + throw new RuntimeException(e); + } + } + }; if (success) { if (!librarySettings.hasLibraryAction(LibraryActionType.NOTHING)) { - Path oldLocationFile = release.getPath().resolve(release.getFileName()); + Path oldLocationFile = release.getPath().resolve(release.fileName); if (oldLocationFile.exists()) { LOGGER.info("Moving/Renaming [{}] to folder [{}] this might take a while... ", videoFileName, path); oldLocationFile.moveToDir(path); @@ -92,14 +92,15 @@ private void download(Release release, Subtitle subtitle, LibrarySettings librar CleanAction cleanAction = new CleanAction(librarySettings); cleanAction.cleanUpFiles(release, path, videoFileName); } - if (librarySettings.isLibraryRemoveEmptyFolders() && release.getPath().isEmptyDir()) { - FileUtils.delete(release.getPath()); + if (librarySettings.removeEmptyFolders && release.path.isEmptyDir()) { + release.path.deletePath(); } } } - if (librarySettings.isLibraryBackupSubtitle()) { - String langFolder = subtitle.getLanguage() == null ? Language.ENGLISH.getName() : subtitle.getLanguage().getName(); - Path backupPath = librarySettings.getLibraryBackupSubtitlePath().resolve(langFolder); + if (librarySettings.backupSubtitle) { + String langFolder = + subtitle.language == null ? Language.ENGLISH.getName() : subtitle.language.getName(); + Path backupPath = librarySettings.backupSubtitlePath.resolve(langFolder); if (!backupPath.exists()) { try { @@ -109,8 +110,8 @@ private void download(Release release, Subtitle subtitle, LibrarySettings librar } } - if (librarySettings.isLibraryBackupUseWebsiteFileName()) { - subFile.copyToDirAndRename(backupPath, subtitle.getFileName()); + if (librarySettings.backupUseWebsiteFileName) { + subFile.copyToDirAndRename(backupPath, subtitle.fileName); } else { subFile.copyToDirAndRename(backupPath, subFileName); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/FileListAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/FileListAction.java index a01cdd09..6812e334 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/FileListAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/FileListAction.java @@ -9,44 +9,36 @@ import java.util.Optional; import java.util.Set; +import extensions.java.nio.file.Path.PathExt; +import lombok.RequiredArgsConstructor; +import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.set; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.listeners.IndexingProgressListener; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.control.VideoPatterns; -import org.lodder.subtools.sublibrary.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ Files.class, FileUtils.class }) +@RequiredArgsConstructor +@ExtensionMethod({ Files.class }) public class FileListAction { - private IndexingProgressListener indexingProgressListener; - private int progressFileIndex; - private int progressFilesTotal; - private final Settings settings; - private final static String subtitleExtension = "srt"; - private static final Logger LOGGER = LoggerFactory.getLogger(FileListAction.class); + private static final String SUBTITLE_EXTENSION = "srt"; + + private final Settings settings; + @set IndexingProgressListener indexingProgressListener; - public FileListAction(Settings settings) { - this.settings = settings; - } public List getFileListing(Path dir, boolean recursive, Language language, boolean forceSubtitleOverwrite) { - LOGGER.trace("getFileListing: dir [{}] Recursive [{}] languageCode [{}] forceSubtitleOverwrite [{}]", dir, recursive, language, - forceSubtitleOverwrite); - /* Reset progress counters */ - this.progressFileIndex = 0; - this.progressFilesTotal = 0; + LOGGER.trace("getFileListing: dir [{}] Recursive [{}] languageCode [{}] forceSubtitleOverwrite [{}]", + dir, recursive, language, forceSubtitleOverwrite); + int progressFileIndex = 0; + int progressFilesTotal = 0; /* Start listing process */ - return this._getFileListing(dir, recursive, language, forceSubtitleOverwrite); - } - - private List _getFileListing(Path dir, boolean recursive, Language language, boolean forceSubtitleOverwrite) { final List filelist = new ArrayList<>(); List contents; try { @@ -57,26 +49,26 @@ private List _getFileListing(Path dir, boolean recursive, Language languag } /* Increase progressTotalFiles count */ - this.progressFilesTotal += contents.size(); + progressFilesTotal += contents.size(); if (this.indexingProgressListener != null) { this.indexingProgressListener.progress(dir.toString()); } for (Path file : contents) { - /* Increase progressFileIndex */ - this.progressFileIndex++; + progressFileIndex++; /* Update progressListener */ if (this.indexingProgressListener != null) { /* Tell the progress listener the overall progress */ - int progress = (int) Math.floor((float) this.progressFileIndex / this.progressFilesTotal * 100); + int progress = (int) Math.floor((float) progressFileIndex / progressFilesTotal * 100); this.indexingProgressListener.progress(progress); } try { if (file.isRegularFile()) { - if (isValidVideoFile(file) && (forceSubtitleOverwrite || !fileHasSubtitles(file, language)) && !isExcludedFile(file)) { + if (isValidVideoFile(file) && (forceSubtitleOverwrite || !fileHasSubtitles(file, language)) && + !isExcludedFile(file)) { filelist.add(file); } } else if (recursive && !isExcludedDir(file)) { @@ -93,7 +85,7 @@ private List _getFileListing(Path dir, boolean recursive, Language languag } private boolean isExcludedDir(Path path) { - boolean excludedDir = settings.getExcludeList().stream().anyMatch(item -> item.isExcludedPath(path)); + boolean excludedDir = settings.excludeList.stream().anyMatch(item -> item.isExcludedPath(path)); if (excludedDir) { LOGGER.trace("isExcludedDir, skipping [{}]", path); } @@ -101,7 +93,7 @@ private boolean isExcludedDir(Path path) { } private boolean isExcludedFile(Path path) { - boolean excludedFile = settings.getExcludeList().stream().anyMatch(item -> item.isExcludedPath(path)); + boolean excludedFile = settings.excludeList.stream().anyMatch(item -> item.isExcludedPath(path)); if (excludedFile) { LOGGER.trace("isExcludedFile, skipping [{}]", path); } @@ -115,9 +107,9 @@ public boolean isValidVideoFile(Path file) { public boolean fileHasSubtitles(Path file, Language language) throws IOException { String extension = file.getExtension(); Optional subtitleNameOptional = VideoPatterns.EXTENSIONS.stream() - .filter(extension::equals) - .map(x -> file.changeExtension(subtitleExtension)) - .findAny(); + .filter(extension::equals) + .map(_ -> file.changeExtension(SUBTITLE_EXTENSION)) + .findAny(); if (subtitleNameOptional.isEmpty()) { return false; @@ -127,23 +119,22 @@ public boolean fileHasSubtitles(Path file, Language language) throws IOException if (f.exists()) { return true; } else { - String subtitleExtensionWithDot = "." + subtitleExtension; + String subtitleExtensionWithDot = "." + SUBTITLE_EXTENSION; Set langCodes = new HashSet<>(); - langCodes.add(language.getLangCode()); - langCodes.addAll(language.getLangCodesOther()); - String customLangCode = settings.getEpisodeLibrarySettings().getLangCodeMap().get(language); + langCodes.add(language.langCode); + langCodes.addAll(language.langCodesOther); + String customLangCode = settings.episodeLibrarySettings.langCodeMap.get(language); if (!StringUtils.isBlank(customLangCode)) { langCodes.add(customLangCode); } - List filters = langCodes.stream().map(word -> word + "." + subtitleExtension).toList(); + List filters = langCodes.stream().map(word -> word + "." + SUBTITLE_EXTENSION).toList(); String subtitleNameWithoutExtension = subtitleName.replace(subtitleExtensionWithDot, ""); - return file.getParent().list().map(FileUtils::getFileNameAsString).filter(fileName -> filters.stream().anyMatch(fileName::endsWith)) - .anyMatch(fileName -> fileName.contains(subtitleNameWithoutExtension)); + return file.getParent() + .list() + .map(PathExt::getFileNameAsString) + .filter(fileName -> filters.stream().anyMatch(fileName::endsWith)) + .anyMatch(fileName -> fileName.contains(subtitleNameWithoutExtension)); } } - - public void setIndexingProgressListener(IndexingProgressListener indexingProgressListener) { - this.indexingProgressListener = indexingProgressListener; - } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/RenameAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/RenameAction.java index dfd8d580..a9fe63c4 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/RenameAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/RenameAction.java @@ -4,6 +4,8 @@ import java.nio.file.Files; import java.nio.file.Path; +import lombok.RequiredArgsConstructor; +import lombok.experimental.ExtensionMethod; import org.lodder.subtools.multisubdownloader.lib.library.FilenameLibraryBuilder; import org.lodder.subtools.multisubdownloader.lib.library.LibraryActionType; import org.lodder.subtools.multisubdownloader.lib.library.LibraryOtherFileActionType; @@ -14,33 +16,30 @@ import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.RequiredArgsConstructor; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ FileUtils.class, Files.class }) +@ExtensionMethod({ Files.class }) @RequiredArgsConstructor public class RenameAction { + private static final Logger LOGGER = LoggerFactory.getLogger(RenameAction.class); + private final LibrarySettings librarySettings; private final Manager manager; private final UserInteractionHandler userInteractionHandler; - private static final Logger LOGGER = LoggerFactory.getLogger(RenameAction.class); - public void rename(Path f, Release release) { - String filename = switch (librarySettings.getLibraryAction()) { + String filename = switch (librarySettings.action) { case MOVE, NOTHING -> f.getFileNameAsString(); case MOVEANDRENAME, RENAME -> getNewFilename(f, release); }; LOGGER.trace("rename: filename [{}]", filename); - - Path newDir = switch (librarySettings.getLibraryAction()) { - case MOVE, MOVEANDRENAME -> PathLibraryBuilder.fromSettings(librarySettings, manager, userInteractionHandler).build(release); + + Path newDir = switch (librarySettings.action) { + case MOVE, MOVEANDRENAME -> + PathLibraryBuilder.fromSettings(librarySettings, manager, userInteractionHandler).build(release); case RENAME, NOTHING -> release.getPath(); }; if (!newDir.exists()) { @@ -54,21 +53,23 @@ public void rename(Path f, Release release) { } LOGGER.trace("rename: newDir [{}]", newDir); - Path file = release.getPath().resolve(release.getFileName()); + Path file = release.getPath().resolve(release.fileName); try { - if (librarySettings.hasLibraryAction(LibraryActionType.MOVE) || librarySettings.hasLibraryAction(LibraryActionType.MOVEANDRENAME)) { + if (librarySettings.hasLibraryAction(LibraryActionType.MOVE) || + librarySettings.hasLibraryAction(LibraryActionType.MOVEANDRENAME)) { LOGGER.info("Moving [{}] to the library folder [{}] , this might take a while... ", filename, newDir); file.moveToDirAndRename(newDir, filename); } else { - LOGGER.info("Moving [{}] to the library folder [{}] , this might take a while... ", filename, release.getPath()); + LOGGER.info("Moving [{}] to the library folder [{}] , this might take a while... ", filename, + release.getPath()); file.moveToDirAndRename(release.getPath(), filename); } if (!librarySettings.hasLibraryOtherFileAction(LibraryOtherFileActionType.NOTHING)) { new CleanAction(librarySettings).cleanUpFiles(release, newDir, filename); } - if (librarySettings.isLibraryRemoveEmptyFolders() && release.getPath().isEmptyDir()) { + if (librarySettings.removeEmptyFolders && release.getPath().isEmptyDir()) { Files.delete(release.getPath()); } } catch (IOException e) { @@ -77,11 +78,12 @@ public void rename(Path f, Release release) { } private String getNewFilename(Path f, Release release) { - FilenameLibraryBuilder filenameLibraryBuilder = FilenameLibraryBuilder.fromSettings(librarySettings, manager, userInteractionHandler); + FilenameLibraryBuilder filenameLibraryBuilder = + FilenameLibraryBuilder.fromSettings(librarySettings, manager, userInteractionHandler); String filename = filenameLibraryBuilder.build(release).toString(); if (release.hasExtension("srt")) { Language language = null; - if (librarySettings.isLibraryIncludeLanguageCode()) { + if (librarySettings.includeLanguageCode) { language = DetectLanguage.execute(f); if (language == null) { LOGGER.error("Unable to detect language, leaving language code blank"); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/SearchAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/SearchAction.java index 5db2937f..405c9697 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/SearchAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/SearchAction.java @@ -1,7 +1,12 @@ package org.lodder.subtools.multisubdownloader.actions; +import static manifold.ext.props.rt.api.PropOption.*; + import java.util.List; +import manifold.ext.props.rt.api.get; +import manifold.ext.props.rt.api.set; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.exceptions.SearchSetupException; @@ -14,29 +19,30 @@ import org.lodder.subtools.multisubdownloader.workers.SearchHandler; import org.lodder.subtools.multisubdownloader.workers.SearchManager; import org.lodder.subtools.sublibrary.Language; -import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.model.Release; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Getter(value = AccessLevel.PROTECTED) public abstract class SearchAction implements Runnable, Cancelable, SearchHandler { - private final Manager manager; - private final @NonNull Settings settings; - private final @NonNull SubtitleProviderStore subtitleProviderStore; - private StatusListener statusListener; - private SearchManager searchManager; - private List releases; - private static final Logger LOGGER = LoggerFactory.getLogger(SearchAction.class); + @val(Protected) Settings settings; + @val(Protected) SubtitleProviderStore subtitleProviderStore; + + @get(Protected) @set(Private) StatusListener statusListener; + @get(Protected) @set(Private) SearchManager searchManager; + @get(Protected) @set(Private) List releases; + @get(Protected) abstract Language language; + abstract @get(Protected) IndexingProgressListener indexingProgressListener; + abstract @get(Protected) UserInteractionHandler userInteractionHandler; + abstract @get(Protected) SearchProgressListener searchProgressListener; + + protected SearchAction(Settings settings, SubtitleProviderStore subtitleProviderStore) { + this.settings = settings; + this.subtitleProviderStore = subtitleProviderStore; + } + @Override public void run() { LOGGER.trace("SearchAction is being executed"); @@ -51,15 +57,13 @@ public void run() { } private void search() throws ActionException { - this.statusListener = this.getIndexingProgressListener(); - this.getIndexingProgressListener().reset(); - this.getSearchProgressListener().reset(); + this.statusListener = this.indexingProgressListener; + this.indexingProgressListener.reset(); + this.searchProgressListener.reset(); validate(); - Language language = this.getLanguage(); - - setStatusMessage(Messages.getString("SearchAction.StatusIndexing")); + setStatusMessage(Messages.getText("SearchAction.StatusIndexing")); this.releases = createReleases(); @@ -72,31 +76,32 @@ private void search() throws ActionException { return; } - this.getIndexingProgressListener().completed(); + this.indexingProgressListener.completed(); - this.statusListener = this.getSearchProgressListener(); + this.statusListener = this.searchProgressListener; /* Create a new SearchManager. */ this.searchManager = - SearchManager.createWithSettings(this.settings) - /* Tell the manager which language we want */ - .language(language) - /* Tell the manager where to push progressUpdates */ - .progressListener(getSearchProgressListener()) - /* Tell the manager how to handle user interactions */ - .userInteractionHandler(getUserInteractionHandler()) - /* Listen for when the manager tells us Subtitles are found */ - .onFound(this); + new SearchManager(settings, + /* Tell the manager which language we want */ + language, + /* Tell the manager where to push progressUpdates */ + searchProgressListener, + /* Tell the manager how to handle user interactions */ + userInteractionHandler, + /* Listen for when the manager tells us Subtitles are found */ + this); /* Tell the manager which providers to use */ + searchManager.reset(); this.subtitleProviderStore.getAllProviders().stream() - .filter(subtitleProvider -> settings.isSerieSource(subtitleProvider.getSubtitleSource())) - .forEach(searchManager::addProvider); + .filter(subtitleProvider -> settings.useSerieSource(subtitleProvider.subtitleSource)) + .forEach(searchManager::addProvider); /* Tell the manager which releases to search. */ this.releases.forEach(searchManager::addRelease); - setStatusMessage(Messages.getString("SearchAction.StatusSearching")); + setStatusMessage(Messages.getText("SearchAction.StatusSearching")); /* Tell the manager to start searching */ this.searchManager.start(); @@ -116,17 +121,8 @@ public boolean cancel(boolean mayInterruptIfRunning) { this.searchManager.cancel(mayInterruptIfRunning); } Thread.currentThread().interrupt(); - this.getIndexingProgressListener().completed(); - this.getSearchProgressListener().completed(); + this.indexingProgressListener.completed(); + this.searchProgressListener.completed(); return true; } - - protected abstract Language getLanguage(); - - protected abstract UserInteractionHandler getUserInteractionHandler(); - - protected abstract IndexingProgressListener getIndexingProgressListener(); - - protected abstract SearchProgressListener getSearchProgressListener(); - } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/UserInteractionHandlerAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/UserInteractionHandlerAction.java index 46d89289..22d02aa8 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/UserInteractionHandlerAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/actions/UserInteractionHandlerAction.java @@ -2,6 +2,7 @@ import java.util.List; +import lombok.RequiredArgsConstructor; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.lib.control.subtitles.sorting.SubtitleComparator; import org.lodder.subtools.multisubdownloader.settings.model.Settings; @@ -11,8 +12,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.RequiredArgsConstructor; - @RequiredArgsConstructor public class UserInteractionHandlerAction { @@ -36,37 +35,36 @@ public List subtitleSelection(final Release release, final boolean sub * @param dryRun dryRun * @return integer which subtitle is selected for downloading */ - public List subtitleSelection(Release release, final boolean subtitleSelectionDialog, final boolean dryRun) { + public List subtitleSelection(Release release, final boolean subtitleSelectionDialog, + final boolean dryRun) { // Sort subtitles by score - release.getMatchingSubs().sort(new SubtitleComparator()); + List subs = release.getMatchingSubs().stream().sorted(new SubtitleComparator()).toList(); if (dryRun) { - if (!release.getMatchingSubs().isEmpty()) { + if (!subs.isEmpty()) { userInteractionHandler.dryRunOutput(release); } } else { - if (!release.getMatchingSubs().isEmpty()) { + if (!subs.isEmpty()) { LOGGER.debug("determineWhatSubtitleDownload for videoFile: [{}] # found subs: [{}]", - release.getFileName(), release.getMatchingSubs().size()); - if (settings.isOptionsAlwaysConfirm()) { + release.fileName, subs.size()); + if (settings.optionsAlwaysConfirm) { return userInteractionHandler.selectSubtitles(release); - } else if (release.getMatchingSubs().size() == 1 - && release.getMatchingSubs().get(0).getSubtitleMatchType() == SubtitleMatchType.EXACT) { + } else if (subs.size() == 1 && subs.first.subtitleMatchType == SubtitleMatchType.EXACT) { LOGGER.debug("determineWhatSubtitleDownload: Exact Match"); - return List.of(release.getMatchingSubs().get(0)); - } else if (release.getMatchingSubs().size() > 1) { + return List.of(subs.first); + } else if (subs.size() > 1) { LOGGER.debug("determineWhatSubtitleDownload: Multiple subs detected"); // Automatic selection - List shortlist = userInteractionHandler.getAutomaticSelection(release.getMatchingSubs()); + List shortlist = userInteractionHandler.getAutomaticSelection(subs); shortlist.forEach(release::addMatchingSub); - // automatic selection results in 1 result - if (shortlist.size() == 1) { - return List.of(release.getMatchingSubs().get(0)); - } - // nothing match the minimum automatic selection value if (shortlist.isEmpty()) { + // nothing match the minimum automatic selection value return List.of(); + } else if (shortlist.size() == 1) { + // automatic selection results in 1 result + return List.of(subs.first); } // still more than 1 subtitle, let the user decide! @@ -74,15 +72,15 @@ public List subtitleSelection(Release release, final boolean subtitleS LOGGER.debug("determineWhatSubtitleDownload: Select subtitle with dialog"); return userInteractionHandler.selectSubtitles(release); } else { - LOGGER.info("Multiple subs detected for: [{}] Unhandleable for CMD! switch to GUI or use '--selection' as switch in de CMD", - release.getFileName()); + LOGGER.info("Multiple subs detected for: [{}] Unhandleable for CMD! switch to GUI or use " + + "'--selection' as switch in de CMD", release.fileName); } - } else if (release.getMatchingSubs().size() == 1) { + } else { LOGGER.debug("determineWhatSubtitleDownload: only one sub taking it!!!!"); - return List.of(release.getMatchingSubs().get(0)); + return List.of(subs.first); } } - LOGGER.debug("determineWhatSubtitleDownload: No subs found for [{}]", release.getFileName()); + LOGGER.debug("determineWhatSubtitleDownload: No subs found for [{}]", release.fileName); } return List.of(); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/CliOption.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/CliOption.java index 6af9c91b..897eb139 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/CliOption.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/CliOption.java @@ -1,12 +1,10 @@ package org.lodder.subtools.multisubdownloader.cli; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.get; import org.lodder.subtools.multisubdownloader.Messages; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor +@AllArgsConstructor public enum CliOption { HELP("help", false, "App.OptionHelpMsg"), NO_GUI("nogui", false, "App.OptionNoGuiMsg"), @@ -24,17 +22,17 @@ public enum CliOption { DRY_RUN("dryrun", false, "App.OptionDryRun"), CONFIRM_PROVIDER_MAPPING("confirmProviderMapping", false, "App.OptionConfirmProviderMapping"); - private final String value; - private final String longValue; - private final boolean hasArg; - private final String msgCode; + @get String value; + @get String longValue; + @get boolean hasArg; + @get String msgCode; CliOption(String value, boolean hasArg, String description) { this(value, null, hasArg, description); } public String getDescription() { - return Messages.getString(msgCode); + return Messages.getText(msgCode); } } \ No newline at end of file diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/actions/CliSearchAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/actions/CliSearchAction.java index d11ade2e..a66554b8 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/actions/CliSearchAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/actions/CliSearchAction.java @@ -1,17 +1,15 @@ package org.lodder.subtools.multisubdownloader.cli.actions; +import static manifold.ext.props.rt.api.PropOption.*; + import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.get; +import manifold.ext.props.rt.api.override; import org.lodder.subtools.multisubdownloader.CLI; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; @@ -26,119 +24,34 @@ import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.sublibrary.Language; -import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -import org.lodder.subtools.sublibrary.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@ExtensionMethod({FileUtils.class, Files.class}) -@Setter +@ExtensionMethod({Files.class}) public class CliSearchAction extends SearchAction { private static final Logger LOGGER = LoggerFactory.getLogger(CliSearchAction.class); - private final @NonNull CLI cli; - private final @NonNull FileListAction fileListAction; - @Getter - private final @NonNull Language language; - private final @NonNull ReleaseFactory releaseFactory; - private final @NonNull SubtitleFiltering filtering; + private final CLI cli; + private final FileListAction fileListAction; + private final ReleaseFactory releaseFactory; + private final SubtitleFiltering filtering; private final boolean overwriteSubtitles; - private final @NonNull List folders; + private final List folders; private final boolean recursive; - @Getter(value = AccessLevel.PROTECTED) - private final @NonNull IndexingProgressListener indexingProgressListener; - @Getter(value = AccessLevel.PROTECTED) - private final @NonNull SearchProgressListener searchProgressListener; - - public interface CliSearchActionBuilderManager { - CliSearchActionBuilderSubtitleProviderStore manager(Manager manager); - } - - public interface CliSearchActionBuilderSubtitleProviderStore { - CliSearchActionBuilderIndexingProgressListener subtitleProviderStore(SubtitleProviderStore subtitleProviderStore); - } - - public interface CliSearchActionBuilderIndexingProgressListener { - CliSearchActionBuilderSearchProgressListener indexingProgressListener(IndexingProgressListener indexingProgressListener); - } - - public interface CliSearchActionBuilderSearchProgressListener { - CliSearchActionBuilderCLI searchProgressListener(SearchProgressListener searchProgressListener); - } - - public interface CliSearchActionBuilderCLI { - CliSearchActionBuilderFileListAction cli(CLI cli); - } - - public interface CliSearchActionBuilderFileListAction { - CliSearchActionBuilderLanguage fileListAction(FileListAction fileListAction); - } - - public interface CliSearchActionBuilderLanguage { - CliSearchActionBuilderReleaseFactory language(Language language); - } - public interface CliSearchActionBuilderReleaseFactory { - CliSearchActionBuilderFiltering releaseFactory(ReleaseFactory releaseFactory); - } - - public interface CliSearchActionBuilderFiltering { - CliSearchActionBuilderFolders filtering(@NonNull SubtitleFiltering filtering); - } - - public interface CliSearchActionBuilderFolders { - CliSearchActionBuilderOther folders(List folders); - } - - public interface CliSearchActionBuilderOther { - CliSearchActionBuilderOther overwriteSubtitles(boolean overwriteSubtitles); - - CliSearchActionBuilderOther recursive(boolean recursive); - - CliSearchAction build() throws SearchSetupException; - } - - public static CliSearchActionBuilderManager createWithSettings(Settings settings) { - return new CliSearchActionBuilder(settings); - } - - @RequiredArgsConstructor - @Setter - @Accessors(fluent = true, chain = true) - public static class CliSearchActionBuilder - implements CliSearchActionBuilderSearchProgressListener, CliSearchActionBuilderIndexingProgressListener, - CliSearchActionBuilderSubtitleProviderStore, CliSearchActionBuilderCLI, - CliSearchActionBuilderFileListAction, CliSearchActionBuilderLanguage, CliSearchActionBuilderReleaseFactory, - CliSearchActionBuilderFiltering, CliSearchActionBuilderFolders, CliSearchActionBuilderOther, CliSearchActionBuilderManager { - private final Settings settings; - private Manager manager; - private SubtitleProviderStore subtitleProviderStore; - private IndexingProgressListener indexingProgressListener; - private SearchProgressListener searchProgressListener; - private CLI cli; - private FileListAction fileListAction; - private Language language; - private ReleaseFactory releaseFactory; - private SubtitleFiltering filtering; - private List folders; - private boolean overwriteSubtitles; - private boolean recursive; - - @Override - public CliSearchAction build() throws SearchSetupException { - return new CliSearchAction(manager, settings, subtitleProviderStore, indexingProgressListener, searchProgressListener, cli, - fileListAction, language, releaseFactory, filtering, folders, overwriteSubtitles, recursive); - } - } + @get @override Language language; + @get(Protected) @override IndexingProgressListener indexingProgressListener; + @get(Protected) @override SearchProgressListener searchProgressListener; - private CliSearchAction(Manager manager, Settings settings, SubtitleProviderStore subtitleProviderStore, - IndexingProgressListener indexingProgressListener, SearchProgressListener searchProgressListener, - CLI cli, FileListAction fileListAction, Language language, ReleaseFactory releaseFactory, - SubtitleFiltering filtering, List folders, boolean overwriteSubtitles, boolean recursive) throws SearchSetupException { - super(manager, settings, subtitleProviderStore); + public CliSearchAction(Settings settings, SubtitleProviderStore subtitleProviderStore, + IndexingProgressListener indexingProgressListener, SearchProgressListener searchProgressListener, CLI cli, + FileListAction fileListAction, Language language, ReleaseFactory releaseFactory, SubtitleFiltering filtering, + List folders, boolean overwriteSubtitles=true, boolean recursive=true) + throws SearchSetupException { + super(settings, subtitleProviderStore); this.indexingProgressListener = indexingProgressListener; this.searchProgressListener = searchProgressListener; this.cli = cli; @@ -156,11 +69,12 @@ private CliSearchAction(Manager manager, Settings settings, SubtitleProviderStor @Override protected List createReleases() { - fileListAction.setIndexingProgressListener(this.getIndexingProgressListener()); + fileListAction.indexingProgressListener = this.indexingProgressListener; List files = this.folders.stream() - .flatMap(folder -> fileListAction.getFileListing(folder, recursive, language, overwriteSubtitles).stream()) - .toList(); + .flatMap(folder -> fileListAction.getFileListing(folder, recursive, language, overwriteSubtitles) + .stream()) + .toList(); /* fix: remove carriage return from progressbar */ System.out.println(); @@ -171,8 +85,8 @@ protected List createReleases() { LOGGER.debug("# Files found to process [{}] ", total); - System.out.println(Messages.getString("CliSearchAction.ParsingFoundFiles")); - this.getIndexingProgressListener().progress(progress); + System.out.println(Messages.getText("CliSearchAction.ParsingFoundFiles")); + this.indexingProgressListener.progress(progress); List releases = new ArrayList<>(); for (Path file : files) { @@ -180,9 +94,9 @@ protected List createReleases() { progress = (int) Math.floor((float) index / total * 100); /* Tell progressListener which file we are processing */ - this.getIndexingProgressListener().progress(file.getFileNameAsString()); + this.indexingProgressListener.progress(file.getFileNameAsString()); - Release release = this.releaseFactory.createRelease(file, getUserInteractionHandler()); + Release release = this.releaseFactory.createRelease(file, userInteractionHandler); if (release == null) { continue; } @@ -190,7 +104,7 @@ protected List createReleases() { releases.add(release); /* Update progressListener */ - this.getIndexingProgressListener().progress(progress); + this.indexingProgressListener.progress(progress); } return releases; @@ -198,21 +112,23 @@ protected List createReleases() { @Override public void onFound(Release release, List subtitles) { - subtitles.stream().filter(subtitle -> filtering.useSubtitle(subtitle, release)).forEach(release::addMatchingSub); - if (getSearchManager().getProgress() < 100) { + subtitles.stream() + .filter(subtitle -> filtering.useSubtitle(subtitle, release)) + .forEach(release::addMatchingSub); + if (searchManager.progress < 100) { return; } - LOGGER.debug("found files for doDownload [{}]", getReleases().size()); + LOGGER.debug("found files for doDownload [{}]", releases.size()); /* stop printing progress */ - this.getSearchProgressListener().completed(); + this.searchProgressListener.completed(); - this.cli.download(getReleases()); + this.cli.download(releases); } @Override protected UserInteractionHandler getUserInteractionHandler() { - return new UserInteractionHandlerCLI(getSettings()); + return new UserInteractionHandlerCLI(settings); } @Override diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIFileindexerProgress.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIFileIndexerProgress.java similarity index 68% rename from MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIFileindexerProgress.java rename to MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIFileIndexerProgress.java index b5670916..51bdd16b 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIFileindexerProgress.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIFileIndexerProgress.java @@ -3,18 +3,18 @@ import org.lodder.subtools.multisubdownloader.actions.ActionException; import org.lodder.subtools.multisubdownloader.listeners.IndexingProgressListener; -public class CLIFileindexerProgress extends CLIProgress implements IndexingProgressListener { +public final class CLIFileIndexerProgress extends CLIProgress implements IndexingProgressListener { private String currentFile; - public CLIFileindexerProgress() { + public CLIFileIndexerProgress() { super(); currentFile = ""; } @Override public void progress(int progress) { - setProgress(progress); + this.progress = progress; this.printProgress(); } @@ -26,7 +26,7 @@ public void progress(String directory) { @Override public void completed() { - if (!this.isEnabled()) { + if (!enabled) { return; } this.disable(); @@ -34,12 +34,12 @@ public void completed() { @Override public void reset() { - this.setEnabled(true); + this.enabled = true; } @Override public void onError(ActionException exception) { - if (!this.isEnabled()) { + if (!enabled) { return; } System.out.println("Error: " + exception.getMessage()); @@ -47,7 +47,7 @@ public void onError(ActionException exception) { @Override public void onStatus(String message) { - if (!this.isEnabled()) { + if (!enabled) { return; } System.out.println(message); @@ -55,17 +55,24 @@ public void onStatus(String message) { @Override protected void printProgress() { - if (!isEnabled()) { + if (!enabled) { return; } - if (isVerbose()) { + if (verbose) { /* newlines to counter the return carriage from printProgBar() */ System.out.println(); System.out.println(this.currentFile); System.out.println(); } - this.printProgBar(this.getProgress()); + this.printProgBar(this.progress); + } + + // TODO: remove this when https://github.com/manifold-systems/manifold/issues/642 is fixed + @Override + public CLIFileIndexerProgress verbose(boolean verbose) { + super.verbose(verbose); + return this; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIProgress.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIProgress.java index 6820adbf..50a0f2f8 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIProgress.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLIProgress.java @@ -1,33 +1,31 @@ package org.lodder.subtools.multisubdownloader.cli.progress; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; +import static manifold.ext.props.rt.api.PropOption.*; -@Getter(value = AccessLevel.PROTECTED) -@Setter(value = AccessLevel.PROTECTED) -abstract class CLIProgress> { +import manifold.ext.props.rt.api.var; +import manifold.ext.rt.api.Self; - private int progress; - private boolean isEnabled; - private boolean isVerbose; +abstract sealed class CLIProgress permits CLIFileIndexerProgress, CLISearchProgress { + + @var(Protected) int progress; + @var(Protected) boolean enabled; + @var(Protected) boolean verbose; protected CLIProgress() { - isEnabled = true; - isVerbose = false; + enabled = true; + verbose = false; progress = 0; } public void disable() { - this.isEnabled = false; + this.enabled = false; /* Print a line */ System.out.println(); } - @SuppressWarnings("unchecked") - public T verbose(boolean isVerbose) { - this.isVerbose = isVerbose; - return (T) this; + public @Self CLIProgress verbose(boolean verbose) { + this.verbose = verbose; + return this; } protected abstract void printProgress(); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLISearchProgress.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLISearchProgress.java index 13a2c0f7..a95695d7 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLISearchProgress.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/cli/progress/CLISearchProgress.java @@ -1,14 +1,13 @@ package org.lodder.subtools.multisubdownloader.cli.progress; +import dnl.utils.text.table.TextTable; import org.lodder.subtools.multisubdownloader.actions.ActionException; import org.lodder.subtools.multisubdownloader.gui.dialog.progress.search.SearchProgressTableModel; import org.lodder.subtools.multisubdownloader.listeners.SearchProgressListener; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.sublibrary.model.Release; -import dnl.utils.text.table.TextTable; - -public class CLISearchProgress extends CLIProgress implements SearchProgressListener { +public final class CLISearchProgress extends CLIProgress implements SearchProgressListener { private final TextTable table; private final SearchProgressTableModel tableModel; @@ -20,19 +19,19 @@ public CLISearchProgress() { @Override public void progress(SubtitleProvider provider, int jobsLeft, Release release) { - this.tableModel.update(provider.getName(), jobsLeft, release == null ? "Done" : release.getFileName()); + this.tableModel.update(provider.name, jobsLeft, release == null ? "Done" : release.fileName); this.printProgress(); } @Override public void progress(int progress) { - setProgress(progress); + this.progress = progress; this.printProgress(); } @Override public void completed() { - if (!this.isEnabled()) { + if (!this.enabled) { return; } this.disable(); @@ -40,12 +39,12 @@ public void completed() { @Override public void reset() { - this.setEnabled(true); + this.enabled = true; } @Override public void onError(ActionException exception) { - if (!isEnabled()) { + if (!enabled) { return; } System.out.println("Error: " + exception.getMessage()); @@ -53,7 +52,7 @@ public void onError(ActionException exception) { @Override public void onStatus(String message) { - if (!isEnabled()) { + if (!enabled) { return; } System.out.println(message); @@ -61,17 +60,24 @@ public void onStatus(String message) { @Override protected void printProgress() { - if (!isEnabled()) { + if (!enabled) { return; } /* print table */ - if (isVerbose()) { + if (verbose) { System.out.println(); table.printTable(); } /* print progressbar */ - this.printProgBar(this.getProgress()); + this.printProgBar(this.progress); + } + + // TODO: remove this when https://github.com/manifold-systems/manifold/issues/642 is fixed + @Override + public CLISearchProgress verbose(boolean verbose) { + super.verbose(verbose); + return this; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/exceptions/SearchSetupException.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/exceptions/SearchSetupException.java index c96f28eb..6d9c2344 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/exceptions/SearchSetupException.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/exceptions/SearchSetupException.java @@ -2,9 +2,8 @@ import java.io.Serial; -import org.lodder.subtools.multisubdownloader.actions.ActionException; - import lombok.experimental.StandardException; +import org.lodder.subtools.multisubdownloader.actions.ActionException; @StandardException public class SearchSetupException extends ActionException { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Bootstrapper.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Bootstrapper.java index a5656e57..b649d364 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Bootstrapper.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Bootstrapper.java @@ -47,7 +47,7 @@ public void initialize(UserInteractionHandler userInteractionHandler) { this.registerProviders(providers, userInteractionHandler); } - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings({ "rawtypes", "unchecked" }) public List getProviders() { Reflections reflections = new Reflections("org.lodder.subtools.multisubdownloader"); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Container.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Container.java index bb53d5f4..330c217c 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Container.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/Container.java @@ -2,7 +2,12 @@ import java.util.HashMap; import java.util.Map; +import java.util.prefs.Preferences; +import org.lodder.subtools.multisubdownloader.framework.event.Emitter; +import org.lodder.subtools.multisubdownloader.settings.model.Settings; +import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; +import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; public class Container { @@ -16,4 +21,24 @@ public void bind(String name, LazySupplier resolver) { public Object make(String name) { return bindings.get(name).get(); } + + public SubtitleProviderStore makeSubtitleProviderStore() { + return (SubtitleProviderStore) make("SubtitleProviderStore"); + } + + public Manager makeManager() { + return (Manager) make("Manager"); + } + + public Settings makeSettings() { + return (Settings) make("Settings"); + } + + public Preferences makePreferences() { + return (Preferences) make("Preferences"); + } + + public Emitter makeEventEmitter() { + return (Emitter) make("EventEmitter"); + } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Emitter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Emitter.java index bad305a1..ee57eb0d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Emitter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Emitter.java @@ -16,7 +16,7 @@ public void fire(Event event) { } public void listen(String eventName, Handler handler) { - eventListeners.computeIfAbsent(eventName, k -> new ArrayList<>()).add(handler); + eventListeners.computeIfAbsent(eventName, _ -> new ArrayList<>()).add(handler); } public void unlisten(String eventName, Handler handler) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Event.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Event.java index 1b9934f9..9d923fc9 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Event.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/event/Event.java @@ -9,12 +9,7 @@ public class Event { private final String eventName; private final EventBag eventBag; - public Event(String name) { - this.eventName = name; - this.eventBag = new EventBag(); - } - - public Event(String name, EventBag bag) { + public Event(String name, EventBag bag=new EventBag()) { this.eventName = name; this.eventBag = bag; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/EventServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/EventServiceProvider.java index 9f7e24b2..77445b9d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/EventServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/EventServiceProvider.java @@ -1,5 +1,7 @@ package org.lodder.subtools.multisubdownloader.framework.service.providers; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; import org.lodder.subtools.multisubdownloader.framework.event.Emitter; @@ -7,10 +9,7 @@ public class EventServiceProvider implements ServiceProvider { - @Override - public int getPriority() { - return 0; - } + @val @override int priority = 0; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProvider.java index 288bfe6e..87c6a642 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProvider.java @@ -1,10 +1,11 @@ package org.lodder.subtools.multisubdownloader.framework.service.providers; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; public interface ServiceProvider { - int getPriority(); + @val int priority; void register(Container app, UserInteractionHandler userInteractionHandler); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProviderComparator.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProviderComparator.java index 7e5c0ae7..24c07a50 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProviderComparator.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/framework/service/providers/ServiceProviderComparator.java @@ -10,6 +10,6 @@ public class ServiceProviderComparator implements Comparator, S @Override public int compare(ServiceProvider a, ServiceProvider b) { - return Integer.compare(a.getPriority(), b.getPriority()); + return Integer.compare(a.priority, b.priority); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Menu.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Menu.java index 5336d78b..db3b3d7f 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Menu.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Menu.java @@ -1,20 +1,16 @@ package org.lodder.subtools.multisubdownloader.gui; -import java.io.Serial; - -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; - -import org.lodder.subtools.multisubdownloader.Messages; +import static org.lodder.subtools.multisubdownloader.Messages.*; +import javax.swing.*; import java.awt.event.ActionListener; +import java.io.Serial; public class Menu extends JMenuBar { @Serial private static final long serialVersionUID = -7384297314593169280L; + private JMenu mnFile; private JMenuItem mntmQuit; private JMenu mnView; @@ -47,31 +43,31 @@ public Menu() { } private void createComponents() { - mnFile = new JMenu(Messages.getString("Menu.Path")); - mntmQuit = new JMenuItem(Messages.getString("App.Close")); - mnView = new JMenu(Messages.getString("Menu.View")); - mnSearchResults = new JMenu(Messages.getString("Menu.SearchResults")); - chckbxmntmFileName = new JCheckBoxMenuItem(Messages.getString("Menu.Filename")); - chckbxmntmType = new JCheckBoxMenuItem(Messages.getString("Menu.Type")); - chckbxmntmTitle = new JCheckBoxMenuItem(Messages.getString("Menu.Title")); - chckbxmntmSeason = new JCheckBoxMenuItem(Messages.getString("App.Season")); - chckbxmntmEpisode = new JCheckBoxMenuItem(Messages.getString("App.Episode")); - chckbxmntmShowOnlyFound = new JCheckBoxMenuItem(Messages.getString("Menu.OnlyShowFound")); - mntmClearLog = new JMenuItem(Messages.getString("Menu.EraseLogging")); - mntmRenameSerieFiles = new JMenuItem(Messages.getString("Menu.RenameSerie")); - mntmRenameMovieFiles = new JMenuItem(Messages.getString("Menu.RenameMovie")); - mntmPreferences = new JMenuItem(Messages.getString("Menu.Preferences")); - mnSerieNames = new JMenu(Messages.getString("Menu.SerieNames")); - mntmTranslateShowNames = new JMenuItem(Messages.getString("Menu.MappingTvdbScene")); - mnImportExport = new JMenu(Messages.getString("Menu.ImportExport")); - mnEdit = new JMenu(Messages.getString("App.Edit")); - mnHelp = new JMenu(Messages.getString("Menu.Help")); - mntmExportTranslate = new JMenuItem(Messages.getString("Menu.ExportMappingTvdbScene")); - mntmImportTranslate = new JMenuItem(Messages.getString("Menu.ImportMappingTvdbScene")); - mntmExportPreferences = new JMenuItem(Messages.getString("Menu.ExportPreferences")); - mntmImportPreferences = new JMenuItem(Messages.getString("Menu.ImportPreferences")); - mntmAbout = new JMenuItem(Messages.getString("Menu.About")); - mntmCheckForUpdate = new JMenuItem(Messages.getString("Menu.CheckForUpdate")); + mnFile = new JMenu(getText("Menu.Path")); + mntmQuit = new JMenuItem(getText("App.Close")); + mnView = new JMenu(getText("Menu.View")); + mnSearchResults = new JMenu(getText("Menu.SearchResults")); + chckbxmntmFileName = new JCheckBoxMenuItem(getText("Menu.Filename")); + chckbxmntmType = new JCheckBoxMenuItem(getText("Menu.Type")); + chckbxmntmTitle = new JCheckBoxMenuItem(getText("Menu.Title")); + chckbxmntmSeason = new JCheckBoxMenuItem(getText("App.Season")); + chckbxmntmEpisode = new JCheckBoxMenuItem(getText("App.Episode")); + chckbxmntmShowOnlyFound = new JCheckBoxMenuItem(getText("Menu.OnlyShowFound")); + mntmClearLog = new JMenuItem(getText("Menu.EraseLogging")); + mntmRenameSerieFiles = new JMenuItem(getText("Menu.RenameSerie")); + mntmRenameMovieFiles = new JMenuItem(getText("Menu.RenameMovie")); + mntmPreferences = new JMenuItem(getText("Menu.Preferences")); + mnSerieNames = new JMenu(getText("Menu.SerieNames")); + mntmTranslateShowNames = new JMenuItem(getText("Menu.MappingTvdbScene")); + mnImportExport = new JMenu(getText("Menu.ImportExport")); + mnEdit = new JMenu(getText("App.Edit")); + mnHelp = new JMenu(getText("Menu.Help")); + mntmExportTranslate = new JMenuItem(getText("Menu.ExportMappingTvdbScene")); + mntmImportTranslate = new JMenuItem(getText("Menu.ImportMappingTvdbScene")); + mntmExportPreferences = new JMenuItem(getText("Menu.ExportPreferences")); + mntmImportPreferences = new JMenuItem(getText("Menu.ImportPreferences")); + mntmAbout = new JMenuItem(getText("Menu.About")); + mntmCheckForUpdate = new JMenuItem(getText("Menu.CheckForUpdate")); } private void addComponentsToMenu() { @@ -209,7 +205,7 @@ public Menu withExportTranslationsAction(Runnable exportTranslationsAction) { } public Menu withAboutAction(Runnable aboutAction) { - addActionListener(mntmAbout, arg -> aboutAction.run()); + addActionListener(mntmAbout, _ -> aboutAction.run()); return this; } @@ -254,7 +250,7 @@ public Menu withViewSeasonAction(Runnable viewSeasonAction) { } private void addActionListener(JMenuItem menuItem, Runnable actionListener) { - addActionListener(menuItem, arg -> actionListener.run()); + addActionListener(menuItem, _ -> actionListener.run()); } private void addActionListener(JMenuItem menuItem, ActionListener actionListener) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Splash.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Splash.java index 1be85ac1..5561159d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Splash.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/Splash.java @@ -1,33 +1,30 @@ package org.lodder.subtools.multisubdownloader.gui; +import static org.lodder.subtools.multisubdownloader.Messages.*; + import javax.swing.*; import java.awt.*; import java.io.Serial; import net.miginfocom.swing.MigLayout; -import org.lodder.subtools.multisubdownloader.Messages; public class Splash extends JWindow { @Serial private static final long serialVersionUID = -7795482367449509520L; - private JProgressBar progressBar; + private final JProgressBar progressBar; public Splash() { - initialize_ui(); - } - - public void initialize_ui() { setBounds(100, 100, 501, 100); - getContentPane().setLayout(new MigLayout("", "[][475px,center][]", "[][40px:n]")); + contentPane.setLayout(new MigLayout("", "[][475px,center][]", "[][40px:n]")); - JLabel label = new JLabel(Messages.getString("Splash.starting")); - getContentPane().add(label, "cell 1 0 2 1,alignx left"); + JLabel label = new JLabel(getText("Splash.starting")); + contentPane.add(label, "cell 1 0 2 1,alignx left"); progressBar = new JProgressBar(0, 100); progressBar.setIndeterminate(true); progressBar.setStringPainted(true); - getContentPane().add(progressBar, "cell 1 1,grow"); + contentPane.add(progressBar, "cell 1 1,grow"); Rectangle r = getBounds(); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); @@ -37,9 +34,10 @@ public void initialize_ui() { } - public void showSplash() { + public Splash showSplash() { setVisible(true); toFront(); + return this; } public void setProgressMsg(String msg) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/ToStringListCellRenderer.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/ToStringListCellRenderer.java index ec474374..895f47d7 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/ToStringListCellRenderer.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/ToStringListCellRenderer.java @@ -1,33 +1,34 @@ package org.lodder.subtools.multisubdownloader.gui; +import javax.swing.*; +import java.awt.*; import java.util.function.Function; -import javax.swing.JList; -import javax.swing.ListCellRenderer; - -import org.lodder.subtools.multisubdownloader.Messages; - -import java.awt.Component; - import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import org.lodder.subtools.multisubdownloader.Messages; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public final class ToStringListCellRenderer implements ListCellRenderer { + private final ListCellRenderer originalRenderer; private final Function toStringMapper; - public static ToStringListCellRenderer of(ListCellRenderer originalRenderer, Function toStringMapper) { + public static ToStringListCellRenderer of(ListCellRenderer originalRenderer, + Function toStringMapper) { return new ToStringListCellRenderer<>(originalRenderer, toStringMapper); } - public static ToStringListCellRenderer ofMessage(ListCellRenderer originalRenderer, Function toStringMapper) { - return of(originalRenderer, item -> Messages.getString(toStringMapper.apply(item))); + public static ToStringListCellRenderer ofMessage(ListCellRenderer originalRenderer, + Function toStringMapper) { + return of(originalRenderer, item -> Messages.getText(toStringMapper.apply(item))); } @Override - public Component getListCellRendererComponent(JList list, T value, int index, boolean isSelected, boolean cellHasFocus) { - return originalRenderer.getListCellRendererComponent(list, toStringMapper.apply(value), index, isSelected, cellHasFocus); + public Component getListCellRendererComponent(JList list, T value, int index, boolean isSelected, + boolean cellHasFocus) { + return originalRenderer.getListCellRendererComponent(list, toStringMapper.apply(value), index, isSelected, + cellHasFocus); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/FileGuiSearchAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/FileGuiSearchAction.java index 8fd79245..09f637e7 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/FileGuiSearchAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/FileGuiSearchAction.java @@ -4,10 +4,7 @@ import java.util.ArrayList; import java.util.List; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; +import org.jspecify.annotations.NonNull; import org.lodder.subtools.multisubdownloader.GUI; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.actions.FileListAction; @@ -19,86 +16,37 @@ import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.sublibrary.Language; -import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -public class FileGuiSearchAction extends GuiSearchAction { +public final class FileGuiSearchAction extends GuiSearchAction { private final @NonNull FileListAction filelistAction; - public interface FileGuiSearchActionBuilderManager { - FileGuiSearchActionBuilderSubtitleProviderStore manager(Manager manager); - } - - public interface FileGuiSearchActionBuilderSubtitleProviderStore { - FileGuiSearchActionBuilderGUI subtitleProviderStore(SubtitleProviderStore subtitleProviderStore); - } - - public interface FileGuiSearchActionBuilderGUI { - FileGuiSearchActionBuilderSearchPanel mainWindow(GUI mainWindow); - } - - public interface FileGuiSearchActionBuilderSearchPanel { - FileGuiSearchActionBuilderReleaseFactory searchPanel(SearchPanel searchPanel); - } - - public interface FileGuiSearchActionBuilderReleaseFactory { - FileGuiSearchActionBuilderBuild releaseFactory(ReleaseFactory releaseFactory); - } - - public interface FileGuiSearchActionBuilderBuild { - FileGuiSearchAction build(); - } - - public static FileGuiSearchActionBuilderManager createWithSettings(Settings settings) { - return new FileGuiSearchActionBuilder(settings); - } - - @RequiredArgsConstructor - @Setter - @Accessors(chain = true, fluent = true) - public static class FileGuiSearchActionBuilder - implements FileGuiSearchActionBuilderBuild, FileGuiSearchActionBuilderReleaseFactory, - FileGuiSearchActionBuilderSearchPanel, FileGuiSearchActionBuilderGUI, - FileGuiSearchActionBuilderSubtitleProviderStore, FileGuiSearchActionBuilderManager { - private final Settings settings; - private Manager manager; - private SubtitleProviderStore subtitleProviderStore; - private GUI mainWindow; - private SearchPanel searchPanel; - private ReleaseFactory releaseFactory; - - @Override - public FileGuiSearchAction build() { - return new FileGuiSearchAction(manager, settings, subtitleProviderStore, mainWindow, searchPanel, releaseFactory); - } - } - - private FileGuiSearchAction(Manager manager, Settings settings, SubtitleProviderStore subtitleProviderStore, GUI mainWindow, - SearchPanel searchPanel, ReleaseFactory releaseFactory) { - super(manager, settings, subtitleProviderStore, mainWindow, searchPanel, releaseFactory); + public FileGuiSearchAction(Settings settings, SubtitleProviderStore subtitleProviderStore, GUI mainWindow, + SearchPanel searchPanel, ReleaseFactory releaseFactory) { + super(settings, subtitleProviderStore, mainWindow, searchPanel, releaseFactory); this.filelistAction = new FileListAction(settings); } @Override protected void validate() throws SearchSetupException { String path = getInputPanel().getIncomingPath(); - if ("".equals(path) && !this.getSettings().hasDefaultFolders()) { - throw new SearchSetupException(Messages.getString("App.NoFolderSelected")); + if (path.isEmpty() && !this.settings.hasDefaultFolders()) { + throw new SearchSetupException(Messages.getText("App.NoFolderSelected")); } } @Override public void onFound(Release release, List subtitles) { - VideoTableModel model = (VideoTableModel) this.getSearchPanel().getResultPanel().getTable().getModel(); + VideoTableModel model = (VideoTableModel) this.searchPanel.resultPanel.getTable().getModel(); - List filteredSubtitles = - getFiltering() != null ? subtitles.stream().filter(subtitle -> getFiltering().useSubtitle(subtitle, release)).toList() : subtitles; + List filteredSubtitles = filtering != null ? + subtitles.stream().filter(subtitle -> filtering.useSubtitle(subtitle, release)).toList() : subtitles; filteredSubtitles.forEach(release::addMatchingSub); model.addRow(release); - getMainWindow().repaint(); + mainWindow.repaint(); /* Let GuiSearchAction also make some decisions */ super.onFound(release, filteredSubtitles); @@ -112,7 +60,7 @@ protected List createReleases() { boolean recursive = inputPanel.isRecursiveSelected(); boolean overwriteExistingSubtitles = inputPanel.isForceOverwrite(); - VideoTableModel model = (VideoTableModel) this.getSearchPanel().getResultPanel().getTable().getModel(); + VideoTableModel model = (VideoTableModel) this.searchPanel.resultPanel.getTable().getModel(); model.clearTable(); /* get a list of video files */ @@ -130,38 +78,40 @@ private List createReleases(List files) { int index = 0; int progress = 0; - this.getIndexingProgressListener().progress(progress); + this.indexingProgressListener.progress(progress); for (Path file : files) { index++; progress = (int) Math.floor((float) index / total * 100); /* Tell progressListener which file we are processing */ - this.getIndexingProgressListener().progress(file.getFileName().toString()); + this.indexingProgressListener.progress(file.getFileName().toString()); - Release r = getReleaseFactory().createRelease(file, getUserInteractionHandler()); + Release r = releaseFactory.createRelease(file, userInteractionHandler); if (r != null) { releases.add(r); } /* Update progressListener */ - this.getIndexingProgressListener().progress(progress); + this.indexingProgressListener.progress(progress); } return releases; } - private List getFiles(String filePath, Language language, boolean recursive, boolean overwriteExistingSubtitles) { + private List getFiles(String filePath, Language language, boolean recursive, + boolean overwriteExistingSubtitles) { /* Get a list of selected directories */ - List dirs = !filePath.isEmpty() ? List.of(Path.of(filePath)) : this.getSettings().getDefaultFolders(); + List dirs = !filePath.isEmpty() ? List.of(Path.of(filePath)) : this.settings.defaultFolders; /* Scan directories for video files */ /* Tell Action where to send progressUpdates */ - this.filelistAction.setIndexingProgressListener(this.getIndexingProgressListener()); + this.filelistAction.indexingProgressListener = this.indexingProgressListener; /* Start the getFileListing Action */ return dirs.stream() - .flatMap(dir -> this.filelistAction.getFileListing(dir, recursive, language, overwriteExistingSubtitles).stream()) - .toList(); + .flatMap(dir -> this.filelistAction.getFileListing(dir, recursive, language, overwriteExistingSubtitles) + .stream()) + .toList(); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/GuiSearchAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/GuiSearchAction.java index 8990188b..bb2183ed 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/GuiSearchAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/GuiSearchAction.java @@ -1,7 +1,11 @@ package org.lodder.subtools.multisubdownloader.gui.actions.search; +import static manifold.ext.props.rt.api.PropOption.*; + import java.util.List; +import manifold.ext.props.rt.api.get; +import manifold.ext.props.rt.api.override; import org.lodder.subtools.multisubdownloader.GUI; import org.lodder.subtools.multisubdownloader.UserInteractionHandlerGUI; import org.lodder.subtools.multisubdownloader.actions.SearchAction; @@ -15,28 +19,23 @@ import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.sublibrary.Language; -import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NonNull; - -@Getter(value = AccessLevel.PROTECTED) -public abstract class GuiSearchAction

extends SearchAction { +public abstract sealed class GuiSearchAction

extends SearchAction + permits FileGuiSearchAction, TextGuiSearchAction { - private final @NonNull GUI mainWindow; - private final @NonNull SearchPanel

searchPanel; - private final SubtitleFiltering filtering; - private final @NonNull ReleaseFactory releaseFactory; - private final IndexingProgressListener indexingProgressListener; - private final SearchProgressListener searchProgressListener; - private final UserInteractionHandlerGUI userInteractionHandler; + @get(Protected) GUI mainWindow; + @get(Protected) SearchPanel

searchPanel; + @get(Protected) SubtitleFiltering filtering; + @get(Protected) ReleaseFactory releaseFactory; + @get(Protected) @override IndexingProgressListener indexingProgressListener; + @get(Protected) @override SearchProgressListener searchProgressListener; + @get(Protected) @override UserInteractionHandlerGUI userInteractionHandler; - public GuiSearchAction(Manager manager, Settings settings, SubtitleProviderStore subtitleProviderStore, - GUI mainWindow, SearchPanel

searchPanel, ReleaseFactory releaseFactory) { - super(manager, settings, subtitleProviderStore); + GuiSearchAction(Settings settings, SubtitleProviderStore subtitleProviderStore, + GUI mainWindow, SearchPanel

searchPanel, ReleaseFactory releaseFactory) { + super(settings, subtitleProviderStore); this.mainWindow = mainWindow; this.searchPanel = searchPanel; this.filtering = new SubtitleFiltering(settings); @@ -53,12 +52,12 @@ public GuiSearchAction(Manager manager, Settings settings, SubtitleProviderStore } protected P getInputPanel() { - return this.getSearchPanel().getInputPanel(); + return this.searchPanel.inputPanel; } @Override protected Language getLanguage() { - return this.searchPanel.getInputPanel().getSelectedLanguage(); + return this.searchPanel.inputPanel.selectedLanguage; } @Override @@ -67,15 +66,19 @@ public void onFound(Release release, List subtitles) { return; } - VideoTableModel model = (VideoTableModel) this.searchPanel.getResultPanel().getTable().getModel(); + VideoTableModel model = (VideoTableModel) this.searchPanel.resultPanel.table.model; if (model.getRowCount() > 0) { - searchPanel.getResultPanel().enableButtons(); + searchPanel.resultPanel.enableButtons(); } - if (this.getSearchManager().getProgress() == 100) { - this.getSearchProgressListener().completed(); - searchPanel.getInputPanel().enableSearchButton(); + if (this.searchManager.progress == 100) { + this.searchProgressListener.completed(); + searchPanel.inputPanel.enableSearchButton(); } } + + public void reset() { + searchProgressListener.reset(); + } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/TextGuiSearchAction.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/TextGuiSearchAction.java index 8a15c8fe..082ebb5d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/TextGuiSearchAction.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/actions/search/TextGuiSearchAction.java @@ -3,9 +3,6 @@ import java.nio.file.Path; import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; import org.lodder.subtools.multisubdownloader.GUI; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.exceptions.SearchSetupException; @@ -15,71 +12,24 @@ import org.lodder.subtools.multisubdownloader.lib.ReleaseFactory; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; -import org.lodder.subtools.sublibrary.Manager; +import org.lodder.subtools.sublibrary.control.VideoPatterns.VideoExtensions; import org.lodder.subtools.sublibrary.model.MovieRelease; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; import org.lodder.subtools.sublibrary.model.TvRelease; import org.lodder.subtools.sublibrary.model.VideoSearchType; -public class TextGuiSearchAction extends GuiSearchAction { +public final class TextGuiSearchAction extends GuiSearchAction { - public interface FileGuiSearchActionBuilderManager { - TextGuiSearchActionBuilderSubtitleProviderStore manager(Manager manager); - } - - public interface TextGuiSearchActionBuilderSubtitleProviderStore { - TextGuiSearchActionBuilderGUI subtitleProviderStore(SubtitleProviderStore subtitleProviderStore); - } - - public interface TextGuiSearchActionBuilderGUI { - TextGuiSearchActionBuilderSearchPanel mainWindow(GUI mainWindow); - } - - public interface TextGuiSearchActionBuilderSearchPanel { - TextGuiSearchActionBuilderReleaseFactory searchPanel(SearchPanel searchPanel); - } - - public interface TextGuiSearchActionBuilderReleaseFactory { - TextGuiSearchActionBuilderBuild releaseFactory(ReleaseFactory releaseFactory); - } - - public interface TextGuiSearchActionBuilderBuild { - TextGuiSearchAction build(); - } - - public static FileGuiSearchActionBuilderManager createWithSettings(Settings settings) { - return new TextGuiSearchActionBuilder(settings); - } - - @RequiredArgsConstructor - @Setter - @Accessors(chain = true, fluent = true) - public static class TextGuiSearchActionBuilder - implements TextGuiSearchActionBuilderBuild, TextGuiSearchActionBuilderReleaseFactory, TextGuiSearchActionBuilderSearchPanel, - TextGuiSearchActionBuilderGUI, TextGuiSearchActionBuilderSubtitleProviderStore, FileGuiSearchActionBuilderManager { - private final Settings settings; - private Manager manager; - private SubtitleProviderStore subtitleProviderStore; - private GUI mainWindow; - private SearchPanel searchPanel; - private ReleaseFactory releaseFactory; - - @Override - public TextGuiSearchAction build() { - return new TextGuiSearchAction(manager, settings, subtitleProviderStore, mainWindow, searchPanel, releaseFactory); - } - } - - private TextGuiSearchAction(Manager manager, Settings settings, SubtitleProviderStore subtitleProviderStore, GUI mainWindow, - SearchPanel searchPanel, ReleaseFactory releaseFactory) { - super(manager, settings, subtitleProviderStore, mainWindow, searchPanel, releaseFactory); + public TextGuiSearchAction(Settings settings, SubtitleProviderStore subtitleProviderStore, GUI mainWindow, + SearchPanel searchPanel, ReleaseFactory releaseFactory) { + super(settings, subtitleProviderStore, mainWindow, searchPanel, releaseFactory); } @Override protected void validate() throws SearchSetupException { if (getInputPanel().getReleaseName().isEmpty()) { - throw new SearchSetupException(Messages.getString("App.NoReleaseEntered")); + throw new SearchSetupException(Messages.getText("App.NoReleaseEntered")); } } @@ -88,36 +38,31 @@ protected List createReleases() { String name = getInputPanel().getReleaseName(); VideoSearchType type = getInputPanel().getType(); - VideoTableModel model = (VideoTableModel) this.getSearchPanel().getResultPanel().getTable().getModel(); + VideoTableModel model = (VideoTableModel) this.searchPanel.resultPanel.getTable().getModel(); model.clearTable(); // TODO: Redefine what a "release" is. Release release = switch (type) { - case EPISODE -> TvRelease.builder() - .name(name) - .season(getInputPanel().getSeason()) - .episode(getInputPanel().getEpisode()) - .quality(getInputPanel().getQuality()) - .build(); - case MOVIE -> MovieRelease.builder() - .name(name) - .quality(getInputPanel().getQuality()) - .build(); - default -> getReleaseFactory().createRelease(Path.of(name), getUserInteractionHandler()); + case EPISODE -> + new TvRelease(name:name, season:inputPanel.season, episode:inputPanel.episode, quality:inputPanel.quality); + case MOVIE -> new MovieRelease(name:name, quality:inputPanel.quality); + default -> releaseFactory.createRelease(Path.of( + name + (VideoExtensions.values().stream().anyMatch(ext -> name.endsWith("." + ext)) ? "" : ".")), + userInteractionHandler); }; return release != null ? List.of(release) : List.of(); } @Override public void onFound(Release release, List subtitles) { - VideoTableModel model = (VideoTableModel) this.getSearchPanel().getResultPanel().getTable().getModel(); + VideoTableModel model = (VideoTableModel) this.searchPanel.resultPanel.getTable().getModel(); - List subtitlesFiltered = - getFiltering() != null ? subtitles.stream().filter(subtitle -> getFiltering().useSubtitle(subtitle, release)).toList() : subtitles; + List subtitlesFiltered = filtering != null ? + subtitles.stream().filter(subtitle -> filtering.useSubtitle(subtitle, release)).toList() : subtitles; subtitlesFiltered.forEach(release::addMatchingSub); // use automatic selection to reduce the selection for the user - List subtitlesFilteredAutomatic = getUserInteractionHandler().getAutomaticSelection(subtitlesFiltered); + List subtitlesFilteredAutomatic = userInteractionHandler.getAutomaticSelection(subtitlesFiltered); subtitlesFilteredAutomatic.forEach(model::addRow); /* Let GuiSearchAction also make some decisions */ diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MappingEpisodeNameDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MappingEpisodeNameDialog.java index df12e22a..b8fd6b7a 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MappingEpisodeNameDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MappingEpisodeNameDialog.java @@ -1,5 +1,13 @@ package org.lodder.subtools.multisubdownloader.gui.dialog; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; +import javax.swing.RowSorter.SortKey; +import javax.swing.border.EmptyBorder; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableRowSorter; +import java.awt.*; import java.io.Serial; import java.util.Arrays; import java.util.Comparator; @@ -9,28 +17,13 @@ import java.util.function.BiFunction; import java.util.function.Function; -import javax.swing.DefaultComboBoxModel; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.RowSorter; -import javax.swing.RowSorter.SortKey; -import javax.swing.border.EmptyBorder; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableModel; -import javax.swing.table.TableRowSorter; - +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; +import manifold.ext.props.rt.api.var; +import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.tuple.Pair; -import org.lodder.subtools.multisubdownloader.Messages; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.UserInteractionHandlerGUI; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.JButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.sublibrary.Manager; @@ -39,86 +32,145 @@ import org.lodder.subtools.sublibrary.model.TvRelease; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import java.awt.BorderLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JButtonExtension.class, AbstractButtonExtension.class, JComponentExtension.class }) public class MappingEpisodeNameDialog extends MultiSubDialog { - @Serial - private static final long serialVersionUID = 1L; - private final JPanel contentPanel = new JPanel(); - private JTable table; - private final Manager manager; + @Serial private static final long serialVersionUID = 1L; + private final MappingTableModel mappingTableModel; private final SubtitleProviderStore subtitleProviderStore; - private final UserInteractionHandlerGUI userInteractionHandler; - private Optional selectedSubtitleProvider; private final JButton btnAddCustomMapping; + private final JTable table; + private Optional selectedSubtitleProvider; private MappingType selectedMappingType; - /** - * Create the dialog. - */ - public MappingEpisodeNameDialog(JFrame frame, final SettingsControl prefCtrl, Manager manager, SubtitleProviderStore subtitleProviderStore, - UserInteractionHandlerGUI userInteractionHandler) { - super(frame, Messages.getString("MappingEpisodeNameDialog.Title"), true); - this.manager = manager; + public MappingEpisodeNameDialog(@Nullable JFrame frame=null, Manager manager, + SubtitleProviderStore subtitleProviderStore, + UserInteractionHandlerGUI userInteractionHandler) { + super(frame, getText("MappingEpisodeNameDialog.Title"), true); this.subtitleProviderStore = subtitleProviderStore; - this.userInteractionHandler = userInteractionHandler; - this.btnAddCustomMapping = new JButton(Messages.getString("MappingEpisodeNameDialog.ChangeMapping")); this.mappingTableModel = new MappingTableModel(manager); - initialize(); + setResizable(true); + setBounds(150, 150, 650, 400); + + table = new JTable().model(mappingTableModel).rowSorter(TableRowSorter::new); + + contentPane + .layout(new BorderLayout()) + .addComponent(BorderLayout.CENTER, new JPanel() + .border(new EmptyBorder(5, 5, 5, 5)) + .layout(new GridBagLayout() + .columnWidths(new int[]{0, 0}) + .rowHeights(new int[]{0, 40, 0}) + .columnWeights(new double[]{1.0, Double.MIN_VALUE}) + .rowWeights(new double[]{0.0, 1.0, Double.MIN_VALUE})) + // select provider panel + .addComponent(new JPanel() + .addComponent(new JLabel(getText("MappingEpisodeNameDialog.SelectProvider"))) + .addComponent(new JComboBox<>() + .model(new DefaultComboBoxModel<>(MappingType.values())) + .itemListener(event -> selectMappingType((MappingType) event.getItem())))) + .addComponent(new JPanel(), + new GridBagConstraints().insets(new Insets(0, 0, 5, 0)) + .fill(GridBagConstraints.BOTH).gridx(0).gridy(0)) + .addComponent(new JScrollPane().viewportView(table), + new GridBagConstraints().fill(GridBagConstraints.BOTH).gridx(0).gridy(1))) + // button panel + .addComponent(BorderLayout.SOUTH, new JPanel() + .layout(new MigLayout("", "[25px][50px][grow][50px][grow][50px][25px]", + "[][25px,grow,fill]")) + .addComponent("skip", new JButton(getText("MappingEpisodeNameDialog.DeleteRow")) + .actionListener(_ -> { + int rowNbr = table.convertRowIndexToModel(table.getSelectedRow()); + MappingTableModel model = (MappingTableModel) table.getModel(); + Row row = (Row) model.getDataVector().get(rowNbr); + manager.getCache(CacheType.DISK, row.key).remove(); + if (row.selectionForKeyPrefix.deleteOtherFunction() != null) { + manager.getCache(CacheType.DISK, + row.selectionForKeyPrefix.deleteOtherFunction().apply(row.key)).remove(); + } + model.removeRow(rowNbr); + })) + .addComponent("skip", btnAddCustomMapping = + new JButton(getText("MappingEpisodeNameDialog.ChangeMapping")) + .actionListener(() -> { + int rowNbr = table.convertRowIndexToModel(table.getSelectedRow()); + MappingTableModel model = (MappingTableModel) table.getModel(); + + Row row = (Row) model.getDataVector().get(rowNbr); + String currentName = row.serieMapping.name; + + String message = getText("MappingEpisodeNameDialog.enterNewNameForSerie", + currentName); + selectedSubtitleProvider.ifPresent(provider -> + userInteractionHandler.enter(message).ifPresent(newName -> { + TvRelease tvRelease = new TvRelease( + name:currentName, + season:row.serieMapping.season, + episode:1, + originalName:currentName, + customName:newName); + try { + provider.getProviderSerieId(tvRelease).ifPresentOrElse(serieId -> { + row.serieMapping = + new SerieMapping(currentName, serieId.providerId, serieId.providerName, + serieId.season); + List sortKeys = table.rowSorter.sortKeys; + selectMappingType(selectedMappingType); + table.rowSorter.sortKeys = sortKeys; + }, () -> userInteractionHandler.message( + getText("MappingEpisodeNameDialog.NoResultsFoundForSerieName", newName), + getText("App.Info"))); + } catch (Exception e) { + userInteractionHandler.message(getText("App.ErrorOccurred", e.getMessage()), + getText("App.Error")); + } + })); + })) + .addComponent("skip", new JButton(getText("App.Close")) + .defaultButtonFor(getRootPane()) + .actionListener(() -> setVisible(false)) + .actionCommand(getText("App.Close")))); + selectMappingType(MappingType.values()[0]); } private void selectMappingType(MappingType mappingType) { this.selectedMappingType = mappingType; - this.selectedSubtitleProvider = subtitleProviderStore.getAllProviders().stream() - .filter(subtitleProvider -> subtitleProvider.getProviderName().equals(mappingType.getProviderName())) - .findAny(); - btnAddCustomMapping.setEnabled(selectedSubtitleProvider.isPresent()); - mappingTableModel.setMappingType(mappingType); + this.selectedSubtitleProvider = subtitleProviderStore.getAllProviders() + .stream() + .filter(subtitleProvider -> subtitleProvider.providerName.equals(mappingType.providerName)) + .findAny(); + btnAddCustomMapping.enabled = selectedSubtitleProvider.isPresent(); + mappingTableModel.mappingType = mappingType; repaint(); } - @Getter public enum MappingType { TVDB("TVDB", "TVDB", - new SelectionForKeyPrefix("", "TVDB-serieId-", k -> k.replace("-serieId-", "-tvdbSerie-"))), - ADDIC7ED("Addic7ed", SubtitleSource.ADDIC7ED, - new SelectionForKeyPrefix("", "ADDIC7ED-serieName-name:"), - new SelectionForKeyPrefix("", "ADDIC7ED-serieName-tvdbId:")), + new SelectionForKeyPrefix("", "TVDB-serieId-", k -> k.replace("-serieId-", "-tvdbSerie-"))), + ADDIC7ED("Addic7ed", SubtitleSource.ADDIC7ED, new SelectionForKeyPrefix("", "ADDIC7ED-serieName-name:"), + new SelectionForKeyPrefix("", "ADDIC7ED-serieName-tvdbId:")), ADDIC7ED_PROXY("Addic7ed (Proxy)", SubtitleSource.ADDIC7ED.name() + "-GESTDOWN", - new SelectionForKeyPrefix("", "ADDIC7ED-GESTDOWN-serieName-name:"), - new SelectionForKeyPrefix("", "ADDIC7ED-GESTDOWN-serieName-tvdbId:")), - SUBSCENE("Subscene", SubtitleSource.SUBSCENE, - new SelectionForKeyPrefix("", "SUBSCENE-serieName-name:"), - new SelectionForKeyPrefix("", "SUBSCENE-serieName-tvdbId:")), + new SelectionForKeyPrefix("", "ADDIC7ED-GESTDOWN-serieName-name:"), + new SelectionForKeyPrefix("", "ADDIC7ED-GESTDOWN-serieName-tvdbId:")), + SUBSCENE("Subscene", SubtitleSource.SUBSCENE, new SelectionForKeyPrefix("", "SUBSCENE-serieName-name:"), + new SelectionForKeyPrefix("", "SUBSCENE-serieName-tvdbId:")), TV_SUBTITLES("TVSubtitles", SubtitleSource.TVSUBTITLES, - new SelectionForKeyPrefix("", "TVSUBTITLES-serieName-name:"), - new SelectionForKeyPrefix("", "TVSUBTITLES-serieName-tvdbId:")), + new SelectionForKeyPrefix("", "TVSUBTITLES-serieName-name:"), + new SelectionForKeyPrefix("", "TVSUBTITLES-serieName-tvdbId:")), OPEN_SUBTITLES("OpenSubtitles", SubtitleSource.OPENSUBTITLES, - new SelectionForKeyPrefix("", "OPENSUBTITLES-serieName-name:"), - new SelectionForKeyPrefix("", "OPENSUBTITLES-serieName-tvdbId:")), - PODNAPISI("Podnapisi", SubtitleSource.PODNAPISI, - new SelectionForKeyPrefix("", "PODNAPISI-serieName-name:"), - new SelectionForKeyPrefix("", "PODNAPISI-serieName-tvdbId:")); - - public static final BiFunction>> MAPPING_SUPPLIER; - private final String name; - private final String providerName; - private final String nameColumn; - private final String mappingColumn; - private final String providerNameColumn; - private final SelectionForKeyPrefix[] selectionForKeyPrefixList; + new SelectionForKeyPrefix("", "OPENSUBTITLES-serieName-name:"), + new SelectionForKeyPrefix("", "OPENSUBTITLES-serieName-tvdbId:")), + PODNAPISI("Podnapisi", SubtitleSource.PODNAPISI, new SelectionForKeyPrefix("", "PODNAPISI-serieName-name:"), + new SelectionForKeyPrefix("", "PODNAPISI-serieName-tvdbId:")); + + public static final BiFunction>> + MAPPING_SUPPLIER; + @val String name; + @val String providerName; + @val String nameColumn; + @val String mappingColumn; + @val String providerNameColumn; + @val SelectionForKeyPrefix[] selectionForKeyPrefixList; @Override public String toString() { @@ -126,11 +178,8 @@ public String toString() { } static { - MAPPING_SUPPLIER = (manager, selectionForKeyPrefix) -> manager.valueBuilder() - .cacheType(CacheType.DISK) - .keyFilter(k -> k.startsWith(selectionForKeyPrefix.keyPrefix)) - .returnType(SerieMapping.class) - .getEntries(); + MAPPING_SUPPLIER = (manager, selectionForKeyPrefix) -> + manager.getCache(CacheType.DISK, k -> k.startsWith(selectionForKeyPrefix.keyPrefix)).getEntries(); } MappingType(String name, SubtitleSource subtitleSource, SelectionForKeyPrefix... selectionForKeyPrefixList) { @@ -140,9 +189,9 @@ public String toString() { MappingType(String name, String providerName, SelectionForKeyPrefix... selectionForKeyPrefixList) { this.name = name; this.providerName = providerName; - this.nameColumn = Messages.getString("MappingEpisodeNameDialog.SceneShowName"); - this.mappingColumn = Messages.getString("MappingEpisodeNameDialog.ProviderId"); - this.providerNameColumn = Messages.getString("MappingEpisodeNameDialog.ProviderName"); + this.nameColumn = getText("MappingEpisodeNameDialog.SceneShowName"); + this.mappingColumn = getText("MappingEpisodeNameDialog.ProviderId"); + this.providerNameColumn = getText("MappingEpisodeNameDialog.ProviderName"); this.selectionForKeyPrefixList = selectionForKeyPrefixList; } } @@ -153,18 +202,14 @@ public SelectionForKeyPrefix(String name, String keyPrefix) { } } - @Getter - @Setter - @RequiredArgsConstructor private static class Row extends Vector { - @Serial - private static final long serialVersionUID = 8620670431074648999L; - private final String key; - private SerieMapping serieMapping; - private final SelectionForKeyPrefix selectionForKeyPrefix; + @Serial private static final long serialVersionUID = 8620670431074648999L; + @val String key; + @val SelectionForKeyPrefix selectionForKeyPrefix; + @var SerieMapping serieMapping; public Row(String key, String name, String providerId, String providerName, SerieMapping serieMapping, - SelectionForKeyPrefix selectionForKeyPrefix) { + SelectionForKeyPrefix selectionForKeyPrefix) { this.key = key; this.serieMapping = serieMapping; this.selectionForKeyPrefix = selectionForKeyPrefix; @@ -174,30 +219,31 @@ public Row(String key, String name, String providerId, String providerName, Seri } } - @RequiredArgsConstructor + @AllArgsConstructor private static class MappingTableModel extends DefaultTableModel { - @Serial - private static final long serialVersionUID = 7860605766969472980L; - private final Manager manager; + @Serial private static final long serialVersionUID = 7860605766969472980L; + @val Manager manager; void setMappingType(MappingType mappingType) { - setDataVector(null, new String[] { mappingType.getNameColumn(), mappingType.getMappingColumn(), mappingType.getProviderNameColumn() }); - Arrays.stream(mappingType.getSelectionForKeyPrefixList()) - .flatMap(selectionForKeyPrefix -> MappingType.MAPPING_SUPPLIER.apply(manager, selectionForKeyPrefix).stream() - .map(serieMappingPair -> { - SerieMapping serieMapping = serieMappingPair.getValue(); - String name = serieMapping.getName(); - String providerId = serieMapping.getProviderId() == null ? "" : serieMapping.getProviderId(); - String providerName = serieMapping.getProviderName(); - if (providerId.contains("/")) { - providerId = providerId.substring(providerId.lastIndexOf("/") + 1); - } - providerId = providerId.replace(".html", ""); - return new Row(serieMappingPair.getKey(), name, providerId, providerName, serieMapping, selectionForKeyPrefix); - })) - .sorted(Comparator.comparing(row -> row.getSerieMapping() == null || row.getSerieMapping().getProviderName() == null ? "zzz" - : row.getSerieMapping().getName())) - .forEach(this::addRow); + setDataVector(null, + new String[]{mappingType.nameColumn, mappingType.mappingColumn, mappingType.providerNameColumn}); + Arrays.stream(mappingType.selectionForKeyPrefixList) + .flatMap(selectionForKeyPrefix -> MappingType.MAPPING_SUPPLIER.apply(manager, selectionForKeyPrefix) + .stream() + .map(serieMappingPair -> { + SerieMapping serieMapping = serieMappingPair.getValue(); + String providerId = serieMapping.providerId == null ? "" : serieMapping.providerId; + if (providerId.contains("/")) { + providerId = providerId.substring(providerId.lastIndexOf("/") + 1); + } + providerId = providerId.replace(".html", ""); + return new Row(serieMappingPair.getKey(), serieMapping.name, providerId, + serieMapping.providerName, serieMapping, selectionForKeyPrefix); + })) + .sorted(Comparator.comparing( + row -> row.serieMapping == null || row.serieMapping.providerName == null ? "zzz" : + row.serieMapping.name)) + .forEach(this::addRow); } @Override @@ -205,131 +251,4 @@ public boolean isCellEditable(int row, int col) { return false; } } - - private void initialize() { - setResizable(true); - setBounds(150, 150, 650, 400); - getContentPane().setLayout(new BorderLayout()); - contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); - getContentPane().add(contentPanel, BorderLayout.CENTER); - GridBagLayout gbl_contentPanel = new GridBagLayout(); - gbl_contentPanel.columnWidths = new int[] { 0, 0 }; - gbl_contentPanel.rowHeights = new int[] { 0, 40, 0 }; - gbl_contentPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE }; - gbl_contentPanel.rowWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE }; - contentPanel.setLayout(gbl_contentPanel); - { - JPanel selectionPane = new JPanel(); - contentPanel.add(selectionPane); - - JLabel lblDefaultIncomingFolder = new JLabel(Messages.getString("MappingEpisodeNameDialog.SelectProvider")); - selectionPane.add(lblDefaultIncomingFolder); - - JComboBox mappingTypeList = new JComboBox<>(); - mappingTypeList.setModel(new DefaultComboBoxModel<>(MappingType.values())); - mappingTypeList.addItemListener(arg0 -> selectMappingType((MappingType) arg0.getItem())); - selectMappingType(MappingType.values()[0]); - selectionPane.add(mappingTypeList); - } - { - JPanel pnlButtons = new JPanel(); - GridBagConstraints gbc_pnlButtons = new GridBagConstraints(); - gbc_pnlButtons.insets = new Insets(0, 0, 5, 0); - gbc_pnlButtons.fill = GridBagConstraints.BOTH; - gbc_pnlButtons.gridx = 0; - gbc_pnlButtons.gridy = 0; - contentPanel.add(pnlButtons, gbc_pnlButtons); - } - { - JScrollPane scrollPane = new JScrollPane(); - GridBagConstraints gbc_scrollPane = new GridBagConstraints(); - gbc_scrollPane.fill = GridBagConstraints.BOTH; - gbc_scrollPane.gridx = 0; - gbc_scrollPane.gridy = 1; - contentPanel.add(scrollPane, gbc_scrollPane); - { - table = new JTable(); - - table.setModel(mappingTableModel); - RowSorter sorter = new TableRowSorter<>(table.getModel()); - table.setRowSorter(sorter); - scrollPane.setViewportView(table); - } - } - { - JPanel buttonPane = new JPanel(); - getContentPane().add(buttonPane, BorderLayout.SOUTH); - buttonPane.setLayout(new MigLayout("", "[25px][50px][grow][50px][grow][50px][25px]", "[][25px,grow,fill]")); - - { - JButton btnDeleteSelectedRow = new JButton(Messages.getString("MappingEpisodeNameDialog.DeleteRow")); - btnDeleteSelectedRow.addActionListener(arg0 -> { - int rowNbr = table.convertRowIndexToModel(table.getSelectedRow()); - MappingTableModel model = (MappingTableModel) table.getModel(); - - Row row = (Row) model.getDataVector().get(rowNbr); - String key = row.getKey(); - manager.valueBuilder() - .cacheType(CacheType.DISK) - .key(key) - .remove(); - if (row.getSelectionForKeyPrefix().deleteOtherFunction() != null) { - manager.valueBuilder() - .cacheType(CacheType.DISK) - .key(row.getSelectionForKeyPrefix().deleteOtherFunction().apply(key)) - .remove(); - } - model.removeRow(rowNbr); - }); - buttonPane.add(btnDeleteSelectedRow, "skip"); - } - - { - btnAddCustomMapping.withActionListener(() -> { - int rowNbr = table.convertRowIndexToModel(table.getSelectedRow()); - MappingTableModel model = (MappingTableModel) table.getModel(); - - Row row = (Row) model.getDataVector().get(rowNbr); - String currentName = row.getSerieMapping().getName(); - - String message = Messages.getString("MappingEpisodeNameDialog.enterNewNameForSerie", currentName); - selectedSubtitleProvider.ifPresent(provider -> { - userInteractionHandler.enter(message, message).ifPresent(newName -> { - TvRelease tvRelease = TvRelease.builder() - .name(currentName) - .season(row.getSerieMapping().getSeason()) - .episode(1) - .originalName(currentName) - .customName(newName).build(); - try { - provider.getProviderSerieId(tvRelease).ifPresentOrElse(providerSerieId -> { - SerieMapping newSerieMapping = - new SerieMapping(currentName, providerSerieId.getProviderId(), providerSerieId.getProviderName(), - providerSerieId.getSeason()); - row.setSerieMapping(newSerieMapping); - List sortKeys = table.getRowSorter().getSortKeys(); - selectMappingType(selectedMappingType); - table.getRowSorter().setSortKeys(sortKeys); - }, () -> userInteractionHandler.message( - Messages.getString("MappingEpisodeNameDialog.NoResultsFoundForSerieName", newName), - Messages.getString("App.Info"))); - } catch (Exception e) { - userInteractionHandler.message( - Messages.getString("App.ErrorOccurred", e.getMessage()), Messages.getString("App.Error")); - } - }); - }); - }); - buttonPane.add(btnAddCustomMapping, "skip"); - } - - { - new JButton(Messages.getString("App.Close")) - .defaultButtonFor(getRootPane()) - .withActionListener(() -> setVisible(false)) - .withActionCommand(Messages.getString("App.Close")) - .addTo(buttonPane, "skip"); - } - } - } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MultiSubDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MultiSubDialog.java index 93057c77..b23b9895 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MultiSubDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/MultiSubDialog.java @@ -1,33 +1,26 @@ package org.lodder.subtools.multisubdownloader.gui.dialog; -import javax.swing.JDialog; -import javax.swing.JFrame; - -import java.awt.Frame; -import java.awt.Rectangle; +import javax.swing.*; +import java.awt.*; import java.io.Serial; +import org.jspecify.annotations.Nullable; + public class MultiSubDialog extends JDialog { @Serial private static final long serialVersionUID = -2357021997104425566L; - public MultiSubDialog(JFrame frame, String title, boolean modal) { + public MultiSubDialog(@Nullable JFrame frame=null, String title, boolean modal) { super(frame); setTitle(title); setModal(modal); } - public MultiSubDialog(String title, boolean modal) { - super(); - setTitle(title); - setModal(modal); - } - protected void setDialogLocation(Frame f) { Rectangle r = f.getBounds(); - int x = r.x + (r.width - getSize().width) / 2; - int y = r.y + (r.height - getSize().height) / 2; + int x = r.x + (r.width - size.width) / 2; + int y = r.y + (r.height - size.height) / 2; setLocation(x, y); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/PreferenceDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/PreferenceDialog.java index 01a1be45..878cf48f 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/PreferenceDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/PreferenceDialog.java @@ -1,23 +1,16 @@ package org.lodder.subtools.multisubdownloader.gui.dialog; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; +import javax.swing.border.*; +import java.awt.*; import java.io.Serial; import java.util.concurrent.atomic.AtomicInteger; -import javax.swing.JButton; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.SwingConstants; -import javax.swing.border.EmptyBorder; - import org.lodder.subtools.multisubdownloader.GUI; -import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.framework.event.Emitter; import org.lodder.subtools.multisubdownloader.framework.event.Event; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.JButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.container.ContainerExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; import org.lodder.subtools.multisubdownloader.gui.panels.preference.EpisodeLibraryPanel; import org.lodder.subtools.multisubdownloader.gui.panels.preference.GeneralPanel; import org.lodder.subtools.multisubdownloader.gui.panels.preference.MovieLibraryPanel; @@ -28,95 +21,76 @@ import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import java.awt.BorderLayout; -import java.awt.FlowLayout; - -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ JComponentExtension.class, ContainerExtension.class, AbstractButtonExtension.class, JButtonExtension.class }) public class PreferenceDialog extends MultiSubDialog { - @Serial - private static final long serialVersionUID = -4910124272966075979L; + @Serial private static final long serialVersionUID = -4910124272966075979L; private final SettingsControl settingsCtrl; private final Emitter eventEmitter; - private final GeneralPanel pnlGeneral; private final EpisodeLibraryPanel pnlEpisodeLibrary; private final MovieLibraryPanel pnlMovieLibrary; private final OptionsPanel pnlOptions; private final SerieProvidersPanel pnlSerieSources; - public PreferenceDialog(GUI gui, final SettingsControl settingsCtrl, Emitter eventEmitter, - Manager manager, UserInteractionHandler userInteractionHandler) { - super(gui, Messages.getString("PreferenceDialog.Title"), true); + public PreferenceDialog(GUI gui, final SettingsControl settingsCtrl, Emitter eventEmitter, Manager manager, + UserInteractionHandler userInteractionHandler) { + super(gui, getText("PreferenceDialog.Title"), true); this.settingsCtrl = settingsCtrl; this.eventEmitter = eventEmitter; setResizable(false); setModalityType(ModalityType.APPLICATION_MODAL); setBounds(100, 100, 650, 700); - getContentPane().setLayout(new BorderLayout()); - - JPanel contentPanel = new JPanel().addTo(getContentPane(), BorderLayout.CENTER); - contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); - contentPanel.setLayout(new BorderLayout(0, 0)); - { - JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP); - AtomicInteger selectedIndex = new AtomicInteger(); - tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - tabbedPane.addChangeListener(l -> { - if (tabbedPane.getSelectedIndex() != selectedIndex.get()) { - PreferencePanelIntf sourcePanel = (PreferencePanelIntf) tabbedPane.getComponentAt(selectedIndex.get()); - if (!sourcePanel.hasValidSettings()) { - tabbedPane.setSelectedIndex(selectedIndex.get()); - JOptionPane.showMessageDialog(this, Messages.getString("PreferenceDialog.invalidInput"), "Error", JOptionPane.ERROR_MESSAGE); - } else { - selectedIndex.set(tabbedPane.getSelectedIndex()); - } - } - }); - contentPanel.add(tabbedPane); - - this.pnlGeneral = new GeneralPanel(gui, settingsCtrl); - tabbedPane.addTab(Messages.getString("PreferenceDialog.TabGeneral"), null, pnlGeneral, null); - this.pnlEpisodeLibrary = - new EpisodeLibraryPanel(settingsCtrl.getSettings().getEpisodeLibrarySettings(), manager, false, userInteractionHandler); - tabbedPane.addTab(Messages.getString("PreferenceDialog.SerieLibrary"), null, pnlEpisodeLibrary, null); - - this.pnlMovieLibrary = - new MovieLibraryPanel(settingsCtrl.getSettings().getMovieLibrarySettings(), manager, false, userInteractionHandler); - tabbedPane.addTab(Messages.getString("PreferenceDialog.MovieLibrary"), null, pnlMovieLibrary, null); - - this.pnlOptions = new OptionsPanel(settingsCtrl); - tabbedPane.addTab(Messages.getString("PreferenceDialog.Options"), null, pnlOptions, null); - - this.pnlSerieSources = new SerieProvidersPanel(settingsCtrl); - tabbedPane.addTab(Messages.getString("PreferenceDialog.SerieSources"), null, pnlSerieSources, null); - } - - { - new JPanel().layout(new FlowLayout(FlowLayout.RIGHT)).addTo(getContentPane(), BorderLayout.SOUTH) - .addComponent( - new JButton(Messages.getString("App.OK")) - .defaultButtonFor(getRootPane()) - .withActionListener(this::testAndSaveValues) - .actionCommand(Messages.getString("App.OK"))) - .addComponent( - new JButton(Messages.getString("App.Cancel")) - .withActionListener(() -> setVisible(false)) - .actionCommand("Cancel")); - } + AtomicInteger selectedIdx = new AtomicInteger(); + contentPane + .layout(new BorderLayout()) + .addComponent(BorderLayout.CENTER, new JLabel() + .border(new EmptyBorder(5, 5, 5, 5)) + .layout(new BorderLayout(0, 0)) + .addComponent(new JTabbedPane(SwingConstants.TOP) + .tabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT) + .changeListener(tabbedPane -> { + if (tabbedPane.selectedIndex != selectedIdx.get()) { + var sourcePanel = (PreferencePanelIntf) tabbedPane.getComponentAt(selectedIdx.get()); + if (!sourcePanel.hasValidSettings()) { + tabbedPane.selectedIndex = selectedIdx.get(); + JOptionPane.showMessageDialog(this, getText("PreferenceDialog.invalidInput"), + "Error", JOptionPane.ERROR_MESSAGE); + } else { + selectedIdx.set(tabbedPane.selectedIndex); + } + } + }) + .withTab(getText("PreferenceDialog.TabGeneral"), + pnlGeneral = new GeneralPanel(gui, settingsCtrl)) + .withTab(getText("PreferenceDialog.SerieLibrary"), + pnlEpisodeLibrary = new EpisodeLibraryPanel(settingsCtrl.settings.episodeLibrarySettings, + manager, false, userInteractionHandler)) + .withTab(getText("PreferenceDialog.MovieLibrary"), + pnlMovieLibrary = new MovieLibraryPanel(settingsCtrl.settings.movieLibrarySettings, + manager, false, userInteractionHandler)) + .withTab(getText("PreferenceDialog.Options"), + pnlOptions = new OptionsPanel(settingsCtrl)) + .withTab(getText("PreferenceDialog.SerieSources"), + pnlSerieSources = new SerieProvidersPanel(settingsCtrl))) + ) + .addComponent(BorderLayout.SOUTH, new JPanel() + .layout(new FlowLayout(FlowLayout.RIGHT)) + .addComponent(new JButton(getText("App.OK")) + .defaultButtonFor(getRootPane()) + .actionListener(this::testAndSaveValues) + .actionCommand(getText("App.OK"))) + .addComponent(new JButton(getText("App.Cancel")) + .actionListener(() -> setVisible(false)) + .actionCommand("Cancel"))); } private void testAndSaveValues() { - if (pnlGeneral.hasValidSettings() && - pnlEpisodeLibrary.hasValidSettings() && - pnlMovieLibrary.hasValidSettings() && - pnlOptions.hasValidSettings() && - pnlSerieSources.hasValidSettings()) { + if (pnlGeneral.hasValidSettings() && pnlEpisodeLibrary.hasValidSettings() && + pnlMovieLibrary.hasValidSettings() && pnlOptions.hasValidSettings() && + pnlSerieSources.hasValidSettings()) { pnlGeneral.savePreferenceSettings(); pnlEpisodeLibrary.savePreferenceSettings(); pnlMovieLibrary.savePreferenceSettings(); @@ -126,7 +100,8 @@ private void testAndSaveValues() { settingsCtrl.store(); this.eventEmitter.fire(new Event("providers.settings.change")); } else { - JOptionPane.showMessageDialog(this, Messages.getString("PreferenceDialog.invalidInput"), "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, getText("PreferenceDialog.invalidInput"), "Error", + JOptionPane.ERROR_MESSAGE); } } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/ProgressDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/ProgressDialog.java index 129000c0..8ddb05e2 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/ProgressDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/ProgressDialog.java @@ -5,8 +5,8 @@ import java.awt.event.WindowEvent; import java.io.Serial; -import lombok.Getter; import net.miginfocom.swing.MigLayout; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.gui.extra.progress.Messenger; import org.lodder.subtools.multisubdownloader.gui.extra.progress.StatusMessenger; @@ -15,29 +15,21 @@ public class ProgressDialog extends MultiSubDialog implements Messenger { @Serial private static final long serialVersionUID = -2320149791421648965L; - @Getter + private JProgressBar progressBar; private JLabel label; - private final Cancelable worker; - - public ProgressDialog(JFrame frame, Cancelable sft) { - super(frame, Messages.getString("ProgressDialog.Title"), false); - worker = sft; - StatusMessenger.instance.addListener(this); - initialize_ui(); - setDialogLocation(frame); - repaint(); - } - public ProgressDialog(Cancelable sft) { - super(Messages.getString("ProgressDialog.Title"), false); - worker = sft; + public ProgressDialog(@Nullable JFrame frame=null, Cancelable sft) { + super(frame, Messages.getText("ProgressDialog.Title"), false); StatusMessenger.instance.addListener(this); - initialize_ui(); + initializeUi(sft); + if (frame != null) { + setDialogLocation(frame); + } repaint(); } - private void initialize_ui() { + private void initializeUi(Cancelable worker) { addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -45,41 +37,37 @@ public void windowClosing(WindowEvent e) { } }); setBounds(100, 100, 501, 151); - getContentPane().setLayout(new MigLayout("", "[][475px,center][]", "[][40px:n][][]")); - - label = new JLabel(""); - getContentPane().add(label, "cell 1 0 2 1,alignx left"); - - progressBar = new JProgressBar(0, 100); - progressBar.setIndeterminate(true); - getContentPane().add(progressBar, "cell 1 1,grow"); - JButton btnStop = new JButton("Stop!"); - btnStop.addActionListener(arg0 -> worker.cancel(true)); - getContentPane().add(btnStop, "cell 1 2 1 2,alignx left"); + contentPane + .layout(new MigLayout("", "[][475px,center][]", "[][40px:n][][]")) + .addComponent("cell 1 0 2 1,alignx left", label = new JLabel("")) + .addComponent("cell 1 1,grow", progressBar = new JProgressBar(0, 100).indeterminate((true))) + .addComponent("cell 1 2 1 2,alignx left", new JButton("Stop!") + .actionListener(_ -> worker.cancel(true)) + ); } public void setMessage(String message) { - label.setText(message); + label.text = message; repaint(); } public String getMessage() { - return label.getText(); + return label.text; } @Override public void message(String message) { - setMessage(message); + this.message = message; } public void updateProgress(int progress) { if (progress == 0) { - getProgressBar().setIndeterminate(true); + progressBar.setIndeterminate(true); } else { - getProgressBar().setIndeterminate(false); - getProgressBar().setValue(progress); - getProgressBar().setString(Integer.toString(progress)); + progressBar.setIndeterminate(false); + progressBar.setValue(progress); + progressBar.setString(Integer.toString(progress)); } } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/RenameDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/RenameDialog.java index 0fcefca6..a3cb6c2a 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/RenameDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/RenameDialog.java @@ -1,5 +1,9 @@ package org.lodder.subtools.multisubdownloader.gui.dialog; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; +import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; @@ -11,24 +15,16 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.SwingWorker; - -import org.lodder.subtools.multisubdownloader.Messages; +import com.google.common.collect.Streams; +import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.set; +import net.miginfocom.swing.MigLayout; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.actions.RenameAction; +import org.lodder.subtools.multisubdownloader.gui.extra.BoxModelProperties; import org.lodder.subtools.multisubdownloader.gui.extra.MemoryFolderChooser; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; import org.lodder.subtools.multisubdownloader.gui.extra.progress.StatusMessenger; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.JButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.container.ContainerExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldPath; import org.lodder.subtools.multisubdownloader.gui.panels.preference.EpisodeLibraryPanel; import org.lodder.subtools.multisubdownloader.gui.panels.preference.MovieLibraryPanel; @@ -41,64 +37,55 @@ import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.FileUtils; -import org.lodder.subtools.sublibrary.util.StreamExtension; - -import com.google.common.collect.Streams; -import java.awt.BorderLayout; -import java.awt.FlowLayout; - -import lombok.Setter; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JTextFieldExtension.class, ContainerExtension.class, JButtonExtension.class, AbstractButtonExtension.class, - JComponentExtension.class }) public class RenameDialog extends MultiSubDialog implements PropertyChangeListener { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; + private final VideoLibraryPanel pnlLibrary; private final MyTextFieldPath txtFolder; private final JCheckBox chkRecursive; + private ProgressDialog progressDialog; - public RenameDialog(JFrame frame, Settings settings, VideoType videoType, String title, Manager manager, - UserInteractionHandler userInteractionHandler) { + public RenameDialog(@Nullable JFrame frame=null, Settings settings, VideoType videoType, String title, + Manager manager, UserInteractionHandler userInteractionHandler) { super(frame, title, false); setResizable(false); setBounds(100, 100, 650, 680); - getContentPane().setLayout(new MigLayout("fill, nogrid", "[]", "[][]20:push[]")); - TitlePanel.title(Messages.getString("PreferenceDialog.Settings")) - .padding(0).paddingLeft(20).fillContents(true).addTo(getContentPane(), "span, grow, wrap") - .addComponent("shrink", new JLabel(Messages.getString("PreferenceDialog.Location"))) - .addComponent("grow", this.txtFolder = MyTextFieldPath.builder().requireValue().build().withColumns(20)) - .addComponent("shrink, wrap", new JButton(Messages.getString("App.Browse")) - .withActionListener(() -> MemoryFolderChooser.getInstance() - .selectDirectory(getContentPane(), Messages.getString("PreferenceDialog.SelectFolderForRenameReplace")) - .ifPresent(txtFolder::setObject))) - .addComponent("wrap", this.chkRecursive = new JCheckBox(Messages.getString("RenameDialog.RecursiveSearch"))); + contentPane.setLayout(new MigLayout("fill, nogrid", "[]", "[][]20:push[]")); + + new TitlePanel( + title:getText("PreferenceDialog.Settings"), + padding:new BoxModelProperties(0, 20, 0, 0), + fillContents:true) + .addToPanel(contentPane, "span, grow, wrap") + .addComponent("shrink", new JLabel(getText("PreferenceDialog.Location"))) + .addComponent("grow", this.txtFolder = MyTextFieldPath.builder().requireValue().build().columns(20)) + .addComponent("shrink, wrap", new JButton(getText("App.Browse")).actionListener( + () -> MemoryFolderChooser.getInstance() + .selectDirectory(contentPane, + getText("PreferenceDialog.SelectFolderForRenameReplace")) + .ifPresent(txtFolder::setObject))) + .addComponent("wrap", + this.chkRecursive = new JCheckBox(getText("RenameDialog.RecursiveSearch"))); if (videoType == VideoType.EPISODE) { - pnlLibrary = new EpisodeLibraryPanel(settings.getEpisodeLibrarySettings(), manager, true, userInteractionHandler) - .addTo(getContentPane(), "grow"); + pnlLibrary = new EpisodeLibraryPanel(settings.episodeLibrarySettings, manager, true, + userInteractionHandler).addTo(contentPane, "grow"); } else { - pnlLibrary = new MovieLibraryPanel(settings.getMovieLibrarySettings(), manager, true, userInteractionHandler) - .addTo(getContentPane(), "grow"); + pnlLibrary = + new MovieLibraryPanel(settings.movieLibrarySettings, manager, true, userInteractionHandler).addTo( + contentPane, "grow"); } - new JPanel().layout(new FlowLayout(FlowLayout.RIGHT)).addTo(getContentPane(), BorderLayout.SOUTH) - .addComponent( - new JButton(Messages.getString("RenameDialog.Rename")) - .defaultButtonFor(getRootPane()) - .withActionListener(() -> rename(videoType, settings, manager, userInteractionHandler)) - .withActionCommand("Rename")) - .addComponent( - new JButton(Messages.getString("App.Cancel")) - .withActionListener(() -> setVisible(false)) - .actionCommand("Cancel")); - + new JPanel().layout(new FlowLayout(FlowLayout.RIGHT)) + .addTo(contentPane, BorderLayout.SOUTH) + .addComponent(new JButton(getText("RenameDialog.Rename")).defaultButtonFor(getRootPane()) + .actionListener(() -> rename(videoType, settings, manager, userInteractionHandler)) + .actionCommand("Rename")) + .addComponent(new JButton(getText("App.Cancel")).actionListener(() -> setVisible(false)) + .actionCommand("Cancel")); } private boolean hasValidSettings() { @@ -106,18 +93,20 @@ private boolean hasValidSettings() { } private void rename(VideoType videoType, Settings settings, Manager manager, - UserInteractionHandler userInteractionHandler) { + UserInteractionHandler userInteractionHandler) { if (!hasValidSettings()) { - JOptionPane.showMessageDialog(this, Messages.getString("PreferenceDialog.invalidInput"), "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, getText("PreferenceDialog.invalidInput"), "Error", + JOptionPane.ERROR_MESSAGE); return; } setVisible(false); pnlLibrary.savePreferenceSettings(); - TypedRenameWorker renameWorker = new TypedRenameWorker(txtFolder.getObject(), pnlLibrary.getLibrarySettings(), videoType, + TypedRenameWorker renameWorker = + new TypedRenameWorker(txtFolder.getObject(), pnlLibrary.librarySettings, videoType, this.chkRecursive.isSelected(), manager, userInteractionHandler); renameWorker.addPropertyChangeListener(this); - renameWorker.setReleaseFactory(new ReleaseFactory(settings, manager)); + renameWorker.releaseFactory = new ReleaseFactory(settings, manager); progressDialog = new ProgressDialog(renameWorker); progressDialog.setVisible(true); renameWorker.execute(); @@ -131,12 +120,12 @@ public void propertyChange(PropertyChangeEvent event) { } else { final int progress = renameWorker.getProgress(); progressDialog.updateProgress(progress); - StatusMessenger.instance.message(Messages.getString("RenameDialog.StatusRename")); + StatusMessenger.instance.message(getText("RenameDialog.StatusRename")); } } } - @ExtensionMethod({ FileUtils.class, Files.class, StreamExtension.class }) + @ExtensionMethod({ Files.class }) private static class TypedRenameWorker extends SwingWorker implements Cancelable { private final UserInteractionHandler userInteractionHandler; @@ -144,14 +133,14 @@ private static class TypedRenameWorker extends SwingWorker impleme private final VideoType videoType; private final Set extensions; private final boolean isRecursive; - @Setter - private ReleaseFactory releaseFactory; private final RenameAction renameAction; + @set ReleaseFactory releaseFactory; - public TypedRenameWorker(Path dir, LibrarySettings librarySettings, VideoType videoType, - boolean isRecursive, Manager manager, UserInteractionHandler userInteractionHandler) { + public TypedRenameWorker(Path dir, LibrarySettings librarySettings, VideoType videoType, boolean isRecursive, + Manager manager, UserInteractionHandler userInteractionHandler) { this.userInteractionHandler = userInteractionHandler; - this.extensions = Streams.concat(VideoPatterns.EXTENSIONS.stream(), Stream.of("srt")).collect(Collectors.toUnmodifiableSet()); + this.extensions = Streams.concat(VideoPatterns.EXTENSIONS.stream(), Stream.of("srt")) + .collect(Collectors.toUnmodifiableSet()); this.dir = dir; this.videoType = videoType; this.isRecursive = isRecursive; @@ -170,8 +159,8 @@ private void rename(Path dir) throws IOException { if (!file.fileNameContainsIgnoreCase("sample") && extensions.contains(file.getExtension())) { Release release = releaseFactory.createRelease(file, userInteractionHandler); if (release != null) { - publish(release.getFileName()); - if (release.getVideoType() == videoType) { + publish(release.fileName); + if (release.videoType == videoType) { renameAction.rename(file, release); } } @@ -184,7 +173,7 @@ private void rename(Path dir) throws IOException { @Override protected void process(List data) { - data.forEach(s -> StatusMessenger.instance.message(Messages.getString("MainWindow.RenamingFile", s))); + data.forEach(s -> StatusMessenger.instance.message(getText("MainWindow.RenamingFile", s))); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/SelectDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/SelectDialog.java index 3442170c..c562a64a 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/SelectDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/SelectDialog.java @@ -1,124 +1,105 @@ package org.lodder.subtools.multisubdownloader.gui.dialog; +import static org.lodder.subtools.multisubdownloader.Messages.*; + import javax.swing.*; -import javax.swing.table.*; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import javax.swing.table.TableRowSorter; import java.awt.*; import java.io.Serial; import java.util.Comparator; import java.util.List; import java.util.stream.IntStream; -import lombok.experimental.ExtensionMethod; import net.miginfocom.swing.MigLayout; -import org.lodder.subtools.multisubdownloader.Messages; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.gui.extra.table.CustomTable; import org.lodder.subtools.multisubdownloader.gui.extra.table.SubtitleTableColumnName; import org.lodder.subtools.multisubdownloader.gui.extra.table.SubtitleTableModel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.JButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -@ExtensionMethod({ JButtonExtension.class, AbstractButtonExtension.class, JComponentExtension.class }) public class SelectDialog extends MultiSubDialog { - @Serial - private static final long serialVersionUID = -4092909537478305235L; - private List selectedSubtitleIdxs; + @Serial private static final long serialVersionUID = -4092909537478305235L; + private final List subtitles; - private final Release release; - private CustomTable customTable; + private final CustomTable customTable; + + private List selectedSubtitleIdxs; /** * Create the dialog. */ - public SelectDialog(JFrame frame, List subtitles, Release release) { - super(frame, Messages.getString("SelectDialog.SelectCorrectSubtitle"), true); - this.subtitles = subtitles.stream().distinct().sorted(Comparator.comparing(Subtitle::getScore).reversed()).toList(); - this.release = release; - initialize(); - pack(); - setDialogLocation(frame); - setVisible(true); - } - - private void initialize() { - getContentPane().setLayout(new MigLayout("", "[1000px:n,grow,fill]", "[][::100px,fill][grow]")); - JLabel lblNewLabel = - new JLabel(Messages.getString("SelectDialog.SelectCorrectSubtitleThisRelease") - + release.getFileName()); - getContentPane().add(lblNewLabel, "cell 0 0"); - { - JScrollPane scrollPane = new JScrollPane(); - getContentPane().add(scrollPane, "cell 0 1,grow"); - customTable = createCustomTable(); - scrollPane.setViewportView(customTable); - JPanel buttonPane = new JPanel(); - buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); - getContentPane().add(buttonPane, "cell 0 2,grow"); - - new JButton(Messages.getString("App.OK")) + public SelectDialog(@Nullable JFrame frame = null, List subtitles, Release release) { + super(frame, getText("SelectDialog.SelectCorrectSubtitle"), true); + this.subtitles = + subtitles.stream().distinct().sorted(Comparator.comparing(Subtitle::getScore).reversed()).toList(); + contentPane + .layout(new MigLayout("", "[1000px:n,grow,fill]", "[][::100px,fill][grow]")) + .addComponent("cell 0 0", + new JLabel(getText("SelectDialog.SelectCorrectSubtitleThisRelease") + release.fileName)) + .addComponent("cell 0 1,grow", new JScrollPane().viewportView(customTable = createCustomTable())) + .addComponent("cell 0 2,grow", new JPanel() + .layout(new FlowLayout(FlowLayout.RIGHT)) + .addComponent(new JButton(getText("App.OK")) .defaultButtonFor(getRootPane()) - .withActionListener(() -> { + .actionListener(() -> { selectedSubtitleIdxs = getSelectedIdxs(); setVisible(false); }) - .withActionCommand(Messages.getString("App.OK")) - .addTo(buttonPane); - - new JButton(Messages.getString("SelectDialog.Everything")) - .withActionListener(() -> { + .actionCommand(getText("App.OK"))) + .addComponent(new JButton(getText("SelectDialog.Everything")) + .actionListener(() -> { selectedSubtitleIdxs = IntStream.range(0, release.getMatchingSubs().size()).boxed().toList(); setVisible(false); }) - .withActionCommand(Messages.getString("App.All")) - .addTo(buttonPane); - - new JButton(Messages.getString("App.Cancel")) - .withActionListener(() -> { + .actionCommand(getText("App.All"))) + .addComponent(new JButton(getText("App.Cancel")) + .actionListener(() -> { selectedSubtitleIdxs = List.of(); setVisible(false); }) - .withActionCommand(Messages.getString("App.Cancel")) - .addTo(buttonPane); - } + .actionCommand(getText("App.Cancel")))); + pack(); + setDialogLocation(frame); + setVisible(true); } private CustomTable createCustomTable() { - CustomTable customTable = new CustomTable(); - customTable.setModel(SubtitleTableModel.getDefaultSubtitleTableModel()); - final RowSorter sorter = new TableRowSorter<>(customTable.getModel()); - customTable.setRowSorter(sorter); - customTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - - SubtitleTableModel subtitleTableModel = (SubtitleTableModel) customTable.getModel(); - - int columnId = customTable.getColumnIdByName(SubtitleTableColumnName.SELECT); - customTable.getColumnModel().getColumn(columnId).setResizable(false); - customTable.getColumnModel().getColumn(columnId).setPreferredWidth(55); - customTable.getColumnModel().getColumn(columnId).setMaxWidth(55); - - columnId = customTable.getColumnIdByName(SubtitleTableColumnName.SCORE); - customTable.getColumnModel().getColumn(columnId).setResizable(false); - customTable.getColumnModel().getColumn(columnId).setPreferredWidth(60); - customTable.getColumnModel().getColumn(columnId).setMaxWidth(60); - - columnId = customTable.getColumnIdByName(SubtitleTableColumnName.FILENAME); - customTable.getColumnModel().getColumn(columnId).setResizable(true); - customTable.getColumnModel().getColumn(columnId).setMinWidth(500); - - for (Subtitle subtitle : subtitles) { - subtitleTableModel.addRow(subtitle); - } - - return customTable; + SubtitleTableModel subtitleTableModel = SubtitleTableModel.createDefaultSubtitleTableModel(); + CustomTable table = new CustomTable() + .model(subtitleTableModel) + .rowSorter(TableRowSorter::new) + .autoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + + TableColumnModel columnModel = table.getColumnModel(); + + TableColumn column = columnModel.getColumn(table.getColumnIdByName(SubtitleTableColumnName.SELECT)); + column.resizable = false; + column.preferredWidth = 55; + column.maxWidth = 55; + + column = columnModel.getColumn(table.getColumnIdByName(SubtitleTableColumnName.SCORE)); + column.resizable = false; + column.preferredWidth = 60; + column.maxWidth = 60; + + column = columnModel.getColumn(table.getColumnIdByName(SubtitleTableColumnName.FILENAME)); + column.resizable = true; + column.minWidth = 500; + + subtitles.forEach(subtitleTableModel::addRow); + return table; } private List getSelectedIdxs() { return IntStream.range(0, customTable.getModel().getRowCount()) - .filter(i -> (boolean) customTable.getModel().getValueAt(i, customTable.getColumnIdByName(SubtitleTableColumnName.SELECT))) - .boxed().toList(); + .filter(i -> (boolean) customTable.getModel() + .getValueAt(i, customTable.getColumnIdByName(SubtitleTableColumnName.SELECT))) + .boxed() + .toList(); } public List getSelection() { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/StructureBuilderDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/StructureBuilderDialog.java index 7906d5f7..8c802b84 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/StructureBuilderDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/StructureBuilderDialog.java @@ -1,25 +1,20 @@ package org.lodder.subtools.multisubdownloader.gui.dialog; +import static org.lodder.subtools.multisubdownloader.Messages.*; + import javax.swing.*; -import javax.swing.event.*; -import javax.swing.text.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.Serial; import java.nio.file.Path; -import java.util.Arrays; import java.util.function.Function; -import lombok.experimental.ExtensionMethod; import net.miginfocom.swing.MigLayout; -import org.lodder.subtools.multisubdownloader.Messages; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.JButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.component.ComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.container.ContainerExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.lib.ReleaseFactory; import org.lodder.subtools.multisubdownloader.lib.library.LibraryBuilder; import org.lodder.subtools.multisubdownloader.settings.model.Settings; @@ -34,17 +29,15 @@ import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -@ExtensionMethod({ JButtonExtension.class, AbstractButtonExtension.class, JComponentExtension.class, ContainerExtension.class, - ComponentExtension.class, JTextFieldExtension.class }) public class StructureBuilderDialog extends MultiSubDialog implements DocumentListener { - @Serial - private static final long serialVersionUID = -5174968778375028124L; + @Serial private static final long serialVersionUID = -5174968778375028124L; + private final VideoType videoType; private final StructureType structureType; - private final Manager manager; private final UserInteractionHandler userInteractionHandler; private final Function libraryBuilder; + private JTextField txtStructure; private JLabel lblPreview; private TvRelease tvRelease; @@ -56,86 +49,80 @@ public enum StructureType { FILE, FOLDER } - public StructureBuilderDialog(JFrame frame, String title, boolean modal, VideoType videoType, - StructureType structureType, Manager manager, UserInteractionHandler userInteractionHandler, - Function filenameLibraryBuilder) { + public StructureBuilderDialog(@Nullable JFrame frame=null, String title, boolean modal, VideoType videoType, + StructureType structureType, Manager manager, UserInteractionHandler userInteractionHandler, + Function filenameLibraryBuilder) { super(frame, title, modal); this.videoType = videoType; this.structureType = structureType; - this.manager = manager; this.userInteractionHandler = userInteractionHandler; this.libraryBuilder = filenameLibraryBuilder; - initializeUi(); - generateVideoFiles(); + initializeUI(); + generateVideoFiles(manager); } - private void initializeUi() { + private void initializeUI() { setBounds(100, 100, 600, 300); setMinimumSize(new Dimension(600, 300)); - Container panel = getContentPane().layout(new MigLayout("insets 10, nogrid")); - - new JLabel(Messages.getString("StructureBuilderDialog.AvailableTagsClickToAdd")).addTo(panel, "wrap"); - - this.tagPanel = new JPanel(new MigLayout("flowy, wrap 5", "[150px][150px][150px]")).addTo(panel, "grow, wrap"); - { - if (videoType == VideoType.EPISODE) { - // add tv show tags - buildLabelTable(SerieStructureTag.values()); - } else if (videoType == VideoType.MOVIE) { - // add movie tags - buildLabelTable(MovieStructureTag.values()); - } - if (structureType == StructureType.FOLDER) { - buildLabelTable(FolderStructureTag.values()); - } - } - new JLabel(Messages.getString("StructureBuilderDialog.Structure")).addTo(panel); - this.txtStructure = new JTextField().withColumns(100).addTo(panel, "span, wrap"); - this.txtStructure.getDocument().addDocumentListener(this); - - new JLabel(Messages.getString("StructureBuilderDialog.Preview")).addTo(panel); - this.lblPreview = new JLabel("").addTo(panel); - - new JPanel(new FlowLayout(FlowLayout.RIGHT)).addTo(panel, BorderLayout.SOUTH) - .addComponent( - new JButton(Messages.getString("App.OK")) - .defaultButtonFor(getRootPane()) - .withActionListener(e -> { - setVisible(false); - dispose(); // this is needed to dispose the dialog and return the control to the window - }) - .withActionCommand("OK")) - .addComponent(new JButton(Messages.getString("App.Cancel")) - .withActionListener(e -> { - setVisible(false); - txtStructure.setText(oldStructure); - dispose(); // this is needed to dispose the dialog and return the control to the window - }) - .withActionCommand("Cancel")); + contentPane + .layout(new MigLayout("insets 10, nogrid")) + .addComponent("wrap", new JLabel(getText("StructureBuilderDialog.AvailableTagsClickToAdd"))) + .addComponent("grow, wrap", + tagPanel = new JPanel(new MigLayout("flowy, wrap 5", "[150px][150px][150px]"))) + .addComponent(new JLabel(getText("StructureBuilderDialog.Structure"))) + .addComponent("span, wrap", + txtStructure = new JTextField() + .columns(100) + .documentListener(this)) + .addComponent(new JLabel(getText("StructureBuilderDialog.Preview"))) + .addComponent(lblPreview = new JLabel()) + .addComponent(BorderLayout.SOUTH, new JPanel(new FlowLayout(FlowLayout.RIGHT)) + .addComponent(new JButton(getText("App.OK")) + .defaultButtonFor(rootPane) + .actionListener(_ -> { + setVisible(false); + dispose(); // this is needed to dispose the dialog and return the control to the window + }) + .actionCommand("OK")) + .addComponent(new JButton(getText("App.Cancel")) + .actionListener(_ -> { + setVisible(false); + txtStructure.setText(oldStructure); + dispose(); // this is needed to dispose the dialog and return the control to the window + }) + .actionCommand("Cancel"))); + + switch (videoType) { + case EPISODE -> buildLabelTable(SerieStructureTag.values()); + case MOVIE -> buildLabelTable(MovieStructureTag.values()); + } + if (structureType == StructureType.FOLDER) { + buildLabelTable(FolderStructureTag.values()); + } } - private void generateVideoFiles() { + private void generateVideoFiles(Manager manager) { ReleaseFactory releaseFactory = new ReleaseFactory(new Settings(), manager); - if (videoType == VideoType.EPISODE) { - tvRelease = (TvRelease) releaseFactory.createRelease( - Path.of("Terra.Nova.S01E01E02.Genesis.720p.HDTV.x264-ORENJI.mkv"), - userInteractionHandler); - } else if (videoType == VideoType.MOVIE) { - movieRelease = (MovieRelease) releaseFactory.createRelease(Path.of("Final.Destination.5.720p.Bluray.x264-TWiZTED"), - userInteractionHandler); + switch (videoType) { + case EPISODE -> tvRelease = (TvRelease) releaseFactory.createRelease( + Path.of("Terra.Nova.S01E01E02.Genesis.720p.HDTV.x264-ORENJI.mkv"), + userInteractionHandler, false); + case MOVIE -> movieRelease = (MovieRelease) releaseFactory.createRelease( + Path.of("Final.Destination.5.2011.720p.Bluray.x264-TWiZTED.mkv"), + userInteractionHandler, false); } } private void buildLabelTable(StructureTag[] structureTags) { - Arrays.stream(structureTags).forEach(this::addTag); + structureTags.forEach(this::addTag); } private void addTag(StructureTag structureTag) { - new JLabel(structureTag.getLabel()) - .withToolTipText(structureTag.getDescription()) - .addTo(tagPanel) - .withMouseListener(new InsertTag()); + new JLabel(structureTag.label) + .withToolTipText(structureTag.description) + .addTo(tagPanel) + .mouseListener(new InsertTag()); } public String showDialog(String structure) { @@ -192,7 +179,7 @@ public void mouseClicked(MouseEvent e) { beforeCaret = txtStructure.getText(); afterCaret = ""; } - txtStructure.setText(String.format("%s%s%s", beforeCaret, clickedTag, afterCaret)); + txtStructure.setText("%s%s%s".formatted(beforeCaret, clickedTag, afterCaret)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressDialog.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressDialog.java index 6df020aa..a52e3f5b 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressDialog.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressDialog.java @@ -1,12 +1,15 @@ package org.lodder.subtools.multisubdownloader.gui.dialog.progress.search; -import javax.swing.JButton; -import javax.swing.JProgressBar; -import javax.swing.JScrollPane; -import javax.swing.JTable; +import static org.lodder.subtools.multisubdownloader.Messages.*; +import javax.swing.*; +import javax.swing.table.TableColumn; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.Serial; + +import net.miginfocom.swing.MigLayout; import org.lodder.subtools.multisubdownloader.GUI; -import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.actions.ActionException; import org.lodder.subtools.multisubdownloader.gui.dialog.Cancelable; import org.lodder.subtools.multisubdownloader.gui.dialog.MultiSubDialog; @@ -14,29 +17,43 @@ import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.sublibrary.model.Release; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.Serial; - -import net.miginfocom.swing.MigLayout; - public class SearchProgressDialog extends MultiSubDialog implements SearchProgressListener { @Serial private static final long serialVersionUID = -1331536352530988442L; - private final Cancelable searchAction; private final GUI window; - private SearchProgressTableModel tableModel; - private JProgressBar progressBar; + private final SearchProgressTableModel tableModel; + private final JProgressBar progressBar; private boolean completed; public SearchProgressDialog(GUI window, Cancelable searchAction) { - super(window, Messages.getString("SearchProgressDialog.Title"), false); - this.searchAction = searchAction; + super(window, getText("SearchProgressDialog.Title"), false); this.window = window; this.completed = false; - initialize_ui(); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + searchAction.cancel(true); + } + }); + setBounds(100, 100, 601, 300); + + JTable table = new JTable(this.tableModel = new SearchProgressTableModel()); + TableColumn column1 = table.getColumnModel().getColumn(0); + column1.minWidth = 120; + column1.maxWidth = 150; + TableColumn column2 = table.getColumnModel().getColumn(1); + column2.minWidth = 50; + column2.maxWidth = 50; + + contentPane + .layout(new MigLayout("", "[grow,fill][]", "[][][]")) + .addComponent("cell 0 0 2 1", new JScrollPane(table).viewportView(table)) + .addComponent("cell 0 1 2 1,grow", progressBar = new JProgressBar(0, 100).indeterminate(true)) + .addComponent("cell 1 2,alignx left", + new JButton(getText("SearchProgressDialog.Stop")) + .actionListener(_ -> searchAction.cancel(true))); setDialogLocation(window); repaint(); } @@ -44,8 +61,7 @@ public SearchProgressDialog(GUI window, Cancelable searchAction) { @Override public void progress(SubtitleProvider provider, int jobsLeft, Release release) { this.setVisible(); - this.tableModel.update(provider.getName(), jobsLeft, - release == null ? "Done" : release.getFileName()); + this.tableModel.update(provider.name, jobsLeft, release == null ? "Done" : release.fileName); } @Override @@ -69,6 +85,7 @@ public void completed() { @Override public void reset() { this.completed = false; + tableModel.clear(); } @Override @@ -82,36 +99,6 @@ public void onStatus(String message) { this.window.setStatusMessage(message); } - private void initialize_ui() { - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - searchAction.cancel(true); - } - }); - setBounds(100, 100, 601, 300); - getContentPane().setLayout(new MigLayout("", "[grow,fill][]", "[][][]")); - - this.tableModel = new SearchProgressTableModel(); - JTable table = new JTable(tableModel); - - table.getColumnModel().getColumn(0).setMinWidth(120); - table.getColumnModel().getColumn(0).setMaxWidth(150); - table.getColumnModel().getColumn(0).setMinWidth(50); - table.getColumnModel().getColumn(1).setMaxWidth(50); - - JScrollPane tablePane = new JScrollPane(table); - tablePane.setViewportView(table); - getContentPane().add(tablePane, "cell 0 0 2 1"); - - progressBar = new JProgressBar(0, 100); - progressBar.setIndeterminate(true); - getContentPane().add(progressBar, "cell 0 1 2 1,grow"); - - JButton btnStop = new JButton(Messages.getString("SearchProgressDialog.Stop")); - btnStop.addActionListener(arg0 -> searchAction.cancel(true)); - getContentPane().add(btnStop, "cell 1 2,alignx left"); - } private void setVisible() { if (this.completed || this.isVisible()) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressTableModel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressTableModel.java index 63a5ab1a..c604098c 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressTableModel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/dialog/progress/search/SearchProgressTableModel.java @@ -1,12 +1,12 @@ package org.lodder.subtools.multisubdownloader.gui.dialog.progress.search; -import javax.swing.table.*; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.table.DefaultTableModel; import java.io.Serial; import java.util.HashMap; import java.util.Map; -import org.lodder.subtools.multisubdownloader.Messages; - public class SearchProgressTableModel extends DefaultTableModel { @Serial @@ -17,9 +17,9 @@ public SearchProgressTableModel() { super(); this.setColumnCount(3); this.setColumnIdentifiers(new String[]{ - Messages.getString("SearchProgressTableModel.Source"), - Messages.getString("SearchProgressTableModel.Queue"), - Messages.getString("SearchProgressTableModel.Release")}); + getText("SearchProgressTableModel.Source"), + getText("SearchProgressTableModel.Queue"), + getText("SearchProgressTableModel.Release") }); } public void update(String source, int queue, String release) { @@ -42,7 +42,12 @@ private void updateRow(int rowNr, int queue, String release) { private void createRow(String source, int queue, String release) { this.rowMap.put(source, this.getRowCount()); - Object[] row = {source, queue, release}; + Object[] row = { source, queue, release }; this.addRow(row); } + + public void clear() { + rowMap.clear(); + dataVector.removeAllElements(); + } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ArrowButton.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ArrowButton.java index fad3084e..538b5cea 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ArrowButton.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ArrowButton.java @@ -4,25 +4,19 @@ import java.awt.*; import java.io.Serial; -import lombok.Getter; -import lombok.Setter; +import manifold.ext.props.rt.api.var; -@Getter -@Setter public class ArrowButton extends JButton { - @Serial - private static final long serialVersionUID = -4630720317499130016L; + @Serial private static final long serialVersionUID = -4630720317499130016L; /** - * The cardinal direction of the arrow(s), - * any of {@link SwingConstants#NORTH}, {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or {@link SwingConstants#EAST} + * The cardinal direction of the arrow(s), any of {@link SwingConstants#NORTH}, {@link SwingConstants#SOUTH}, + * {@link SwingConstants#WEST} or {@link SwingConstants#EAST} */ - private int direction; - - private int arrowCount; - - private int arrowSize; + @var int direction; + @var int arrowCount; + @var int arrowSize; public ArrowButton(int direction, int arrowCount, int arrowSize) { setMargin(new Insets(0, 2, 0, 2)); @@ -39,13 +33,10 @@ public Dimension getPreferredSize() { @Override public Dimension getMinimumSize() { - return new Dimension(arrowSize - * (direction == SwingConstants.EAST || direction == SwingConstants.WEST ? arrowCount : 3) - + getBorder().getBorderInsets(this).left + getBorder().getBorderInsets(this).right, - arrowSize - * (direction == SwingConstants.NORTH || direction == SwingConstants.SOUTH ? arrowCount : 3) - + getBorder().getBorderInsets(this).top - + getBorder().getBorderInsets(this).bottom); + return new Dimension(arrowSize * (direction == EAST || direction == WEST ? arrowCount : 3) + + getBorder().getBorderInsets(this).left + getBorder().getBorderInsets(this).right, + arrowSize * (direction == NORTH || direction == SOUTH ? arrowCount : 3) + + getBorder().getBorderInsets(this).top + getBorder().getBorderInsets(this).bottom); } @Override @@ -65,18 +56,10 @@ protected void paintComponent(Graphics g) { int w = getSize().width; int h = getSize().height; for (int i = 0; i < arrowCount; i++) { - paintArrow(g, - (w - arrowSize - * (direction == SwingConstants.EAST || direction == SwingConstants.WEST ? arrowCount : 1)) - / 2 - + arrowSize - * (direction == SwingConstants.EAST || direction == SwingConstants.WEST ? i : 0), - (h - arrowSize - * (direction == SwingConstants.EAST || direction == SwingConstants.WEST ? 1 : arrowCount)) - / 2 - + arrowSize - * (direction == SwingConstants.EAST || direction == SwingConstants.WEST ? 0 : i), - g.getColor()); + paintArrow(g, (w - arrowSize * (direction == EAST || direction == WEST ? arrowCount : 1)) / 2 + + arrowSize * (direction == EAST || direction == WEST ? i : 0), + (h - arrowSize * (direction == EAST || direction == WEST ? 1 : arrowCount)) / 2 + + arrowSize * (direction == EAST || direction == WEST ? 0 : i), g.getColor()); } g.setColor(oldColor); @@ -86,7 +69,7 @@ private void paintArrow(Graphics g, int x, int y, Color highlight) { int mid, i, j; Color oldColor = g.getColor(); - boolean isEnabled = isEnabled(); + boolean enabled = isEnabled(); j = 0; arrowSize = Math.max(arrowSize, 2); @@ -99,13 +82,13 @@ private void paintArrow(Graphics g, int x, int y, Color highlight) { for (i = 0; i < arrowSize; i++) { g.drawLine(mid - i, i, mid + i, i); } - if (!isEnabled) { + if (!enabled) { g.setColor(highlight); g.drawLine(mid - i + 2, i, mid + i, i); } } case SOUTH -> { - if (!isEnabled) { + if (!enabled) { g.translate(1, 1); g.setColor(highlight); for (i = arrowSize - 1; i >= 0; i--) { @@ -125,13 +108,13 @@ private void paintArrow(Graphics g, int x, int y, Color highlight) { for (i = 0; i < arrowSize; i++) { g.drawLine(i, mid - i, i, mid + i); } - if (!isEnabled) { + if (!enabled) { g.setColor(highlight); g.drawLine(i, mid - i + 2, i, mid + i); } } case EAST -> { - if (!isEnabled) { + if (!enabled) { g.translate(1, 1); g.setColor(highlight); for (i = arrowSize - 1; i >= 0; i--) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/BoxModelProperties.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/BoxModelProperties.java new file mode 100644 index 00000000..e269b6b4 --- /dev/null +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/BoxModelProperties.java @@ -0,0 +1,29 @@ +package org.lodder.subtools.multisubdownloader.gui.extra; + +import manifold.ext.props.rt.api.val; + +public class BoxModelProperties { + @val Integer top; + @val Integer left; + @val Integer bottom; + @val Integer right; + + public BoxModelProperties(Integer top=null, Integer left=null, Integer bottom=null, Integer right=null) { + this.top = top; + this.left = left; + this.bottom = bottom; + this.right = right; + } + + public BoxModelProperties(int padding) { + this(padding, padding, padding, padding); + } + + public String getInsets() { + return "insets %s %s %s %s".formatted(getPadding(top), getPadding(left), getPadding(bottom), getPadding(right)); + } + + private String getPadding(Integer padding) { + return padding == null ? "n" : String.valueOf(padding); + } +} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ImageListCellRenderer.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ImageListCellRenderer.java index 30e7c4e7..81d8b515 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ImageListCellRenderer.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/ImageListCellRenderer.java @@ -13,26 +13,22 @@ public class ImageListCellRenderer extends JLabel implements ListCellRendererSource: - *

- * Return a component that has been configured to display the specified value. - * That component's paint method is then called to "render" the cell. - * If it is necessary to compute the dimensions of a list because the list cells do not have a fixed size, - * this method is called to generate a component on which getPreferredSize can be invoked. - *

- * jlist - the jlist we're painting - * value - the value returned by list.getModel().getElementAt(index). - * cellIndex - the cell index - * isSelected - true if the specified cell is currently selected - * cellHasFocus - true if the cell has focus - */ - @Override - public Component getListCellRendererComponent(JList jlist, Object value, int cellIndex, - boolean isSelected, boolean cellHasFocus) { - if (value instanceof JPanel) { - Component component = (Component) value; - if (isSelected) { + /** + * Source: + *

+ * Return a component that has been configured to display the specified value. That component's paint method is then + * called to "render" the cell. If it is necessary to compute the dimensions of a list because the list cells do not + * have a fixed size, this method is called to generate a component on which getPreferredSize can be invoked. + *

+ * jlist - the jlist we're painting value - the value returned by list.getModel().getElementAt(index). cellIndex - + * the cell index selected - true if the specified cell is currently selected cellHasFocus - true if the cell has + * focus + */ + @Override + public Component getListCellRendererComponent(JList jlist, Object value, int cellIndex, + boolean selected, boolean cellHasFocus) { + if (value instanceof JPanel component) { + if (selected) { component.setBackground(jlist.getSelectionBackground()); component.setForeground(jlist.getSelectionForeground()); } else { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/JListWithImages.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/JListWithImages.java index db84b9f7..6625fd40 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/JListWithImages.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/JListWithImages.java @@ -10,13 +10,8 @@ import java.util.stream.Stream; import com.google.common.base.Objects; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; +import manifold.ext.props.rt.api.val; -@ExtensionMethod({ JComponentExtension.class }) public class JListWithImages extends JList> { @Serial @@ -25,43 +20,21 @@ public class JListWithImages extends JList> { private final Function toStringMapper; private final boolean distinctValues; - private JListWithImages(Function toStringMapper, boolean distinctValues) { - this.toStringMapper = toStringMapper == null ? Object::toString : toStringMapper; + public JListWithImages(Function toStringMapper=Object::toString, boolean distinctValues=true) { + this.toStringMapper = toStringMapper; this.distinctValues = distinctValues; setCellRenderer(new ImageListCellRenderer()); setModel(new DefaultListModel<>()); } - public static JListWithImages forType(Class type) { - return createForType(type).build(); - } - - public static JListWithImagesBuilder createForType(Class type) { - return new JListWithImagesBuilder<>(); - } - - @Setter - @Accessors(chain = true, fluent = true) - public static class JListWithImagesBuilder { - private Function toStringMapper; - private boolean distinctValues; - - public JListWithImagesBuilder distinctValues() { - return distinctValues(true); - } - - public JListWithImages build() { - return new JListWithImages<>(toStringMapper, distinctValues); - } - } - public void addItems(Image image, Collection values) { values.forEach(value -> addItem(image, value)); } public void addItem(Image image, T value) { if (!distinctValues || !contains(value)) { - ((DefaultListModel>) getModel()).addElement(new LabelPanel<>(image, value, toStringMapper, SwingConstants.LEFT)); + ((DefaultListModel>) getModel()).addElement( + new LabelPanel<>(image, value, toStringMapper, SwingConstants.LEFT)); } } @@ -93,11 +66,10 @@ private Optional> getLabelPanel(int index) { return Optional.ofNullable(getModel().getElementAt(index)); } - @Getter public static class LabelPanel extends JPanel { - private static final long serialVersionUID = 1L; - private final Label label; + @Serial private static final long serialVersionUID = 1L; + @val Label label; LabelPanel(Image image, T object, Function toStringMapper, int horizontalAlignment) { this.label = new Label<>(image, object, toStringMapper, horizontalAlignment); @@ -106,19 +78,18 @@ public static class LabelPanel extends JPanel { } public T getObject() { - return label.getObject(); + return label.object; } public Image getImage() { - return label.getImage(); + return label.image; } } - @Getter private static class Label extends JLabel { - private static final long serialVersionUID = 1L; - private final T object; - private final Image image; + @Serial private static final long serialVersionUID = 1L; + @val T object; + @val Image image; Label(Image image, T object, Function toStringMapper, int horizontalAlignment) { super(toStringMapper.apply(object), getImageIcon(image), horizontalAlignment); @@ -131,7 +102,7 @@ private static ImageIcon getImageIcon(Image image) { } private static ImageIcon resizeIcon(ImageIcon icon, int width, int height) { - return new ImageIcon( icon.getImage().getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH)); + return new ImageIcon(icon.getImage().getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH)); } } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/LogTextAppender.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/LogTextAppender.java index fcb428d8..71e6bf63 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/LogTextAppender.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/LogTextAppender.java @@ -1,20 +1,17 @@ package org.lodder.subtools.multisubdownloader.gui.extra; +import javax.swing.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -import javax.swing.JTextArea; -import javax.swing.SwingUtilities; - -import org.slf4j.LoggerFactory; - import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.encoder.EchoEncoder; import ch.qos.logback.core.encoder.Encoder; +import org.slf4j.LoggerFactory; public class LogTextAppender extends AppenderBase { private final Encoder encoder = new EchoEncoder<>(); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/MemoryFolderChooser.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/MemoryFolderChooser.java index bda17fb2..77fba6e4 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/MemoryFolderChooser.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/MemoryFolderChooser.java @@ -7,13 +7,14 @@ import java.util.Objects; import java.util.Optional; +import manifold.ext.props.rt.api.var; import org.apache.commons.lang3.StringUtils; public class MemoryFolderChooser { - private final JFileChooser chooser; private static MemoryFolderChooser instance; - private File memory; + private final JFileChooser chooser; + @var Path memory; private MemoryFolderChooser() { chooser = new JFileChooser(); @@ -35,14 +36,14 @@ public Optional selectDirectory(Component c, String title, Path path) { public Optional selectDirectory(Component c, String title, File file) { chooser.setDialogTitle(title); if (file == null || !StringUtils.isBlank(file.getAbsolutePath())) { - chooser.setCurrentDirectory(Objects.requireNonNullElseGet(memory, () -> new File("."))); + chooser.setCurrentDirectory(Objects.requireNonNullElseGet(memory.toFile(), () -> new File("."))); } else { chooser.setCurrentDirectory(file); } int result = chooser.showOpenDialog(c); if (result == JFileChooser.APPROVE_OPTION) { - memory = chooser.getSelectedFile(); + memory = chooser.getSelectedFile().toPath(); return Optional.of(chooser.getSelectedFile().toPath()); } return Optional.empty(); @@ -51,12 +52,4 @@ public Optional selectDirectory(Component c, String title, File file) { public Optional selectDirectory(Component c, String title) { return selectDirectory(c, title, memory); } - - public void setMemory(Path memory) { - this.memory = memory.toFile(); - } - - public Path getMemory() { - return memory.toPath(); - } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PanelCheckBox.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PanelCheckBox.java index dc7db710..52ae5c4d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PanelCheckBox.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PanelCheckBox.java @@ -1,43 +1,32 @@ package org.lodder.subtools.multisubdownloader.gui.extra; -import java.io.Serial; - -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JSeparator; -import javax.swing.SwingConstants; - -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; - -import java.awt.Component; -import java.awt.Container; -import java.awt.LayoutManager; +import javax.swing.*; +import java.awt.*; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; +import java.io.Serial; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.val; import net.miginfocom.swing.MigLayout; +import org.jspecify.annotations.Nullable; -@ExtensionMethod({ JComponentExtension.class, JCheckBoxExtension.class }) public class PanelCheckBox extends JPanel { @Serial private static final long serialVersionUID = 1L; private final JCheckBox checkbox; - @Getter - private final JPanel panel; + @val JPanel panel; + + public PanelCheckBox(JCheckBox checkbox, + boolean panelOnNewLine, + LayoutManager panelLayout=new MigLayout("insets 0, novisualpadding, fillx"), + boolean addVerticalSeparator=false, + int leftGap=20) { - private PanelCheckBox(JCheckBox checkbox, boolean panelOnNewLine, LayoutManager panelLayout, boolean addVerticalSeparator, int leftGap) { super(new MigLayout("insets 0, novisualpadding, fillx")); this.checkbox = checkbox; this.panel = new JPanel(panelLayout) { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; @Override protected void addImpl(Component comp, Object constraints, int index) { @@ -52,7 +41,7 @@ protected void addImpl(Component comp, Object constraints, int index) { super.addImpl(checkbox, panelOnNewLine ? "span" : "", -1); super.addImpl(panel, "span, growx, " + (addVerticalSeparator ? "" : "gapx " + leftGap), -1); checkbox.addCheckedChangeListener(selected -> setEnabledChildren(panel, selected)); - JComponentExtension.setRecursive(this, this::addContainerListener); + this.setRecursive(this::addContainerListener); setEnabledChildren(panel, isSelected()); } @@ -81,78 +70,6 @@ public void componentAdded(ContainerEvent e) { } } - public static BuilderPanelNewLineIntf checkbox(JCheckBox checkbox) { - return new Builder(checkbox); - } - - public interface BuilderPanelNewLineIntf { - BuilderSeparatorIntf panelOnNewLine(); - - BuilderOtherIntf panelOnSameLine(); - } - - public interface BuilderSeparatorIntf extends BuilderOtherIntf { - BuilderOtherIntf addVerticalSeparator(); - } - - public interface BuilderOtherIntf { - BuilderOtherIntf leftGap(int leftGap); - - BuilderOtherIntf panelLayout(LayoutManager panelLayout); - - JPanel addTo(JComponent component); - - JPanel addTo(JComponent component, Object constraints); - - PanelCheckBox build(); - } - - @RequiredArgsConstructor - @Setter - @Accessors(chain = true, fluent = true) - public static class Builder implements - BuilderPanelNewLineIntf, - BuilderSeparatorIntf, - BuilderOtherIntf { - private final JCheckBox checkbox; - private boolean panelOnNewLine; - private LayoutManager panelLayout = new MigLayout("insets 0, novisualpadding, fillx"); - private boolean addVerticalSeparator; - private int leftGap = 20; - - @Override - public Builder panelOnNewLine() { - return panelOnNewLine(true); - } - - @Override - public Builder panelOnSameLine() { - return panelOnNewLine(false); - } - - @Override - public Builder addVerticalSeparator() { - return addVerticalSeparator(true); - } - - @Override - public JPanel addTo(JComponent component) { - return addTo(component, ""); - } - - @Override - public JPanel addTo(JComponent component, Object constraints) { - PanelCheckBox panelCheckBox = build(); - component.add(panelCheckBox, constraints); - return panelCheckBox.getPanel(); - } - - @Override - public PanelCheckBox build() { - return new PanelCheckBox(checkbox, panelOnNewLine, panelLayout, addVerticalSeparator, leftGap); - } - } - @Override protected void addImpl(Component comp, Object constraints, int index) { panel.add(comp, constraints, index); @@ -178,4 +95,9 @@ public void setEnabled(boolean enabled) { public boolean isSelected() { return checkbox.isSelected(); } + + public JPanel addToPanel(Container parent, @Nullable Object constraints=null) { + parent.addComponent(this, constraints); + return panel; + } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PartialDisableComboBox.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PartialDisableComboBox.java index 10bcfa4d..e27317f4 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PartialDisableComboBox.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PartialDisableComboBox.java @@ -1,7 +1,7 @@ package org.lodder.subtools.multisubdownloader.gui.extra; import javax.swing.*; -import javax.swing.plaf.basic.*; +import javax.swing.plaf.basic.BasicComboBoxRenderer; import java.awt.*; import java.io.Serial; import java.util.ArrayList; @@ -10,7 +10,6 @@ import java.util.stream.IntStream; /** - * * @author author */ public class PartialDisableComboBox extends JComboBox { @@ -27,7 +26,8 @@ public PartialDisableComboBox(T[] items) { private static final long serialVersionUID = -2774241371293899669L; @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); boolean disabled = index >= 0 && index < itemsState.size() && !itemsState.get(index); c.setEnabled(!disabled); @@ -37,6 +37,7 @@ public Component getListCellRendererComponent(JList list, Object value, int inde }); } + @SafeVarargs public static PartialDisableComboBox of(T... items) { return new PartialDisableComboBox<>(items); } @@ -102,4 +103,9 @@ private int requireValidIndex(int index) { } return index; } + + @Override + public T getSelectedItem() { + return (T) super.getSelectedItem(); + } } \ No newline at end of file diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PopupListener.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PopupListener.java index 2e3174e6..4f96a4e1 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PopupListener.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/PopupListener.java @@ -24,8 +24,8 @@ public void mouseReleased(MouseEvent e) { private synchronized void showPopup(MouseEvent e) { if (e.isPopupTrigger() - && e.getComponent() instanceof CustomTable customTable - && customTable.getModel().getRowCount() > 0) { + && e.getComponent() instanceof CustomTable customTable + && customTable.getModel().getRowCount() > 0) { popupMenu.show(e.getComponent(), e.getX(), e.getY()); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/TitlePanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/TitlePanel.java index 6c981d25..083413ff 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/TitlePanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/TitlePanel.java @@ -1,161 +1,38 @@ package org.lodder.subtools.multisubdownloader.gui.extra; +import javax.swing.*; +import java.awt.*; import java.io.Serial; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSeparator; - -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; - -import java.awt.Container; -import java.awt.LayoutManager; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.val; import net.miginfocom.swing.MigLayout; +import org.jspecify.annotations.Nullable; -@ExtensionMethod({ JComponentExtension.class, JCheckBoxExtension.class }) public class TitlePanel extends JPanel { @Serial private static final long serialVersionUID = 1L; - @Getter - private final JPanel panel; + @val JPanel panel; - private TitlePanel(String title, LayoutManager panelLayout, int marginTop, int marginLeft, int marginBottom, int marginRight) { - super(new MigLayout("fillx, nogrid, insets %s %s %s %s".formatted(getPadding(marginTop), - getPadding(marginLeft), getPadding(marginBottom), getPadding(marginRight)))); + public TitlePanel(String title, + boolean useGrid=false, + boolean fillContents=true, + BoxModelProperties margin=new BoxModelProperties(), + BoxModelProperties padding=new BoxModelProperties(), + LayoutManager panelLayout=null, + String panelColumnConstraints="") { + + super(new MigLayout("fillx, nogrid, " + margin.getInsets())); super.add(new JLabel(title)); super.add(new JSeparator(), "growx, gapy 6, wrap"); - super.add(this.panel = new JPanel(panelLayout), "growx, span"); - } - - private static String getPadding(int padding) { - return padding == -1 ? "n" : String.valueOf(padding); - } - - public static BuilderOtherIntf title(String title) { - return new Builder(title); - } - - public interface BuilderOtherIntf { - BuilderOtherIntf useGrid(); - - BuilderOtherIntf fillContents(boolean fillContents); - - BuilderOtherIntf margin(int margin); - - BuilderOtherIntf margin(int top, int left, int bottom, int right); - - BuilderOtherIntf marginTop(int top); - - BuilderOtherIntf marginLeft(int left); - - BuilderOtherIntf marginBottom(int bottom); - - BuilderOtherIntf marginRight(int right); - - BuilderOtherIntf marginSides(int marginSide); - - BuilderOtherIntf panelLayout(LayoutManager panelLayout); - BuilderOtherIntf panelColumnConstraints(String panelColumnConstraints); - - BuilderOtherIntf padding(int padding); - - BuilderOtherIntf padding(int top, int left, int bottom, int right); - - BuilderOtherIntf paddingTop(int top); - - BuilderOtherIntf paddingLeft(int left); - - BuilderOtherIntf paddingBottom(int bottom); - - BuilderOtherIntf paddingRight(int right); - - BuilderOtherIntf paddingSides(int paddingSide); - - JPanel addTo(Container component); - - JPanel addTo(Container component, Object constraints); + LayoutManager panelLayoutNew = panelLayout != null ? panelLayout : new MigLayout( + (fillContents ? "fill," : "") + (useGrid ? "" : "nogrid,") + padding.getInsets(), + panelColumnConstraints); + super.add(this.panel = new JPanel(panelLayoutNew), "growx, span"); } - @RequiredArgsConstructor - @Setter - @Accessors(chain = true, fluent = true) - public static class Builder implements - BuilderOtherIntf { - private final String title; - private boolean useGrid; - private boolean fillContents = true; - private int marginTop = -1; - private int marginLeft = -1; - private int marginBottom = -1; - private int marginRight = -1; - private int paddingTop = -1; - private int paddingLeft = -1; - private int paddingBottom = -1; - private int paddingRight = -1; - private LayoutManager panelLayout; - private String panelColumnConstraints = ""; - - @Override - public Builder useGrid() { - this.useGrid = true; - return this; - } - - @Override - public Builder margin(int margin) { - return margin(margin, margin, margin, margin); - } - - @Override - public Builder margin(int top, int left, int bottom, int right) { - return marginTop(top).marginLeft(left).marginBottom(bottom).marginRight(right); - } - - @Override - public Builder marginSides(int marginSide) { - return marginLeft(marginSide).marginRight(marginSide); - } - - @Override - public Builder padding(int padding) { - return padding(padding, padding, padding, padding); - } - - @Override - public Builder padding(int top, int left, int bottom, int right) { - return paddingTop(top).paddingLeft(left).paddingBottom(bottom).paddingRight(right); - } - - @Override - public Builder paddingSides(int paddingSide) { - return paddingLeft(paddingSide).paddingRight(paddingSide); - } - - @Override - public JPanel addTo(Container component) { - return addTo(component, ""); - } - - @Override - public JPanel addTo(Container component, Object constraints) { - if (panelLayout == null) { - panelLayout = new MigLayout( - (fillContents ? "fill," : "") + (useGrid ? "" : "nogrid,") + "insets %s %s %s %s".formatted(getPadding(paddingTop), - getPadding(paddingLeft), getPadding(paddingBottom), getPadding(paddingRight)), - panelColumnConstraints); - } - TitlePanel titlePanel = new TitlePanel(title, panelLayout, marginTop, marginLeft, marginBottom, marginRight); - component.add(titlePanel, constraints); - return titlePanel.getPanel(); - - } + public JPanel addToPanel(Container parent, @Nullable Object constraints=null) { + parent.addComponent(this, constraints); + return panel; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusLabel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusLabel.java index 03ab0cca..cee0d2b1 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusLabel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusLabel.java @@ -1,6 +1,6 @@ package org.lodder.subtools.multisubdownloader.gui.extra.progress; -import javax.swing.JLabel; +import javax.swing.*; import java.io.Serial; public class StatusLabel extends JLabel implements Messenger { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusMessenger.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusMessenger.java index 769aa2f6..41f334d4 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusMessenger.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/progress/StatusMessenger.java @@ -3,11 +3,17 @@ import java.util.LinkedList; import java.util.List; -public enum StatusMessenger implements Messenger { - instance; +import manifold.ext.props.rt.api.val; + +public class StatusMessenger implements Messenger { + @val static StatusMessenger instance = new StatusMessenger(); private final List statusMessengers = new LinkedList<>(); + private StatusMessenger() { + // private constructor to prevent instantiation + } + public void addListener(Messenger sm) { synchronized (statusMessengers) { statusMessengers.add(sm); @@ -23,7 +29,7 @@ public void removeListener(Messenger sm) { @Override public void message(String message) { synchronized (statusMessengers) { - statusMessengers.forEach(sm -> sm.message(message)); + statusMessengers.forEach(sm -> sm.message(message)); } } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomColumnName.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomColumnName.java index 160cce7b..08e657df 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomColumnName.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomColumnName.java @@ -1,10 +1,10 @@ package org.lodder.subtools.multisubdownloader.gui.extra.table; -public interface CustomColumnName { - - String getColumnName(); +import manifold.ext.props.rt.api.val; - boolean isEditable(); +public interface CustomColumnName { - Class getC(); + @val String columnName; + @val boolean editable; + @val Class clazz; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomTable.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomTable.java index 63ab5120..e22f5519 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomTable.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/CustomTable.java @@ -1,30 +1,31 @@ package org.lodder.subtools.multisubdownloader.gui.extra.table; +import javax.swing.table.*; +import java.awt.event.MouseEvent; import java.io.Serial; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; import java.util.stream.IntStream; -import java.awt.event.MouseEvent; - public class CustomTable extends ZebraJTable { @Serial private static final long serialVersionUID = -3889524906608098585L; - private final Map columnSettings = new HashMap<>(); private static final int MAX_WIDTH = 2147483647; private static final int MIN_WIDTH = 15; private static final int PREFERRED_WIDTH = 75; + private final Map columnSettings = new EnumMap<>(SearchColumnName.class); + public int getColumnIdByName(CustomColumnName customColumnName) { return IntStream.range(0, this.getColumnCount()) - .filter(i -> this.getColumnName(i).equals(customColumnName.getColumnName())).findFirst() - .orElse(-1); + .filter(i -> this.getColumnName(i).equals(customColumnName.columnName)).findFirst() + .orElse(-1); } public void setColumnVisibility(SearchColumnName searchColumnName, boolean visible) { if (visible) { - unhideColumn(searchColumnName); + showColumn(searchColumnName); } else { hideColumn(searchColumnName); } @@ -33,28 +34,27 @@ public void setColumnVisibility(SearchColumnName searchColumnName, boolean visib public void hideColumn(SearchColumnName searchColumnName) { int columnId = getColumnIdByName(searchColumnName); if (columnId > -1) { - columnSettings.put(searchColumnName, new int[] { - getColumnModel().getColumn(columnId).getMaxWidth(), - getColumnModel().getColumn(columnId).getMinWidth(), - getColumnModel().getColumn(columnId).getPreferredWidth() }); - getColumnModel().getColumn(columnId).setMaxWidth(0); - getColumnModel().getColumn(columnId).setMinWidth(0); - getColumnModel().getColumn(columnId).setPreferredWidth(0); + TableColumn column = columnModel.getColumn(columnId); + columnSettings.put(searchColumnName, new int[]{ column.maxWidth, column.minWidth, column.preferredWidth }); + column.maxWidth = 0; + column.minWidth = 0; + column.preferredWidth = 0; } } - public void unhideColumn(SearchColumnName searchColumnName) { + public void showColumn(SearchColumnName searchColumnName) { int columnId = getColumnIdByName(searchColumnName); if (columnId > -1) { + TableColumn column = getColumnModel().getColumn(columnId); if (columnSettings.containsKey(searchColumnName)) { - getColumnModel().getColumn(columnId).setMaxWidth(columnSettings.get(searchColumnName)[0]); - getColumnModel().getColumn(columnId).setMinWidth(columnSettings.get(searchColumnName)[1]); - getColumnModel().getColumn(columnId).setPreferredWidth( - columnSettings.get(searchColumnName)[2]); + int[] columnSetting = columnSettings.get(searchColumnName); + column.maxWidth = columnSetting[0]; + column.minWidth = columnSetting[1]; + column.preferredWidth = columnSetting[2]; } else { - getColumnModel().getColumn(columnId).setMaxWidth(MAX_WIDTH); - getColumnModel().getColumn(columnId).setMinWidth(MIN_WIDTH); - getColumnModel().getColumn(columnId).setPreferredWidth(PREFERRED_WIDTH); + column.maxWidth = MAX_WIDTH; + column.minWidth = MIN_WIDTH; + column.preferredWidth = PREFERRED_WIDTH; } } } @@ -62,8 +62,8 @@ public void unhideColumn(SearchColumnName searchColumnName) { public boolean isHideColumn(SearchColumnName searchColumnName) { int columnId = getColumnIdByName(searchColumnName); if (columnId > -1) { - return getColumnModel().getColumn(columnId).getMinWidth() == 0 - && getColumnModel().getColumn(columnId).getPreferredWidth() == 0; + TableColumn column = getColumnModel().getColumn(columnId); + return column.minWidth == 0 && column.preferredWidth == 0; } return true; } @@ -79,5 +79,4 @@ public String getToolTipText(MouseEvent e) { } return null; } - } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SearchColumnName.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SearchColumnName.java index 576603ba..3139f1b7 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SearchColumnName.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SearchColumnName.java @@ -1,17 +1,14 @@ package org.lodder.subtools.multisubdownloader.gui.extra.table; -import java.util.Arrays; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.Messages; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor public enum SearchColumnName implements CustomColumnName { RELEASE("App.Release", String.class, false), @@ -27,17 +24,17 @@ public enum SearchColumnName implements CustomColumnName { SCORE("SearchColumnName.Score", Integer.class, false); private static final Map MAP = - Arrays.stream(SearchColumnName.values()).collect(Collectors.toMap(SearchColumnName::getColumnName, Function.identity())); + SearchColumnName.values().stream() + .collect(Collectors.toMap(SearchColumnName::getColumnName, Function.identity())); - private final String columnNameCode; - @Getter - private final Class c; - @Getter - private final boolean editable; + @val @override String columnName; + @val @override Class clazz; + @val @override boolean editable; - @Override - public String getColumnName() { - return Messages.getString(columnNameCode); + SearchColumnName(String columnNameCode, Class clazz, boolean editable) { + this.columnName = Messages.getText(columnNameCode); + this.clazz = clazz; + this.editable = editable; } public static Optional getForColumnName(String columnName) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableColumnName.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableColumnName.java index d50b005f..00ba4652 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableColumnName.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableColumnName.java @@ -1,40 +1,41 @@ package org.lodder.subtools.multisubdownloader.gui.extra.table; -import java.util.Arrays; import java.util.function.Function; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.sublibrary.model.Subtitle; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor public enum SubtitleTableColumnName implements CustomColumnName { - SELECT("App.Select", Boolean.class, true, subtitle -> false), + SELECT("App.Select", Boolean.class, true, _ -> false), SCORE("SubtitleTableColumnName.Score", Integer.class, false, Subtitle::getScore), FILENAME("SubtitleTableColumnName.Filename", String.class, false, Subtitle::getFileName), SOURCE("SubtitleTableColumnName.Source", String.class, false, Subtitle::getSubtitleSource), UPLOADER("SubtitleTableColumnName.Uploader", String.class, false, Subtitle::getUploader), - HEARINGIMPAIRED("SubtitleTableColumnName.hearingimpaired", Boolean.class, false, Subtitle::isHearingImpaired), + HEARINGIMPAIRED("SubtitleTableColumnName.hearingImpaired", Boolean.class, false, Subtitle::isHearingImpaired), QUALITY("SubtitleTableColumnName.Quality", String.class, false, Subtitle::getQuality), RELEASEGROUP("SubtitleTableColumnName.Releasegroup", String.class, false, Subtitle::getReleaseGroup); - private final String columnNameCode; - @Getter - private final Class c; - @Getter - private final boolean editable; - @Getter - private final Function valueFunction; - - @Override - public String getColumnName() { - return Messages.getString(columnNameCode); + + @val @override String columnName; + @val @override Class clazz; + @val @override boolean editable; + @val Function valueFunction; + + SubtitleTableColumnName(String columnNameCode, Class clazz, boolean editable, + Function valueFunction) { + this.columnName = Messages.getText(columnNameCode); + this.clazz = clazz; + this.editable = editable; + this.valueFunction = valueFunction; } public static SubtitleTableColumnName forColumnName(String columnName) { - return Arrays.stream(SubtitleTableColumnName.values()).filter(stcn -> stcn.getColumnName().equals(columnName)).findAny().orElseThrow(); + return SubtitleTableColumnName.values().stream() + .filter(stcn -> stcn.columnName.equals(columnName)) + .findAny() + .orElseThrow(); } public Object getValue(Subtitle subtitle) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableModel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableModel.java index 8a591737..19f7487f 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableModel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/SubtitleTableModel.java @@ -2,48 +2,47 @@ import static org.lodder.subtools.multisubdownloader.gui.extra.table.SubtitleTableColumnName.*; +import javax.swing.table.*; import java.io.Serial; -import java.util.Arrays; import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.swing.table.DefaultTableModel; - import org.lodder.subtools.sublibrary.model.Subtitle; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ Arrays.class }) public class SubtitleTableModel extends DefaultTableModel { @Serial private static final long serialVersionUID = 4205143311042280620L; - private final static SubtitleTableColumnName[] COLUMNS = - Stream.of(SELECT, SCORE, FILENAME, RELEASEGROUP, QUALITY, SOURCE, UPLOADER, HEARINGIMPAIRED).toArray(SubtitleTableColumnName[]::new); + private static final SubtitleTableColumnName[] COLUMNS = + Stream.of(SELECT, SCORE, FILENAME, RELEASEGROUP, QUALITY, SOURCE, UPLOADER, HEARINGIMPAIRED) + .toArray(SubtitleTableColumnName[]::new); public SubtitleTableModel(Object[][] data, String[] columnNames) { super(data, columnNames); } - public static SubtitleTableModel getDefaultSubtitleTableModel() { - String[] columnNames = Arrays.stream(COLUMNS).map(SubtitleTableColumnName::getColumnName).toArray(String[]::new); - return new SubtitleTableModel(new Object[][] {}, columnNames); + public static SubtitleTableModel createDefaultSubtitleTableModel() { + String[] columnNames = COLUMNS.stream().map(SubtitleTableColumnName::getColumnName).toArray(String[]::new); + return new SubtitleTableModel(new Object[][]{}, columnNames); } public void addRow(Subtitle subtitle) { - Object[] row = IntStream.range(0, getColumnCount()).mapToObj(this::getColumnName).map(SubtitleTableColumnName::forColumnName) - .map(stcn -> stcn.getValue(subtitle)).toArray(Object[]::new); + Object[] row = IntStream.range(0, getColumnCount()) + .mapToObj(this::getColumnName) + .map(SubtitleTableColumnName::forColumnName) + .map(stcn -> stcn.getValue(subtitle)) + .toArray(Object[]::new); this.addRow(row); } @Override public Class getColumnClass(int columnIndex) { - return COLUMNS[columnIndex].getC(); + return COLUMNS[columnIndex].clazz; } @Override public boolean isCellEditable(int row, int column) { - return COLUMNS[column].isEditable(); + return COLUMNS[column].editable; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/VideoTableModel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/VideoTableModel.java index bed205f3..06b5f89b 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/VideoTableModel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/VideoTableModel.java @@ -1,6 +1,8 @@ package org.lodder.subtools.multisubdownloader.gui.extra.table; -import javax.swing.table.*; +import static org.lodder.subtools.multisubdownloader.gui.extra.table.SearchColumnName.*; + +import javax.swing.table.DefaultTableModel; import java.io.Serial; import java.util.ArrayList; import java.util.EnumMap; @@ -13,8 +15,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import lombok.Getter; -import lombok.Setter; +import manifold.ext.props.rt.api.get; +import manifold.ext.props.rt.api.var; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.sublibrary.model.MovieRelease; import org.lodder.subtools.sublibrary.model.Release; @@ -23,31 +25,29 @@ public class VideoTableModel extends DefaultTableModel { - @Serial - private static final long serialVersionUID = 4205143311042280620L; + @Serial private static final long serialVersionUID = 4205143311042280620L; private static final List SHOW_COLUMNS = - List.of(SearchColumnName.TYPE, SearchColumnName.RELEASE, SearchColumnName.FILENAME, SearchColumnName.TITLE, SearchColumnName.SEASON, - SearchColumnName.EPISODE, SearchColumnName.FOUND, SearchColumnName.SELECT, SearchColumnName.OBJECT); + List.of(TYPE, RELEASE, FILENAME, TITLE, SEASON, EPISODE, FOUND, SELECT, OBJECT); - private static final List SUBTITLE_COLUMNS = - List.of(SearchColumnName.FILENAME, SearchColumnName.SOURCE, SearchColumnName.SCORE, SearchColumnName.SELECT, SearchColumnName.OBJECT); + private static final List SUBTITLE_COLUMNS = List.of(FILENAME, SOURCE, SCORE, SELECT, OBJECT); private static final Map SHOW_COLUMNS_INDEX = IntStream.range(0, SHOW_COLUMNS.size()) - .collect(() -> new EnumMap<>(SearchColumnName.class), (map, i) -> map.put(SHOW_COLUMNS.get(i), i), (l, r) -> { - throw new IllegalArgumentException("Duplicate keys [%s] and [%s]".formatted(l, r)); - }); + .collect(() -> new EnumMap<>(SearchColumnName.class), (map, i) -> map.put(SHOW_COLUMNS.get(i), i), + (l, r) -> { + throw new IllegalArgumentException("Duplicate keys [$l] and [$r]"); + }); private final Class[] columnTypes; private final Boolean[] columnEditables; private final Map rowMap = new LinkedHashMap<>(); + private boolean showOnlyFound = false; - @Setter - private UserInteractionHandler userInteractionHandler; + @var UserInteractionHandler userInteractionHandler; private VideoTableModel(List searchColumnNames) { - super(new Object[][] {}, searchColumnNames.stream().map(SearchColumnName::getColumnName).toArray(String[]::new)); - this.columnTypes = searchColumnNames.stream().map(SearchColumnName::getC).toArray(Class[]::new); + super(new Object[][]{}, searchColumnNames.stream().map(SearchColumnName::getColumnName).toArray(String[]::new)); + this.columnTypes = searchColumnNames.stream().map(SearchColumnName::getClazz).toArray(Class[]::new); this.columnEditables = searchColumnNames.stream().map(SearchColumnName::isEditable).toArray(Boolean[]::new); } @@ -77,7 +77,7 @@ public void addRow(Release release) { if (!showOnlyFound || release.getMatchingSubCount() != 0) { Row row = createRow(release); rowMap.put(release, row); - this.addRow(row.getRowObject()); + this.addRow(row.rowObject); } } } @@ -89,38 +89,32 @@ private Row createRow(Release release) { private static class Row { private final Release release; private final UserInteractionHandler userInteractionHandler; - @Getter - public final Vector rowObject; + @get Vector rowObject; public Row(Release release, UserInteractionHandler userInteractionHandler) { this.release = release; this.userInteractionHandler = userInteractionHandler; this.rowObject = SHOW_COLUMNS.stream().map(searchColumn -> switch (searchColumn) { - case RELEASE -> { - if (release instanceof TvRelease tvRelease) { - yield tvRelease.getOriginalName(); - } else if (release instanceof MovieRelease movieRelease) { - yield movieRelease.getName(); - } else { - throw new IllegalArgumentException("Unexpected release type: " + release.getClass()); - } - } - case FILENAME -> release.getFileName(); + case RELEASE -> switch (release) { + case TvRelease tvRelease -> tvRelease.originalName; + case MovieRelease movieRelease -> movieRelease.name; + }; + case FILENAME -> release.fileName; case FOUND -> calculateSubsFound(); case SELECT -> false; case OBJECT -> release; - case SEASON -> release instanceof TvRelease tvRelease ? tvRelease.getSeason() : null; - case EPISODE -> release instanceof TvRelease tvRelease ? tvRelease.getEpisodeNumbers().get(0) : null; - case TYPE -> release.getVideoType(); - case TITLE -> release instanceof TvRelease tvRelease ? tvRelease.getTitle() : null; + case SEASON -> release instanceof TvRelease tvRelease ? tvRelease.season : null; + case EPISODE -> release instanceof TvRelease tvRelease ? tvRelease.firstEpisode : null; + case TYPE -> release.videoType; + case TITLE -> release instanceof TvRelease tvRelease ? tvRelease.title : null; default -> throw new IllegalArgumentException("Unexpected value: " + searchColumn); }).collect(Collectors.toCollection(Vector::new)); } private int calculateSubsFound() { - return userInteractionHandler != null - ? userInteractionHandler.getAutomaticSelection(release.getMatchingSubs()).size() - : release.getMatchingSubCount(); + return userInteractionHandler != null ? + userInteractionHandler.getAutomaticSelection(release.getMatchingSubs()).size() : + release.getMatchingSubCount(); } public int updateSubsFound() { @@ -139,11 +133,11 @@ public boolean isSelected() { public void addRow(Subtitle subtitle) { synchronized (this) { Vector row = SUBTITLE_COLUMNS.stream().map(searchColumn -> switch (searchColumn) { - case FILENAME -> subtitle.getFileName(); + case FILENAME -> subtitle.fileName; case SELECT -> false; case OBJECT -> subtitle; - case SOURCE -> subtitle.getSubtitleSource(); - case SCORE -> subtitle.getScore(); + case SOURCE -> subtitle.subtitleSource; + case SCORE -> subtitle.score; default -> throw new IllegalArgumentException("Unexpected value: " + searchColumn); }).collect(Collectors.toCollection(Vector::new)); this.addRow(row); @@ -207,10 +201,6 @@ public void setShowOnlyFound(boolean showOnlyFound) { updateTable(); } - public boolean isShowOnlyFound() { - return this.showOnlyFound; - } - public void executedSynchronized(Runnable runnable) { synchronized (this) { runnable.run(); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/ZebraJTable.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/ZebraJTable.java index 20f41050..d648c4ca 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/ZebraJTable.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/extra/table/ZebraJTable.java @@ -1,6 +1,6 @@ package org.lodder.subtools.multisubdownloader.gui.extra.table; -import java.awt.Dimension; +import java.awt.*; import java.io.Serial; /** @@ -10,6 +10,7 @@ public class ZebraJTable extends javax.swing.JTable { @Serial private static final long serialVersionUID = -6943213333291518652L; + private final java.awt.Color[] rowColors = new java.awt.Color[2]; private boolean drawStripes = false; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/button/AbstractButtonExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/button/AbstractButtonExtension.java deleted file mode 100644 index b27f527f..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/button/AbstractButtonExtension.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.button; - -import javax.swing.*; -import java.awt.event.ActionListener; -import java.util.function.Consumer; - -import lombok.experimental.UtilityClass; -import org.lodder.subtools.sublibrary.util.BooleanConsumer; - -@UtilityClass -public class AbstractButtonExtension { - - public T withActionListener(T abstractButton, ActionListener listener) { - abstractButton.addActionListener(listener); - return abstractButton; - } - - public T withActionListener(T abstractButton, Runnable listener) { - return withActionListener(abstractButton, arg -> listener.run()); - } - - public T withActionListenerSelf(T abstractButton, Consumer selfConsumerListener) { - return withActionListener(abstractButton, arg -> selfConsumerListener.accept(abstractButton)); - } - - public T withSelectedListener(T abstractButton, BooleanConsumer selectedConsumer) { - return withActionListener(abstractButton, arg -> selectedConsumer.accept(abstractButton.isSelected())); - } - - public T actionCommand(T abstractButton, String actionCommand) { - abstractButton.setActionCommand(actionCommand); - return abstractButton; - } - - public T withActionCommand(T abstractButton, String actionCommand) { - abstractButton.getModel().setActionCommand(actionCommand); - return abstractButton; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/button/JButtonExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/button/JButtonExtension.java deleted file mode 100644 index 92828e54..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/button/JButtonExtension.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.button; - -import javax.swing.JButton; -import javax.swing.JRootPane; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class JButtonExtension { - - public T defaultButtonFor(T abstractButton, JRootPane rootPane) { - rootPane.setDefaultButton(abstractButton); - return abstractButton; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/component/ComponentExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/component/ComponentExtension.java deleted file mode 100644 index 4c15e8a6..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/component/ComponentExtension.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.component; - -import java.awt.Component; -import java.awt.event.MouseListener; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class ComponentExtension { - - public T withMouseListener(T component, MouseListener listener) { - component.addMouseListener(listener); - return component; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/container/ContainerExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/container/ContainerExtension.java deleted file mode 100644 index 04e60aac..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/container/ContainerExtension.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.container; - -import java.awt.Container; -import java.awt.LayoutManager; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class ContainerExtension { - - public T layout(T container, LayoutManager mgr) { - container.setLayout(mgr); - return container; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcheckbox/JCheckBoxExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcheckbox/JCheckBoxExtension.java deleted file mode 100644 index cb49d75f..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcheckbox/JCheckBoxExtension.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox; - -import javax.swing.*; -import java.util.Arrays; - -import lombok.experimental.UtilityClass; -import org.lodder.subtools.sublibrary.util.BooleanConsumer; - -@UtilityClass -public class JCheckBoxExtension { - - public T addCheckedChangeListener(T checkBox, BooleanConsumer... listeners) { - checkBox.addItemListener(e -> Arrays.stream(listeners).forEach(listener -> listener.accept(((JCheckBox) e.getSource()).isSelected()))); - return checkBox; - } - - public T visible(T checkBox, boolean visible) { - checkBox.setVisible(visible); - return checkBox; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcombobox/MyComboBox.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcombobox/MyComboBox.java deleted file mode 100644 index b524fe3f..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcombobox/MyComboBox.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox; - -import javax.swing.*; -import javax.swing.border.*; -import java.awt.*; -import java.awt.event.ActionListener; -import java.awt.event.ItemListener; -import java.io.Serial; -import java.util.Collection; -import java.util.Vector; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; - -import com.google.common.collect.Iterables; -import org.lodder.subtools.multisubdownloader.gui.ToStringListCellRenderer; - -public class MyComboBox extends JComboBox { - - private static final Border ERROR_BORDER = new LineBorder(Color.RED, 1); - private Border defaultBorder; - private Predicate selectedValueVerifier; - - @Serial - private static final long serialVersionUID = -8449456978689044914L; - - /** - * Creates a MyComboBox that takes its items from an - * existing ComboBoxModel. Since the - * ComboBoxModel is provided, a combo box created using - * this constructor does not create a default combo box model and - * may impact how the insert, remove and add methods behave. - * - * @param aModel the ComboBoxModel that provides the - * displayed list of items - * @see DefaultComboBoxModel - */ - public MyComboBox(ComboBoxModel aModel) { - super(aModel); - this.defaultBorder = getBorder(); - } - - /** - * Creates a MyComboBox that contains the elements - * in the specified array. By default, the first item in the array - * (and therefore the data model) becomes selected. - * - * @param items an array of objects to insert into the combo box - * @see DefaultComboBoxModel - */ - public MyComboBox(E[] items) { - super(items); - this.defaultBorder = getBorder(); - } - - /** - * Creates a MyComboBox that contains the elements - * in the specified collection. By default, the first item in the - * collection (and therefore the data model) becomes selected. - * - * @param items a collection of objects to insert into the combo box - * @param elementType the type of elements in the list - * @see DefaultComboBoxModel - */ - public MyComboBox(Collection items, Class elementType) { - super(Iterables.toArray(items, elementType)); - this.defaultBorder = getBorder(); - } - - /** - * Creates a MyComboBox that contains the elements - * in the specified Vector. By default, the first item in the vector - * (and therefore the data model) becomes selected. - * - * @param items an array of vectors to insert into the combo box - * @see DefaultComboBoxModel - */ - public MyComboBox(Vector items) { - super(items); - this.defaultBorder = getBorder(); - } - - /** - * Creates a MyComboBox with a default data model. - * The default data model is an empty list of objects. - * Use addItem to add items. By default, the first item - * in the data model becomes selected. - * - * @see DefaultComboBoxModel - */ - public MyComboBox() { - super(); - this.defaultBorder = getBorder(); - } - - public static MyComboBox ofValues(E... values) { - return new MyComboBox<>(values); - } - - public MyComboBox withModel(ComboBoxModel model) { - setModel(model); - return this; - } - - public MyComboBox withRenderer(ListCellRenderer renderer) { - setRenderer(renderer); - return this; - } - - public MyComboBox withToStringRenderer(Function toStringRenderer) { - return withRenderer(ToStringListCellRenderer.of(getRenderer(), toStringRenderer)); - } - - public MyComboBox withToMessageStringRenderer(Function toStringRenderer) { - return withRenderer(ToStringListCellRenderer.ofMessage(getRenderer(), toStringRenderer)); - } - - public MyComboBox withItemListener(ItemListener itemListener) { - this.addItemListener(itemListener); - return this; - } - - public MyComboBox withItemListener(Runnable itemListener) { - return withItemListener(arg -> itemListener.run()); - } - - public MyComboBox withSelectedItem(E item) { - setSelectedItem(item); - return this; - } - - public MyComboBox withActionListener(ActionListener actionListener) { - addActionListener(actionListener); - return this; - } - - @SuppressWarnings("unchecked") - public MyComboBox withEventConsumer(Consumer> actionListener) { - addActionListener(event -> actionListener.accept((MyComboBox) (event.getSource()))); - return this; - } - - @SuppressWarnings("unchecked") - public MyComboBox withSelectedItemConsumer(Consumer actionListener) { - addActionListener(event -> actionListener.accept(((MyComboBox) (event.getSource())).getSelectedItem())); - return this; - } - - @SuppressWarnings("unchecked") - @Override - public E getSelectedItem() { - return (E) super.getSelectedItem(); - } - - public MyComboBox withSelectedValueVerifier(Predicate valueVerifier) { - this.selectedValueVerifier = valueVerifier; - return this; - } - - @Override - public void setBorder(Border border) { - super.setBorder(border); - defaultBorder = border; - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - refreshState(); - } - - public void refreshState() { - if (!isEnabled()) { - super.setBorder(defaultBorder); - } else if (selectedValueVerifier != null && !selectedValueVerifier.test(getSelectedItem())) { - super.setBorder(ERROR_BORDER); - } - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcomponent/JComponentExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcomponent/JComponentExtension.java deleted file mode 100644 index b227d8e5..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jcomponent/JComponentExtension.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent; - -import java.util.Arrays; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import javax.swing.JComponent; -import javax.swing.JScrollPane; - -import java.awt.Component; -import java.awt.Container; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class JComponentExtension { - - public T withEnabled(T component, boolean enabled) { - component.setEnabled(enabled); - return component; - } - - public T withEnabled(T component) { - return withEnabled(component, true); - } - - public T withDisabled(T component) { - return withEnabled(component, false); - } - - public T addComponent(T component, S child) { - component.add(child); - return component; - } - - public T addComponent(T component, S child, Object constraints) { - component.add(child, constraints); - return component; - } - - public S addTo(S child, T parent) { - parent.add(child); - return child; - } - - public S addTo(S child, T parent, Object constraints) { - parent.add(child, constraints); - return child; - } - - public T addComponent(T component, Object constraints, S child) { - component.add(child, constraints); - return component; - } - - public void setEnabledRecursive(Component component, boolean enabled) { - setRecursive(component, c -> c.setEnabled(enabled)); - } - - public void setRecursive(Component component, Consumer consumer) { - setRecursive(component, consumer, c -> true); - } - - public void setRecursive(Component component, Consumer consumer, Predicate condition) { - if (component != null) { - consumer.accept(component); - if (component instanceof Container container && condition.test(container)) { - Arrays.stream(container.getComponents()).forEach(child -> setRecursive(child, consumer, condition)); - } - } - } - - public T enabledRecursive(T component, boolean enabled) { - setEnabledRecursive(component, enabled); - return component; - } - - public JScrollPane scrollPane(JScrollPane scrollPane, Component view) { - scrollPane.setViewportView(view); - return scrollPane; - } - - public T withToolTipText(T component, String text) { - component.setToolTipText(text); - return component; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jpopupmenu/MyPopupMenu.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jpopupmenu/MyPopupMenu.java index f1e66ded..84bcbaab 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jpopupmenu/MyPopupMenu.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jpopupmenu/MyPopupMenu.java @@ -1,29 +1,21 @@ package org.lodder.subtools.multisubdownloader.gui.jcomponent.jpopupmenu; -import javax.swing.JPopupMenu; - -import java.awt.Component; -import java.awt.Point; +import javax.swing.*; +import java.awt.*; import java.io.Serial; +import manifold.ext.props.rt.api.var; + public class MyPopupMenu extends JPopupMenu { @Serial private static final long serialVersionUID = 1084650376633196066L; - private Point clickLocation; + @var Point clickLocation; @Override public void show(Component invoker, int x, int y) { super.show(invoker, x, y); - setClickLocation(new Point(x, y)); - } - - public void setClickLocation(Point clickLocation) { - this.clickLocation = clickLocation; - } - - public Point getClickLocation() { - return clickLocation; + clickLocation = new Point(x, y); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jscrollpane/JScrollPaneExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jscrollpane/JScrollPaneExtension.java deleted file mode 100644 index aa8b90d5..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jscrollpane/JScrollPaneExtension.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jscrollpane; - -import javax.swing.JScrollPane; - -import java.awt.Component; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class JScrollPaneExtension { - - public JScrollPane withViewportView(JScrollPane scrollPane, Component view) { - scrollPane.setViewportView(view); - return scrollPane; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jslider/JSliderExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jslider/JSliderExtension.java deleted file mode 100644 index 9f6d05fe..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jslider/JSliderExtension.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jslider; - -import javax.swing.JSlider; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class JSliderExtension { - - public T withMinimum(T slider, int minimum) { - slider.setMinimum(minimum); - return slider; - } - - public T withMaximum(T slider, int maximum) { - slider.setMaximum(maximum); - return slider; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextarea/JTextAreaExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextarea/JTextAreaExtension.java deleted file mode 100644 index 43270147..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextarea/JTextAreaExtension.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextarea; - -import javax.swing.JTextArea; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class JTextAreaExtension { - - public T autoscrolls(T textArea, boolean autoscrolls) { - textArea.setAutoscrolls(autoscrolls); - return textArea; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextcomponent/JTextComponentExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextcomponent/JTextComponentExtension.java deleted file mode 100644 index 4105f1f4..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextcomponent/JTextComponentExtension.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextcomponent; - -import javax.swing.text.JTextComponent; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class JTextComponentExtension { - - public T editable(T textComponent, boolean editable) { - textComponent.setEditable(editable); - return textComponent; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/JTextFieldExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/JTextFieldExtension.java deleted file mode 100644 index 7d981a75..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/JTextFieldExtension.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield; - -import javax.swing.JTextField; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class JTextFieldExtension { - - public T withColumns(T textField, int columns) { - textField.setColumns(columns); - return textField; - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyPasswordField.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyPasswordField.java index ff73cc8d..88433e42 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyPasswordField.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyPasswordField.java @@ -1,20 +1,21 @@ package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield; import javax.swing.*; -import javax.swing.border.*; -import javax.swing.event.*; +import javax.swing.border.Border; +import javax.swing.border.LineBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import java.awt.*; import java.io.Serial; -import java.util.Arrays; -import java.util.Optional; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; -import lombok.Getter; +import manifold.ext.props.rt.api.var; import org.apache.commons.lang3.StringUtils; -import org.lodder.subtools.sublibrary.util.BooleanConsumer; +import org.lodder.subtools.sublibrary.util.function.BooleanConsumer; -public class MyPasswordField extends JPasswordField implements MypasswordFieldOthersIntf { +public class MyPasswordField extends JPasswordField implements MyPasswordFieldOthersIntf { @Serial private static final long serialVersionUID = -3002009544577141751L; @@ -24,8 +25,8 @@ public class MyPasswordField extends JPasswordField implements MypasswordFieldOt public Predicate valueVerifier = StringUtils::isNotEmpty; private boolean requireValue; - private Consumer valueChangedCalbackListener; - private BooleanConsumer[] validityChangedCalbackListeners; + private Consumer valueChangedCallbackListener; + private BooleanConsumer[] validityChangedCallbackListeners; private final ObjectWrapper valueWrapper = new ObjectWrapper<>(); private final ObjectWrapper validWrapper = new ObjectWrapper<>(); @@ -67,26 +68,19 @@ public MyPasswordField requireValue(boolean requireValue) { } @Override - public MyPasswordField withValueChangedCallback(Consumer valueChangedCalbackListener) { - this.valueChangedCalbackListener = valueChangedCalbackListener; + public MyPasswordField withValueChangedCallback(Consumer valueChangedCallbackListener) { + this.valueChangedCallbackListener = valueChangedCallbackListener; return this; } @Override - public MyPasswordField withValidityChangedCallback(BooleanConsumer... validityChangedCalbackListeners) { - this.validityChangedCalbackListeners = validityChangedCalbackListeners; + public MyPasswordField withValidityChangedCallback(BooleanConsumer... validityChangedCallbackListeners) { + this.validityChangedCallbackListeners = validityChangedCallbackListeners; return this; } private static class ObjectWrapper { - @Getter - private S value; - - public boolean setValue(S value) { - boolean changed = this.value != value; - this.value = value; - return changed; - } + @var S value; } @Override @@ -116,10 +110,11 @@ public MyPasswordField build() { } else if (requireValue) { completeValueVerifier = StringUtils::isNotEmpty; } else { - completeValueVerifier = t -> true; + completeValueVerifier = _ -> true; } - if (valueVerifier != null || requireValue || valueChangedCalbackListener != null || validityChangedCalbackListeners != null) { + if (valueVerifier != null || requireValue || valueChangedCallbackListener != null || + validityChangedCallbackListeners != null) { checkValidity(getRawText()); getDocument().addDocumentListener(new DocumentListener() { @@ -147,15 +142,17 @@ private void checkValidity(String text) { boolean valid = completeValueVerifier.test(text); setSuperBorder(valid ? MyPasswordField.getDefaultBorder(this) : ERROR_BORDER); - boolean changedValidity = validWrapper.setValue(valid); - if (changedValidity && validityChangedCalbackListeners != null) { - Arrays.stream(validityChangedCalbackListeners).forEach(listener -> listener.accept(valid)); + boolean changedValidity = Objects.equals(validWrapper.value, valid); + validWrapper.value = valid; + if (changedValidity && validityChangedCallbackListeners != null) { + validityChangedCallbackListeners.forEach(listener -> listener.accept(valid)); } - if (valueChangedCalbackListener != null) { - boolean valueChanged = valueWrapper.setValue(text); + if (valueChangedCallbackListener != null) { + boolean valueChanged = !StringUtils.equals(valueWrapper.value, text); + valueWrapper.value = text; if (valueChanged) { - valueChangedCalbackListener.accept(text); + valueChangedCallbackListener.accept(text); } } } @@ -170,16 +167,11 @@ public String getText() { return completeValueVerifier.test(text) ? text : null; } - - public Optional getOptionalObject() { - return Optional.ofNullable(getText()); - } - @Override public void setText(String password) { super.setText(password); - valueWrapper.setValue(password); - validWrapper.setValue(completeValueVerifier.test(password)); + valueWrapper.value = password; + validWrapper.value = completeValueVerifier.test(password); } public boolean hasValidValue() { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyPasswordFieldOthersIntf.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyPasswordFieldOthersIntf.java new file mode 100644 index 00000000..83bed150 --- /dev/null +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyPasswordFieldOthersIntf.java @@ -0,0 +1,22 @@ +package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +import org.lodder.subtools.sublibrary.util.function.BooleanConsumer; + +public interface MyPasswordFieldOthersIntf { + MyPasswordFieldOthersIntf withValueVerifier(Predicate verifier); + + default MyPasswordFieldOthersIntf requireValue() { + return requireValue(true); + } + + MyPasswordFieldOthersIntf requireValue(boolean requireValue); + + MyPasswordFieldOthersIntf withValueChangedCallback(Consumer valueChangedCallbackListener); + + MyPasswordFieldOthersIntf withValidityChangedCallback(BooleanConsumer... validityChangedCallbackListeners); + + MyPasswordField build(); +} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextField.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextField.java deleted file mode 100644 index 95e92d95..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextField.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield; - -import java.io.Serial; - -public class MyTextField extends MyTextFieldCommon> { - - @Serial - private static final long serialVersionUID = 1580566911085697756L; - - private MyTextField() { - super(); - } - - public static MyTextFieldToStringMapperIntf> buildForType(Class type) { - return new MyTextField<>(); - } -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldCommon.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldCommon.java index 82fc249d..d0cc82f0 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldCommon.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldCommon.java @@ -1,8 +1,10 @@ package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield; import javax.swing.*; -import javax.swing.border.*; -import javax.swing.event.*; +import javax.swing.border.Border; +import javax.swing.border.LineBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import java.awt.*; import java.io.Serial; import java.util.Arrays; @@ -12,12 +14,13 @@ import java.util.function.Predicate; import org.apache.commons.lang3.StringUtils; -import org.lodder.subtools.sublibrary.util.BooleanConsumer; +import org.lodder.subtools.sublibrary.util.function.BooleanConsumer; -public abstract class MyTextFieldCommon> extends JTextField implements - MyTextFieldToStringMapperIntf, - MyTextFieldToObjectMapperIntf, - MyTextFieldOthersIntf { +public abstract sealed class MyTextFieldCommon> extends JTextField implements + MyTextFieldToStringMapperIntf, + MyTextFieldToObjectMapperIntf, + MyTextFieldOthersIntf + permits MyTextFieldInteger, MyTextFieldPath, MyTextFieldString { @Serial private static final long serialVersionUID = -393882042554264226L; @@ -28,8 +31,8 @@ public abstract class MyTextFieldCommon> ex private Function toObjectMapper; private Predicate valueVerifier; private boolean requireValue; - private Consumer valueChangedCalbackListener; - private BooleanConsumer[] validityChangedCalbackListeners; + private Consumer valueChangedCallbackListener; + private BooleanConsumer[] validityChangedCallbackListeners; private final ObjectWrapper valueWrapper = new ObjectWrapper<>(); private final ObjectWrapper validWrapper = new ObjectWrapper<>(); @@ -78,14 +81,14 @@ public R requireValue(boolean requireValue) { } @Override - public R withValueChangedCallback(Consumer valueChangedCalbackListener) { - this.valueChangedCalbackListener = valueChangedCalbackListener; + public R withValueChangedCallback(Consumer valueChangedCallbackListener) { + this.valueChangedCallbackListener = valueChangedCallbackListener; return self(); } @Override - public final R withValidityChangedCallback(BooleanConsumer... validityChangedCalbackListeners) { - this.validityChangedCalbackListeners = validityChangedCalbackListeners; + public final R withValidityChangedCallback(BooleanConsumer... validityChangedCallbackListeners) { + this.validityChangedCallbackListeners = validityChangedCallbackListeners; return self(); } @@ -133,7 +136,8 @@ public R build() { completeValueVerifier = t -> true; } - if (valueVerifier != null || requireValue || valueChangedCalbackListener != null || validityChangedCalbackListeners != null) { + if (valueVerifier != null || requireValue || valueChangedCallbackListener != null || + validityChangedCallbackListeners != null) { checkValidity(getText()); getDocument().addDocumentListener(new DocumentListener() { @@ -162,15 +166,15 @@ private void checkValidity(String text) { setSuperBorder(valid ? MyTextFieldCommon.getDefaultBorder(self()) : ERROR_BORDER); boolean changedValidity = validWrapper.setValue(valid); - if (changedValidity && validityChangedCalbackListeners != null) { - Arrays.stream(validityChangedCalbackListeners).forEach(listener -> listener.accept(valid)); + if (changedValidity && validityChangedCallbackListeners != null) { + Arrays.stream(validityChangedCallbackListeners).forEach(listener -> listener.accept(valid)); } - if (valueChangedCalbackListener != null) { + if (valueChangedCallbackListener != null) { T value = toObjectMapper.apply(text); boolean valueChanged = valueWrapper.setValue(toObjectMapper.apply(text)); if (valueChanged) { - valueChangedCalbackListener.accept(value); + valueChangedCallbackListener.accept(value); } } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldInteger.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldInteger.java index bc5f58ac..d393aba1 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldInteger.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldInteger.java @@ -6,7 +6,7 @@ import org.apache.commons.lang3.StringUtils; -public class MyTextFieldInteger extends MyTextFieldCommon { +public final class MyTextFieldInteger extends MyTextFieldCommon { @Serial private static final long serialVersionUID = -8526638589445703452L; @@ -28,10 +28,10 @@ private MyTextFieldInteger() { } - public static MyTextFieldOthersIntf builder() { + public static MyTextFieldOthersIntf builder() { return new MyTextFieldInteger() - .withToStringMapper(TO_STRING_MAPPER) - .withToObjectMapper(TO_OBJECT_MAPPER) - .withValueVerifier(INT_VERIFIER); + .withToStringMapper(TO_STRING_MAPPER) + .withToObjectMapper(TO_OBJECT_MAPPER) + .withValueVerifier(INT_VERIFIER); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldOthersIntf.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldOthersIntf.java index 8affd69a..81bbf3a8 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldOthersIntf.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldOthersIntf.java @@ -3,7 +3,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; -import org.lodder.subtools.sublibrary.util.BooleanConsumer; +import org.lodder.subtools.sublibrary.util.function.BooleanConsumer; public interface MyTextFieldOthersIntf> { MyTextFieldOthersIntf withValueVerifier(Predicate verifier); @@ -14,9 +14,9 @@ default MyTextFieldOthersIntf requireValue() { MyTextFieldOthersIntf requireValue(boolean requireValue); - MyTextFieldOthersIntf withValueChangedCallback(Consumer valueChangedCalbackListener); + MyTextFieldOthersIntf withValueChangedCallback(Consumer valueChangedCallbackListener); - MyTextFieldOthersIntf withValidityChangedCallback(BooleanConsumer... validityChangedCalbackListeners); + MyTextFieldOthersIntf withValidityChangedCallback(BooleanConsumer... validityChangedCallbackListeners); R build(); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldPath.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldPath.java index c77d252f..68ecd394 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldPath.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldPath.java @@ -8,11 +8,12 @@ import org.apache.commons.lang3.StringUtils; -public class MyTextFieldPath extends MyTextFieldCommon { +public final class MyTextFieldPath extends MyTextFieldCommon { @Serial private static final long serialVersionUID = -8526638589445703452L; - private static final Function TO_STRING_MAPPER = path -> path == null ? null : path.toAbsolutePath().toString(); + private static final Function TO_STRING_MAPPER = + path -> path == null ? null : path.toAbsolutePath().toString(); private static final Function TO_OBJECT_MAPPER = s -> s == null ? null : Path.of(s); public static final Predicate ABSOLUTE_PATH_VERIFIER = text -> { try { @@ -26,10 +27,10 @@ private MyTextFieldPath() { } - public static MyTextFieldOthersIntf builder() { + public static MyTextFieldOthersIntf builder() { return new MyTextFieldPath() - .withToStringMapper(TO_STRING_MAPPER) - .withToObjectMapper(TO_OBJECT_MAPPER) - .withValueVerifier(ABSOLUTE_PATH_VERIFIER); + .withToStringMapper(TO_STRING_MAPPER) + .withToObjectMapper(TO_OBJECT_MAPPER) + .withValueVerifier(ABSOLUTE_PATH_VERIFIER); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldString.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldString.java index 96b4b3d7..07830a38 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldString.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MyTextFieldString.java @@ -4,26 +4,26 @@ import java.util.function.Function; import java.util.function.Predicate; -public class MyTextFieldString extends MyTextFieldCommon { +public final class MyTextFieldString extends MyTextFieldCommon { @Serial private static final long serialVersionUID = -8526638589445703452L; private static final Function TO_STRING_MAPPER = Function.identity(); private static final Function TO_OBJECT_MAPPER = Function.identity(); - public static final Predicate VERIFIER = text -> true; + public static final Predicate VERIFIER = _ -> true; private MyTextFieldString() { } - public static MyTextFieldOthersIntf builder() { + public static MyTextFieldOthersIntf builder() { return new MyTextFieldString() - .withToStringMapper(TO_STRING_MAPPER) - .withToObjectMapper(TO_OBJECT_MAPPER) - .withValueVerifier(VERIFIER); + .withToStringMapper(TO_STRING_MAPPER) + .withToObjectMapper(TO_OBJECT_MAPPER) + .withValueVerifier(VERIFIER); } - public static MyTextFieldOthersIntf create() { + public static MyTextFieldOthersIntf create() { return builder().build(); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MypasswordFieldOthersIntf.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MypasswordFieldOthersIntf.java deleted file mode 100644 index 33b2bc1b..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/jcomponent/jtextfield/MypasswordFieldOthersIntf.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield; - -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.lodder.subtools.sublibrary.util.BooleanConsumer; - -public interface MypasswordFieldOthersIntf { - MypasswordFieldOthersIntf withValueVerifier(Predicate verifier); - - default MypasswordFieldOthersIntf requireValue() { - return requireValue(true); - } - - MypasswordFieldOthersIntf requireValue(boolean requireValue); - - MypasswordFieldOthersIntf withValueChangedCallback(Consumer valueChangedCalbackListener); - - MypasswordFieldOthersIntf withValidityChangedCallback(BooleanConsumer... validityChangedCalbackListeners); - - MyPasswordField build(); -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/InputPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/InputPanel.java index 2d5efec8..6a255bad 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/InputPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/InputPanel.java @@ -1,60 +1,40 @@ package org.lodder.subtools.multisubdownloader.gui.panels; -import java.io.Serial; +import static manifold.ext.props.rt.api.PropOption.*; -import javax.swing.JButton; -import javax.swing.JPanel; +import javax.swing.*; +import java.io.Serial; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.actions.SearchAction; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; import org.lodder.subtools.sublibrary.Language; -public abstract class InputPanel extends JPanel { +public abstract sealed class InputPanel extends JPanel permits SearchFileInputPanel, SearchTextInputPanel { @Serial private static final long serialVersionUID = 7753220002440733463L; - private JButton btnSearch; - private MyComboBox cbxLanguage; - - public InputPanel() { - createComponents(); - } + @val JButton searchButton = new JButton(Messages.getText("InputPanel.SearchForSubtitles")); + @val(Protected) JComboBox languageCbx = + new JComboBox<>(Language.values()).toMessageStringRenderer(Language::getMsgCode); public Language getSelectedLanguage() { - return cbxLanguage.getSelectedItem(); + return languageCbx.getSelectedValue(); } - public void setSelectedlanguage(Language language) { - cbxLanguage.setSelectedItem(language); + public void setSelectedLanguage(Language language) { + languageCbx.setSelectedItem(language); } public void addSearchAction(SearchAction searchAction) { - if (searchAction != null) { - btnSearch.addActionListener(event -> new Thread(searchAction).start()); - } + searchButton.addActionListener(_ -> new Thread(searchAction).start()); } public void enableSearchButton() { - btnSearch.setEnabled(true); + searchButton.setEnabled(true); } public void disableSearchButton() { - this.btnSearch.setEnabled(false); - } - - protected JButton getSearchButton() { - return this.btnSearch; - } - - protected MyComboBox getLanguageCbx() { - return this.cbxLanguage; - } - - private void createComponents() { - cbxLanguage = new MyComboBox<>(Language.values()) - .withToMessageStringRenderer(Language::getMsgCode); - - btnSearch = new JButton(Messages.getString("InputPanel.SearchForSubtitles")); + this.searchButton.setEnabled(false); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/LoggingPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/LoggingPanel.java index 35b6909a..a9ac6219 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/LoggingPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/LoggingPanel.java @@ -1,48 +1,39 @@ package org.lodder.subtools.multisubdownloader.gui.panels; -import java.io.Serial; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JTextArea; +import static org.lodder.subtools.multisubdownloader.Messages.*; -import org.lodder.subtools.multisubdownloader.Messages; -import org.lodder.subtools.multisubdownloader.gui.extra.LogTextAppender; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextarea.JTextAreaExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextcomponent.JTextComponentExtension; +import javax.swing.*; +import java.io.Serial; import ch.qos.logback.classic.Level; -import lombok.experimental.ExtensionMethod; +import ch.qos.logback.classic.Logger; import net.miginfocom.swing.MigLayout; +import org.lodder.subtools.multisubdownloader.gui.extra.LogTextAppender; -@ExtensionMethod({ JTextComponentExtension.class, JTextAreaExtension.class }) public class LoggingPanel extends JPanel { @Serial private static final long serialVersionUID = 1578326761175927376L; + private final JTextArea txtLogging; - private final ch.qos.logback.classic.Logger root = - (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + private final Logger ROOT = (Logger) org.slf4j.LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); public LoggingPanel() { this.setLayout(new MigLayout("", "[698px,grow][]", "[][70px,grow]")); - JScrollPane scrollPane_1 = new JScrollPane(); - this.add(new JLabel(Messages.getString("App.Logging")), "cell 0 0,alignx right,gaptop 5"); + JScrollPane scrollPane = new JScrollPane(); + this.add(new JLabel(getText("App.Logging")), "cell 0 0,alignx right,gaptop 5"); this.add(new JSeparator(), "cell 0 0,growx,gaptop 5"); Level[] logLevels = { Level.ALL, Level.TRACE, Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR }; - MyComboBox cbxLogLevel = new MyComboBox<>(logLevels) - .withSelectedItem(root.getLevel()) - .withSelectedItemConsumer(root::setLevel); + JComboBox cbxLogLevel = new JComboBox<>(logLevels) + .selectedValue(ROOT.getLevel()) + .selectedItemConsumer(ROOT::setLevel); this.add(cbxLogLevel, "cell 1 0,alignx right"); - this.add(scrollPane_1, "cell 0 1 2 1,grow"); + this.add(scrollPane, "cell 0 1 2 1,grow"); - txtLogging = new JTextArea().autoscrolls(true).editable(false); - scrollPane_1.setViewportView(txtLogging); + txtLogging = new JTextArea().autoScrolls(true).editable(false); + scrollPane.setViewportView(txtLogging); new LogTextAppender(txtLogging); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/ResultPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/ResultPanel.java index 762b232b..a6e17d5b 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/ResultPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/ResultPanel.java @@ -1,27 +1,22 @@ package org.lodder.subtools.multisubdownloader.gui.panels; -import java.io.Serial; +import static org.lodder.subtools.multisubdownloader.Messages.*; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.table.DefaultTableModel; +import javax.swing.*; +import javax.swing.table.*; +import java.awt.event.ActionListener; +import java.io.Serial; -import org.lodder.subtools.multisubdownloader.Messages; +import net.miginfocom.swing.MigLayout; import org.lodder.subtools.multisubdownloader.gui.extra.table.CustomTable; import org.lodder.subtools.multisubdownloader.gui.extra.table.SearchColumnName; import org.lodder.subtools.multisubdownloader.gui.extra.table.VideoTableModel; -import java.awt.event.ActionListener; - -import net.miginfocom.swing.MigLayout; - public class ResultPanel extends JPanel { @Serial private static final long serialVersionUID = 2368028332402129899L; + private JButton btnMove; private JButton btnSelectAll; private JScrollPane scrollPane; @@ -44,7 +39,7 @@ private void addComponentsToPanel() { actionButtonsPanel.add(btnDownload, "cell 0 0,alignx right,aligny top"); actionButtonsPanel.add(btnMove, "cell 1 0,alignx left,aligny top"); - add(new JLabel(Messages.getString("ResultPanel.SearchResults")), "cell 0 0 2 1,gapy 5"); + add(new JLabel(getText("ResultPanel.SearchResults")), "cell 0 0 2 1,gapy 5"); add(new JSeparator(), "cell 0 0 2 1,growx,gaptop 5"); add(scrollPane, "cell 0 1 1 4,grow"); add(btnSelectNone, "cell 1 1,aligny bottom"); @@ -103,18 +98,18 @@ public void setMoveAction(ActionListener moveAction) { } private void setupListeners() { - btnSelectNone.addActionListener(e -> deselectAllRows()); - btnSelectFound.addActionListener(e -> selectRowsWithFoundSubtitles()); - btnSelectAll.addActionListener(e -> selectAllRows()); + btnSelectNone.addActionListener(_ -> deselectAllRows()); + btnSelectFound.addActionListener(_ -> selectRowsWithFoundSubtitles()); + btnSelectAll.addActionListener(_ -> selectAllRows()); } private void createComponents() { scrollPane = new JScrollPane(); - btnSelectNone = new JButton(Messages.getString("ResultPanel.SelectNothing")); - btnSelectFound = new JButton(Messages.getString("ResultPanel.SelectFound")); - btnSelectAll = new JButton(Messages.getString("ResultPanel.SelectEverything")); - btnDownload = new JButton(Messages.getString("ResultPanel.DownloadSelected")); - btnMove = new JButton(Messages.getString("ResultPanel.MoveSelected")); + btnSelectNone = new JButton(getText("ResultPanel.SelectNothing")); + btnSelectFound = new JButton(getText("ResultPanel.SelectFound")); + btnSelectAll = new JButton(getText("ResultPanel.SelectEverything")); + btnDownload = new JButton(getText("ResultPanel.DownloadSelected")); + btnMove = new JButton(getText("ResultPanel.MoveSelected")); } private void setEnableButtons(boolean enabled) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchFileInputPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchFileInputPanel.java index b5a8f0ce..a524af2e 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchFileInputPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchFileInputPanel.java @@ -1,22 +1,14 @@ package org.lodder.subtools.multisubdownloader.gui.panels; -import java.io.Serial; - -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JTextField; - -import org.lodder.subtools.multisubdownloader.Messages; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; +import static org.lodder.subtools.multisubdownloader.Messages.*; +import javax.swing.*; import java.awt.event.ActionListener; +import java.io.Serial; -import lombok.experimental.ExtensionMethod; import net.miginfocom.swing.MigLayout; -@ExtensionMethod({ JTextFieldExtension.class }) -public class SearchFileInputPanel extends InputPanel { +public final class SearchFileInputPanel extends InputPanel { @Serial private static final long serialVersionUID = 6522020963519514345L; @@ -34,23 +26,23 @@ public SearchFileInputPanel() { } private void addComponentsToPanel() { - add(new JLabel(Messages.getString("MainWindow.LocationNewEpisodes")), "cell 1 0,alignx trailing"); + add(new JLabel(getText("MainWindow.LocationNewEpisodes")), "cell 1 0,alignx trailing"); add(txtIncomingPath, "cell 2 0,alignx leading"); add(btnBrowse, "cell 3 0"); add(chkRecursive, "cell 2 1 2 1"); add(chkForceSubtitleOverwrite, "cell 2 3 2 1"); - add(getSearchButton(), "cell 0 5 3 1,alignx center"); - add(new JLabel(Messages.getString("MainWindow.SelectSubtitleLanguage")), "cell 2 2"); - add(getLanguageCbx(), "cell 3 2"); + add(searchButton, "cell 0 5 3 1,alignx center"); + add(new JLabel(getText("MainWindow.SelectSubtitleLanguage")), "cell 2 2"); + add(languageCbx, "cell 3 2"); } private void createComponents() { - txtIncomingPath = new JTextField().withColumns(20); + txtIncomingPath = new JTextField().columns(20); - chkRecursive = new JCheckBox(Messages.getString("MainWindow.RecursiveSearch")); - chkForceSubtitleOverwrite = new JCheckBox(Messages.getString("MainWindow.ignoreExistingSubtitles")); + chkRecursive = new JCheckBox(getText("MainWindow.RecursiveSearch")); + chkForceSubtitleOverwrite = new JCheckBox(getText("MainWindow.ignoreExistingSubtitles")); - btnBrowse = new JButton(Messages.getString("App.Browse")); + btnBrowse = new JButton(getText("App.Browse")); } public void setRecursiveSelected(boolean selected) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchPanel.java index 4c244197..9b895af8 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchPanel.java @@ -1,19 +1,18 @@ package org.lodder.subtools.multisubdownloader.gui.panels; -import javax.swing.JPanel; - +import javax.swing.*; import java.io.Serial; -import lombok.Getter; +import manifold.ext.props.rt.api.val; import net.miginfocom.swing.MigLayout; -@Getter public class SearchPanel extends JPanel { @Serial private static final long serialVersionUID = -7602822323779710089L; - private final ResultPanel resultPanel; - private final I inputPanel; + + @val ResultPanel resultPanel; + @val I inputPanel; public SearchPanel(I inputPanel, ResultPanel resultPanel) { this.inputPanel = inputPanel; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchTextInputPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchTextInputPanel.java index 950f7130..eea40fb0 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchTextInputPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/SearchTextInputPanel.java @@ -1,30 +1,24 @@ package org.lodder.subtools.multisubdownloader.gui.panels; -import java.io.Serial; - -import javax.swing.JLabel; -import javax.swing.JTextField; +import static org.lodder.subtools.multisubdownloader.Messages.*; +import static org.lodder.subtools.sublibrary.model.VideoSearchType.*; -import org.lodder.subtools.multisubdownloader.Messages; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextcomponent.JTextComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; -import org.lodder.subtools.sublibrary.model.VideoSearchType; +import javax.swing.*; +import java.io.Serial; -import lombok.experimental.ExtensionMethod; import net.miginfocom.swing.MigLayout; +import org.lodder.subtools.sublibrary.model.VideoSearchType; -@ExtensionMethod({ JTextFieldExtension.class, JComponentExtension.class, JTextComponentExtension.class }) -public class SearchTextInputPanel extends InputPanel { +public final class SearchTextInputPanel extends InputPanel { @Serial private static final long serialVersionUID = 7030171360517948253L; - private MyComboBox cbxVideoType; - protected JTextField txtInputSeason; - protected JTextField txtInputEpisode; - protected JTextField txtQualityVersion; + + private JComboBox cbxVideoType; private JTextField txtInputVideoName; + private JTextField txtInputSeason; + private JTextField txtInputEpisode; + private JTextField txtQualityVersion; public SearchTextInputPanel() { super(); @@ -38,54 +32,38 @@ public SearchTextInputPanel() { private void addComponentsToPanel() { this.add(cbxVideoType, "cell 1 0,growx"); this.add(txtInputVideoName, "cell 2 0 5 1,growx"); - this.add(new JLabel(Messages.getString("MainWindow.QualityVersion")), "cell 1 1,alignx trailing"); + this.add(new JLabel(getText("MainWindow.QualityVersion")), "cell 1 1,alignx trailing"); this.add(txtQualityVersion, "cell 2 1,growx"); - this.add(new JLabel(Messages.getString("App.Season")), "cell 3 1,alignx trailing"); + this.add(new JLabel(getText("App.Season")), "cell 3 1,alignx trailing"); this.add(txtInputSeason, "cell 4 1,alignx left"); - this.add(new JLabel(Messages.getString("App.Episode")), "cell 5 1,alignx trailing"); + this.add(new JLabel(getText("App.Episode")), "cell 5 1,alignx trailing"); this.add(txtInputEpisode, "cell 6 1,growx"); - this.add(new JLabel(Messages.getString("MainWindow.SelectSubtitleLanguage")), "cell 1 2 3 1,alignx trailing"); - this.add(getLanguageCbx(), "cell 4 2 2 1,growx"); - this.add(getSearchButton(), "cell 2 4 2 1"); + this.add(new JLabel(getText("MainWindow.SelectSubtitleLanguage")), "cell 1 2 3 1,alignx trailing"); + this.add(languageCbx, "cell 4 2 2 1,growx"); + this.add(searchButton, "cell 2 4 2 1"); } private void setupListeners() { - cbxVideoType.addItemListener(arg0 -> videoTypeChanged()); + cbxVideoType.addItemListener(_ -> videoTypeChanged()); } private void createComponents() { - cbxVideoType = new MyComboBox<>(VideoSearchType.values()) - .withToMessageStringRenderer(VideoSearchType::getMsgCode); - - txtInputVideoName = new JTextField().withColumns(10); - - txtQualityVersion = new JTextField().withColumns(10); - - txtInputSeason = new JTextField().withColumns(10); - - txtInputEpisode = new JTextField().withColumns(10); + cbxVideoType = new JComboBox<>(values()).toStringRenderer(t -> getText(t.msgCode)); + txtInputVideoName = new JTextField().columns(10); + txtQualityVersion = new JTextField().columns(10); + txtInputSeason = new JTextField().columns(10); + txtInputEpisode = new JTextField().columns(10); } private void videoTypeChanged() { - VideoSearchType videoTypeChoice = cbxVideoType.getSelectedItem(); - if (VideoSearchType.EPISODE == videoTypeChoice) { - txtInputSeason.editable(true).withEnabled(true); - txtInputEpisode.editable(true).withEnabled(true); - } else { - txtInputSeason.editable(false).withEnabled(false); - txtInputEpisode.editable(false).withEnabled(false); - } - if (VideoSearchType.RELEASE == videoTypeChoice) { - txtQualityVersion.editable(false).withEnabled(false); - txtQualityVersion.editable(false).withEnabled(false); - } else { - txtQualityVersion.editable(true).withEnabled(true); - txtQualityVersion.editable(true).withEnabled(true); - } + VideoSearchType videoTypeChoice = cbxVideoType.getSelectedValue(); + txtInputSeason.editable(videoTypeChoice == EPISODE).enabled(videoTypeChoice == EPISODE); + txtInputEpisode.editable(videoTypeChoice == EPISODE).enabled(videoTypeChoice == EPISODE); + txtQualityVersion.editable(videoTypeChoice == RELEASE).enabled(videoTypeChoice == RELEASE); } public VideoSearchType getType() { - return cbxVideoType.getSelectedItem(); + return cbxVideoType.getSelectedValue(); } public int getSeason() { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/DefaultSelectionPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/DefaultSelectionPanel.java index f98a8e29..b016554c 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/DefaultSelectionPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/DefaultSelectionPanel.java @@ -2,37 +2,25 @@ import static java.util.function.Predicate.*; -import java.util.Arrays; +import javax.swing.*; +import javax.swing.table.*; +import java.awt.*; +import java.io.Serial; import java.util.Collection; import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.SwingConstants; -import javax.swing.table.DefaultTableModel; - +import net.miginfocom.swing.MigLayout; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.gui.extra.ArrowButton; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jscrollpane.JScrollPaneExtension; import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.sublibrary.control.VideoPatterns.Source; -import java.awt.Component; -import java.awt.Container; - -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ Arrays.class, JComponentExtension.class, AbstractButtonExtension.class, JScrollPaneExtension.class }) public class DefaultSelectionPanel extends JPanel implements PreferencePanelIntf { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; + private final SettingsControl settingsCtrl; private final ScrollTable unusedPatternsTable; private final ScrollTable usedPatternsTable; @@ -41,20 +29,23 @@ public DefaultSelectionPanel(SettingsControl settingsCtrl) { super(new MigLayout("fill, bottom, insets 0", "[grow][][grow][]", "[grow, bottom][grow, top]")); this.settingsCtrl = settingsCtrl; - unusedPatternsTable = ScrollTable.create(Messages.getString("PreferenceDialog.DefaultSelectionUnused"), Source.class).add(this, "spany 2"); - new ArrowButton(SwingConstants.EAST, 1, 10).withActionListener(this::addPattern).addTo(this); - usedPatternsTable = ScrollTable.create(Messages.getString("PreferenceDialog.DefaultSelectionUsed"), Source.class).add(this, "spany 2"); - new ArrowButton(SwingConstants.NORTH, 1, 10).withActionListener(this::moveRuleRowUp).addTo(this, "wrap"); + unusedPatternsTable = + ScrollTable.create(Messages.getText("PreferenceDialog.DefaultSelectionUnused"), Source.class) + .add(this, "spany 2"); + new ArrowButton(SwingConstants.EAST, 1, 10).actionListener(this::addPattern).addTo(this); + usedPatternsTable = ScrollTable.create(Messages.getText("PreferenceDialog.DefaultSelectionUsed"), Source.class) + .add(this, "spany 2"); + new ArrowButton(SwingConstants.NORTH, 1, 10).actionListener(this::moveRuleRowUp).addTo(this, "wrap"); - new ArrowButton(SwingConstants.WEST, 1, 10).withActionListener(this::removePattern).addTo(this, "skip"); - new ArrowButton(SwingConstants.SOUTH, 1, 10).withActionListener(this::moveRuleRowDown).addTo(this, "skip"); + new ArrowButton(SwingConstants.WEST, 1, 10).actionListener(this::removePattern).addTo(this, "skip"); + new ArrowButton(SwingConstants.SOUTH, 1, 10).actionListener(this::moveRuleRowDown).addTo(this, "skip"); loadPreferenceSettings(); } private static class ScrollTable extends Container { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private final JScrollPane scrollPane; private final JTable table; @@ -73,8 +64,8 @@ public ScrollTable(String header, Collection items) { } private ScrollTable(String header, Stream items) { - this.table = new JTable(new DefaultTableModel(new String[] { header }, 1)); - this.scrollPane = new JScrollPane().withViewportView(table); + this.table = new JTable(new DefaultTableModel(new String[]{ header }, 1)); + this.scrollPane = new JScrollPane().viewportView(table); this.model = (DefaultTableModel) table.getModel(); model.removeRow(0); if (items != null) { @@ -84,7 +75,7 @@ private ScrollTable(String header, Stream items) { } public void addItem(E item) { - model.addRow(new Object[] { item }); + model.addRow(new Object[]{ item }); } public int getSelectedRow() { @@ -145,7 +136,7 @@ public List getItems() { @Override public Component[] getComponents() { - return new Component[] { scrollPane, table }; + return new Component[]{ scrollPane, table }; } @Override @@ -188,13 +179,13 @@ protected void moveRuleRowUp() { } public void loadPreferenceSettings() { - Source.values().stream().filter(not(settingsCtrl.getSettings().getOptionsDefaultSelectionQualityList()::contains)) - .forEach(unusedPatternsTable::addItem); - settingsCtrl.getSettings().getOptionsDefaultSelectionQualityList().forEach(usedPatternsTable::addItem); + Source.values().stream().filter(not(settingsCtrl.settings.optionsDefaultSelectionQualityList::contains)) + .forEach(unusedPatternsTable::addItem); + settingsCtrl.settings.optionsDefaultSelectionQualityList.forEach(usedPatternsTable::addItem); } public void savePreferenceSettings() { - settingsCtrl.getSettings().setOptionsDefaultSelectionQualityList(usedPatternsTable.getItems()); + settingsCtrl.settings.optionsDefaultSelectionQualityList = usedPatternsTable.getItems(); } @Override diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/EpisodeLibraryPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/EpisodeLibraryPanel.java index b5abd356..0e42f6fc 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/EpisodeLibraryPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/EpisodeLibraryPanel.java @@ -7,7 +7,7 @@ import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -public class EpisodeLibraryPanel extends VideoLibraryPanel { +public final class EpisodeLibraryPanel extends VideoLibraryPanel { @Serial private static final long serialVersionUID = -9175813173306481849L; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/GeneralPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/GeneralPanel.java index ec589949..b62c45fb 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/GeneralPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/GeneralPanel.java @@ -1,30 +1,23 @@ package org.lodder.subtools.multisubdownloader.gui.panels.preference; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; import java.io.Serial; import java.nio.file.Path; import java.util.List; import java.util.function.Consumer; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - +import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.GUI; import org.lodder.subtools.multisubdownloader.Messages; +import org.lodder.subtools.multisubdownloader.gui.extra.BoxModelProperties; import org.lodder.subtools.multisubdownloader.gui.extra.JListWithImages; import org.lodder.subtools.multisubdownloader.gui.extra.JListWithImages.LabelPanel; import org.lodder.subtools.multisubdownloader.gui.extra.MemoryFolderChooser; import org.lodder.subtools.multisubdownloader.gui.extra.PanelCheckBox; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldInteger; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldString; import org.lodder.subtools.multisubdownloader.settings.SettingsControl; @@ -34,23 +27,17 @@ import org.lodder.subtools.multisubdownloader.settings.model.UpdateType; import org.lodder.subtools.sublibrary.Language; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JTextFieldExtension.class, JCheckBoxExtension.class, JComponentExtension.class, AbstractButtonExtension.class }) public class GeneralPanel extends JPanel implements PreferencePanelIntf { - @Serial - private static final long serialVersionUID = -5458593307643063563L; + @Serial private static final long serialVersionUID = -5458593307643063563L; private final GUI gui; private final SettingsControl settingsCtrl; - - private final MyComboBox cbxLanguage; + private final JComboBox cbxLanguage; private final JListWithImages defaultIncomingFoldersList; private final JListWithImages excludeList; - private final MyComboBox cbxUpdateCheckPeriod; - private final MyComboBox cbxUpdateType; + private final JComboBox cbxUpdateCheckPeriod; + private final JComboBox cbxUpdateType; private final JCheckBox chkUseProxy; private final MyTextFieldString txtProxyHost; private final MyTextFieldInteger txtProxyPort; @@ -60,129 +47,149 @@ public GeneralPanel(GUI gui, SettingsControl settingsCtrl) { this.gui = gui; this.settingsCtrl = settingsCtrl; - JPanel settingsPanel = TitlePanel.title(Messages.getString("PreferenceDialog.Settings")) - .padding(0).paddingLeft(20).useGrid().fillContents(false).addTo(this, "span, grow, wrap"); + JPanel settingsPanel = new TitlePanel( + title:getText("PreferenceDialog.Settings"), + padding:new BoxModelProperties(0, 20, 0, 0), + useGrid:true, + fillContents:false) + .addToPanel(this, "span, grow, wrap"); { - { - new JLabel(Messages.getString("PreferenceDialog.Language")).addTo(settingsPanel); - - this.cbxLanguage = new MyComboBox<>(Messages.getAvailableLanguages(), Language.class) - .withToMessageStringRenderer(Language::getMsgCode) - .addTo(settingsPanel, "wrap"); - } - - { - new JLabel(Messages.getString("PreferenceDialog.DefaultIncomingFolder")).addTo(settingsPanel, "aligny center, span 1 2"); - - new JScrollPane().addTo(settingsPanel, "growx, span, wrap") - .setViewportView(this.defaultIncomingFoldersList = JListWithImages.createForType(Path.class).distinctValues().build()); - - new JButton(Messages.getString("PreferenceDialog.AddFolder")) - .withActionListener( - () -> MemoryFolderChooser.getInstance() - .selectDirectory(settingsPanel, Messages.getString("PreferenceDialog.SelectFolder")) - .map(Path::toAbsolutePath) - .filter(path -> !defaultIncomingFoldersList.contains(path)) - .ifPresent(path -> defaultIncomingFoldersList.addItem(PathMatchType.FOLDER.getImage(), path))) - .addTo(settingsPanel, "span, split 2"); - - new JButton(Messages.getString("PreferenceDialog.DeleteFolder")) - .withActionListener(defaultIncomingFoldersList::removeSelectedItem) - .addTo(settingsPanel, "wrap, gapbottom 10px"); - } - { - new JLabel(Messages.getString("PreferenceDialog.ExcludeList")).addTo(settingsPanel, "aligny center, span 1 2"); - - new JScrollPane().addTo(settingsPanel, "growx, span, wrap") - .setViewportView(this.excludeList = JListWithImages.createForType(PathOrRegex.class).distinctValues().build()); - - Consumer addExcludeItemConsumer = type -> { - if (type == PathMatchType.FOLDER) { - MemoryFolderChooser.getInstance().selectDirectory(settingsPanel, Messages.getString("PreferenceDialog.SelectExcludeFolder")) - .map(Path::toAbsolutePath).map(PathOrRegex::new) - .ifPresent(pathOrRegex -> excludeList.addItem(pathOrRegex.getImage(), pathOrRegex)); - } else if (type == PathMatchType.REGEX) { - String regex = JOptionPane.showInputDialog(Messages.getString("PreferenceDialog.EnterRegex")); - if (StringUtils.isNotBlank(regex)) { - excludeList.addItem(PathMatchType.REGEX.getImage(), new PathOrRegex(regex)); - } + // Language \\ + + new JLabel(getText("PreferenceDialog.Language")).addTo(settingsPanel); + this.cbxLanguage = JComboBox.create(getAvailableLanguages()) + .toMessageStringRenderer(Language::getMsgCode) + .addTo(settingsPanel, "wrap"); + + // Default Incoming Folder \\ + + new JLabel(getText("PreferenceDialog.DefaultIncomingFolder")).addTo(settingsPanel, + "aligny center, span 1 2"); + + new JScrollPane() + .viewportView(this.defaultIncomingFoldersList = new JListWithImages<>()) + .addTo(settingsPanel, "growx, span, wrap"); + + new JButton(getText("PreferenceDialog.AddFolder")) + .actionListener(() -> MemoryFolderChooser.getInstance() + .selectDirectory(settingsPanel, getText("PreferenceDialog.SelectFolder")) + .map(Path::toAbsolutePath) + .filter(path -> !defaultIncomingFoldersList.contains(path)) + .ifPresent(p -> defaultIncomingFoldersList.addItem(PathMatchType.FOLDER.image, p))) + .addTo(settingsPanel, "span, split 2"); + + new JButton(getText("PreferenceDialog.DeleteFolder")) + .actionListener(defaultIncomingFoldersList::removeSelectedItem) + .addTo(settingsPanel, "wrap, gapbottom 10px"); + + // Exclude List \\ + + new JLabel(getText("PreferenceDialog.ExcludeList")) + .addTo(settingsPanel, "aligny center, span 1 2"); + + new JScrollPane() + .viewportView(this.excludeList = new JListWithImages<>()) + .addTo(settingsPanel, "growx, span, wrap"); + + Consumer addExcludeItemConsumer = type -> { + if (type == PathMatchType.FOLDER) { + MemoryFolderChooser.getInstance() + .selectDirectory(settingsPanel, getText("PreferenceDialog.SelectExcludeFolder")) + .map(Path::toAbsolutePath) + .map(PathOrRegex::new) + .ifPresent(pathOrRegex -> excludeList.addItem(pathOrRegex.image, pathOrRegex)); + } else if (type == PathMatchType.REGEX) { + String regex = JOptionPane.showInputDialog(getText("PreferenceDialog.EnterRegex")); + if (StringUtils.isNotBlank(regex)) { + excludeList.addItem(PathMatchType.REGEX.image, new PathOrRegex(regex)); } - }; + } + }; - new JButton(Messages.getString("PreferenceDialog.AddFolder")) - .withActionListener(() -> addExcludeItemConsumer.accept(PathMatchType.FOLDER)) - .addTo(settingsPanel, "span, split 3"); + new JButton(getText("PreferenceDialog.AddFolder")) + .actionListener(() -> addExcludeItemConsumer.accept(PathMatchType.FOLDER)) + .addTo(settingsPanel, "span, split 3"); - new JButton(Messages.getString("PreferenceDialog.DeleteFolder")) - .withActionListener(excludeList::removeSelectedItem) - .addTo(settingsPanel); + new JButton(getText("PreferenceDialog.DeleteFolder")) + .actionListener(excludeList::removeSelectedItem) + .addTo(settingsPanel); - new JButton(Messages.getString("PreferenceDialog.RegexToevoegen")) - .withActionListener(() -> addExcludeItemConsumer.accept(PathMatchType.REGEX)) - .addTo(settingsPanel); - } + new JButton(getText("PreferenceDialog.RegexToevoegen")) + .actionListener(() -> addExcludeItemConsumer.accept(PathMatchType.REGEX)) + .addTo(settingsPanel); } { - JPanel updatePanel = TitlePanel.title(Messages.getString("PreferenceDialog.Update")) - .padding(0).paddingLeft(20).useGrid().fillContents(false).addTo(this, "span, grow, wrap"); - { - new JLabel(Messages.getString("PreferenceDialog.NewUpdateCheck")).addTo(updatePanel); - this.cbxUpdateCheckPeriod = new MyComboBox<>(UpdateCheckPeriod.values()) - .withToMessageStringRenderer(UpdateCheckPeriod::getLangCode) - .addTo(updatePanel, "wrap"); - new JLabel(Messages.getString("PreferenceDialog.UpdateType")).addTo(updatePanel); - this.cbxUpdateType = new MyComboBox<>(UpdateType.values()) - .withToMessageStringRenderer(UpdateType::getMsgCode).addTo(updatePanel); - } + JPanel updatePanel = new TitlePanel( + title:getText("PreferenceDialog.Update"), + padding:new BoxModelProperties(0, 20, 0, 0), + useGrid:true, + fillContents:false) + .addToPanel(this, "span, grow, wrap"); + + new JLabel(getText("PreferenceDialog.NewUpdateCheck")).addTo(updatePanel); + this.cbxUpdateCheckPeriod = new JComboBox<>(UpdateCheckPeriod.values()) + .toMessageStringRenderer(UpdateCheckPeriod::getLangCode) + .addTo(updatePanel, "wrap"); + new JLabel(getText("PreferenceDialog.UpdateType")).addTo(updatePanel); + this.cbxUpdateType = new JComboBox<>(UpdateType.values()) + .toMessageStringRenderer(UpdateType::getMsgCode) + .addTo(updatePanel); } { - JPanel proxyPanel = TitlePanel.title(Messages.getString("PreferenceDialog.ConfigureProxy")) - .padding(0).paddingLeft(20).fillContents(false).addTo(this, "span, grow"); - - PanelCheckBox.checkbox(this.chkUseProxy = new JCheckBox(Messages.getString("PreferenceDialog.UseProxyServer"))) - .panelOnSameLine().panelLayout(new MigLayout("insets 0, fill")).leftGap(0).addTo(proxyPanel) - .addComponent(new JLabel(Messages.getString("PreferenceDialog.Hostname"))) - .addComponent("wrap", this.txtProxyHost = MyTextFieldString.builder().requireValue().build().withColumns(30)) - .addComponent(new JLabel(Messages.getString("PreferenceDialog.Port"))) - .addComponent(this.txtProxyPort = MyTextFieldInteger.builder().requireValue().build().withColumns(5)); + JPanel proxyPanel = new TitlePanel( + title:getText("PreferenceDialog.ConfigureProxy"), + padding:new BoxModelProperties(0, 20, 0, 0), + fillContents:false) + .addToPanel(this, "span, grow"); + + new PanelCheckBox( + checkbox:this.chkUseProxy = new JCheckBox(getText("PreferenceDialog.UseProxyServer")), + panelOnNewLine:false, + panelLayout:new MigLayout("insets 0, fill") + ) + .addToPanel(proxyPanel) + .addComponent(new JLabel(getText("PreferenceDialog.Hostname"))) + .addComponent("wrap", + this.txtProxyHost = MyTextFieldString.builder().requireValue().build().columns(30)) + .addComponent(new JLabel(getText("PreferenceDialog.Port"))) + .addComponent(this.txtProxyPort = MyTextFieldInteger.builder().requireValue().build().columns(5)); } loadPreferenceSettings(); } public void loadPreferenceSettings() { - cbxLanguage.setSelectedItem(settingsCtrl.getSettings().getLanguage()); - defaultIncomingFoldersList.addItems(PathMatchType.FOLDER.getImage(), settingsCtrl.getSettings().getDefaultIncomingFolders()); - settingsCtrl.getSettings().getExcludeList().forEach(pathOrRegex -> excludeList.addItem(pathOrRegex.getImage(), pathOrRegex)); - cbxUpdateCheckPeriod.setSelectedItem(settingsCtrl.getSettings().getUpdateCheckPeriod()); - cbxUpdateType.setSelectedItem(settingsCtrl.getSettings().getUpdateType()); - chkUseProxy.setSelected(settingsCtrl.getSettings().isGeneralProxyEnabled()); - txtProxyHost.setText(settingsCtrl.getSettings().getGeneralProxyHost()); - txtProxyPort.setObject(settingsCtrl.getSettings().getGeneralProxyPort()); + cbxLanguage.setSelectedItem(settingsCtrl.settings.language); + defaultIncomingFoldersList.addItems(PathMatchType.FOLDER.image, settingsCtrl.settings.defaultIncomingFolders); + settingsCtrl.settings.excludeList.forEach(pathOrRegex -> excludeList.addItem(pathOrRegex.image, pathOrRegex)); + cbxUpdateCheckPeriod.setSelectedItem(settingsCtrl.settings.updateCheckPeriod); + cbxUpdateType.setSelectedItem(settingsCtrl.settings.updateType); + chkUseProxy.setSelected(settingsCtrl.settings.generalProxyEnabled); + txtProxyHost.setText(settingsCtrl.settings.generalProxyHost); + txtProxyPort.setObject(settingsCtrl.settings.generalProxyPort); } public void savePreferenceSettings() { - if (Messages.getLanguage() != cbxLanguage.getSelectedItem()) { - Messages.setLanguage(cbxLanguage.getSelectedItem()); + if (Messages.language != cbxLanguage.getSelectedValue()) { + Messages.language = cbxLanguage.getSelectedValue(); gui.redraw(); } List defaultIncomingFolders = defaultIncomingFoldersList.stream().map(LabelPanel::getObject).toList(); - List exclList = excludeList.stream().map(labelPanel -> new PathOrRegex(labelPanel.getObject().getValue())).toList(); - settingsCtrl.getSettings() - .setLanguage(cbxLanguage.getSelectedItem()) - .setDefaultIncomingFolders(defaultIncomingFolders) - .setExcludeList(exclList) - .setUpdateCheckPeriod(cbxUpdateCheckPeriod.getSelectedItem()) - .setUpdateType(cbxUpdateType.getSelectedItem()) - .setGeneralProxyEnabled(chkUseProxy.isSelected()) - .setGeneralProxyHost(txtProxyHost.getText()) - .setGeneralProxyPort(txtProxyPort.getOptionalObject().orElse(80)); - + List exclList = + excludeList.stream().map(labelPanel -> new PathOrRegex(labelPanel.getObject().value)).toList(); + settingsCtrl.settings.language = cbxLanguage.getSelectedValue(); + settingsCtrl.settings.defaultIncomingFolders = defaultIncomingFolders; + settingsCtrl.settings.replaceExcludeList(exclList); + settingsCtrl.settings.updateCheckPeriod = cbxUpdateCheckPeriod.getSelectedValue(); + settingsCtrl.settings.updateType = cbxUpdateType.getSelectedValue(); + settingsCtrl.settings.generalProxyEnabled = chkUseProxy.isSelected(); + settingsCtrl.settings.generalProxyHost = txtProxyHost.getText(); + settingsCtrl.settings.generalProxyPort = txtProxyPort.getOptionalObject().orElse(80); } @Override diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/MovieLibraryPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/MovieLibraryPanel.java index ed27d4a2..5dcef703 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/MovieLibraryPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/MovieLibraryPanel.java @@ -7,7 +7,7 @@ import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -public class MovieLibraryPanel extends VideoLibraryPanel { +public final class MovieLibraryPanel extends VideoLibraryPanel { @Serial private static final long serialVersionUID = -9175813173306481849L; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/OptionsPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/OptionsPanel.java index afdf65e9..6ad90c7f 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/OptionsPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/OptionsPanel.java @@ -1,33 +1,22 @@ package org.lodder.subtools.multisubdownloader.gui.panels.preference; -import java.io.Serial; +import static org.lodder.subtools.multisubdownloader.Messages.*; -import javax.swing.JCheckBox; -import javax.swing.JPanel; -import javax.swing.JSlider; +import javax.swing.*; +import java.io.Serial; -import org.lodder.subtools.multisubdownloader.Messages; +import net.miginfocom.swing.MigLayout; +import org.lodder.subtools.multisubdownloader.gui.extra.BoxModelProperties; import org.lodder.subtools.multisubdownloader.gui.extra.PanelCheckBox; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jslider.JSliderExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.multisubdownloader.settings.model.SettingsProcessEpisodeSource; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JTextFieldExtension.class, JCheckBoxExtension.class, JComponentExtension.class, JSliderExtension.class }) public class OptionsPanel extends JPanel implements PreferencePanelIntf { - @Serial - private static final long serialVersionUID = -5458593307643063563L; + @Serial private static final long serialVersionUID = -5458593307643063563L; private final SettingsControl settingsCtrl; - private final JCheckBox chkAlwaysConfirm; private final JCheckBox chkMinScoreSelection; private final JSlider sldMinScoreSelection; @@ -38,74 +27,101 @@ public class OptionsPanel extends JPanel implements PreferencePanelIntf { private final JCheckBox chkExcludeHearingImpaired; private final JCheckBox chkOnlyFound; private final JCheckBox chkStopOnSearchError; - private final MyComboBox cbxEpisodeProcessSource; + private final JComboBox cbxEpisodeProcessSource; private final JCheckBox chkConfirmProviderMapping; public OptionsPanel(SettingsControl settingsCtrl) { super(new MigLayout("insets 0, fill, nogrid")); this.settingsCtrl = settingsCtrl; - TitlePanel.title(Messages.getString("PreferenceDialog.DownloadOptions")) - .marginBottom(0).padding(0).paddingLeft(20).addTo(this, "span, grow, wrap") - .addComponent(this.chkAlwaysConfirm = new JCheckBox(Messages.getString("PreferenceDialog.CheckBeforeDownloading")), "wrap") - .addComponent("wrap, grow", PanelCheckBox - .checkbox(this.chkMinScoreSelection = new JCheckBox(Messages.getString("PreferenceDialog.MinAutomaticScoreSelection"))) - .panelOnSameLine().build() - .addComponent(this.sldMinScoreSelection = new JSlider().withMinimum(0).withMaximum(100), "wrap")) - .addComponent("wrap, grow", PanelCheckBox - .checkbox(this.chkDefaultSelection = new JCheckBox(Messages.getString("PreferenceDialog.DefaultSelection"), null, true)) - .panelOnNewLine().build() - .addComponent(this.pnlDefaultSelection = new DefaultSelectionPanel(settingsCtrl))); - - TitlePanel.title(Messages.getString("PreferenceDialog.SearchFilter")) - .marginBottom(0).padding(0).paddingLeft(20).addTo(this, "span, grow, wrap") - .addComponent(this.chkSubtitleExactMethod = new JCheckBox(Messages.getString("PreferenceDialog.SearchFilterExact")), "wrap") - .addComponent(this.chkSubtitleKeywordMethod = new JCheckBox(Messages.getString("PreferenceDialog.SearchFilterKeyword")), "wrap") - .addComponent(this.chkExcludeHearingImpaired = new JCheckBox(Messages.getString("PreferenceDialog.ExcludeHearingImpaired"))); - - TitlePanel.title(Messages.getString("PreferenceDialog.TableOptions")) - .marginBottom(0).padding(0).paddingLeft(20).addTo(this, "span, grow, wrap") - .addComponent(this.chkOnlyFound = new JCheckBox(Messages.getString("PreferenceDialog.ShowOnlyFound"))); - - TitlePanel.title(Messages.getString("PreferenceDialog.ErrorHandlingOption")) - .marginBottom(0).padding(0).paddingLeft(20).addTo(this, "span, grow, wrap") - .addComponent(this.chkStopOnSearchError = new JCheckBox(Messages.getString("PreferenceDialog.StopAfterError"))); - - TitlePanel.title(Messages.getString("PreferenceDialog.SerieDatabaseSource")) - .marginBottom(0).padding(0).paddingLeft(20).addTo(this, "span, grow") - .addComponent(this.cbxEpisodeProcessSource = MyComboBox.ofValues(SettingsProcessEpisodeSource.values()), "wrap") - .addComponent(this.chkConfirmProviderMapping = new JCheckBox(Messages.getString("PreferenceDialog.ConfirmProviderMapping"))); + new TitlePanel( + title:getText("PreferenceDialog.DownloadOptions"), + margin:new BoxModelProperties(null, null, 0, null), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, grow, wrap") + .addComponent(this.chkAlwaysConfirm = + new JCheckBox(getText("PreferenceDialog.CheckBeforeDownloading")), "wrap") + .addComponent("wrap, grow", + new PanelCheckBox( + checkbox:this.chkMinScoreSelection = + new JCheckBox(getText("PreferenceDialog.MinAutomaticScoreSelection")), + panelOnNewLine:false + ) + .addComponent(this.sldMinScoreSelection = new JSlider().minimum(0).maximum(100), "wrap")) + .addComponent("wrap, grow", + new PanelCheckBox( + checkbox:this.chkDefaultSelection = + new JCheckBox(getText("PreferenceDialog.DefaultSelection"), null, true), + panelOnNewLine:true + ) + .addComponent(this.pnlDefaultSelection = new DefaultSelectionPanel(settingsCtrl))); + + new TitlePanel( + title:getText("PreferenceDialog.SearchFilter"), + margin:new BoxModelProperties(null, null, 0, null), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, grow, wrap") + .addComponent( + this.chkSubtitleExactMethod = new JCheckBox(getText("PreferenceDialog.SearchFilterExact")), + "wrap") + .addComponent(this.chkSubtitleKeywordMethod = + new JCheckBox(getText("PreferenceDialog.SearchFilterKeyword")), "wrap") + .addComponent(this.chkExcludeHearingImpaired = + new JCheckBox(getText("PreferenceDialog.ExcludeHearingImpaired"))); + + new TitlePanel( + title:getText("PreferenceDialog.TableOptions"), + margin:new BoxModelProperties(null, null, 0, null), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, grow, wrap") + .addComponent(this.chkOnlyFound = new JCheckBox(getText("PreferenceDialog.ShowOnlyFound"))); + + new TitlePanel( + title:getText("PreferenceDialog.ErrorHandlingOption"), + margin:new BoxModelProperties(null, null, 0, null), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, grow, wrap") + .addComponent(this.chkStopOnSearchError = new JCheckBox(getText("PreferenceDialog.StopAfterError"))); + + new TitlePanel( + title:getText("PreferenceDialog.SerieDatabaseSource"), + margin:new BoxModelProperties(null, null, 0, null), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, grow") + .addComponent(this.cbxEpisodeProcessSource = new JComboBox<>(SettingsProcessEpisodeSource.values()), + "wrap") + .addComponent(this.chkConfirmProviderMapping = + new JCheckBox(getText("PreferenceDialog.ConfirmProviderMapping"))); loadPreferenceSettings(); } public void loadPreferenceSettings() { - chkAlwaysConfirm.setSelected(settingsCtrl.getSettings().isOptionsAlwaysConfirm()); - chkMinScoreSelection.setSelected(settingsCtrl.getSettings().isOptionsMinAutomaticSelection()); - sldMinScoreSelection.setValue(settingsCtrl.getSettings().getOptionsMinAutomaticSelectionValue()); - chkDefaultSelection.setSelected(settingsCtrl.getSettings().isOptionsDefaultSelection()); - chkSubtitleExactMethod.setSelected(settingsCtrl.getSettings().isOptionSubtitleExactMatch()); - chkSubtitleKeywordMethod.setSelected(settingsCtrl.getSettings().isOptionSubtitleKeywordMatch()); - chkExcludeHearingImpaired.setSelected(settingsCtrl.getSettings().isOptionSubtitleExcludeHearingImpaired()); - chkOnlyFound.setSelected(settingsCtrl.getSettings().isOptionsShowOnlyFound()); - chkStopOnSearchError.setSelected(settingsCtrl.getSettings().isOptionsStopOnSearchError()); - cbxEpisodeProcessSource.setSelectedItem(settingsCtrl.getSettings().getProcessEpisodeSource()); - chkConfirmProviderMapping.setSelected(settingsCtrl.getSettings().isOptionsConfirmProviderMapping()); + chkAlwaysConfirm.setSelected(settingsCtrl.settings.optionsAlwaysConfirm); + chkMinScoreSelection.setSelected(settingsCtrl.settings.optionsMinAutomaticSelection); + sldMinScoreSelection.setValue(settingsCtrl.settings.optionsMinAutomaticSelectionValue); + chkDefaultSelection.setSelected(settingsCtrl.settings.optionsDefaultSelection); + chkSubtitleExactMethod.setSelected(settingsCtrl.settings.optionSubtitleExactMatch); + chkSubtitleKeywordMethod.setSelected(settingsCtrl.settings.optionSubtitleKeywordMatch); + chkExcludeHearingImpaired.setSelected(settingsCtrl.settings.optionSubtitleExcludeHearingImpaired); + chkOnlyFound.setSelected(settingsCtrl.settings.optionsShowOnlyFound); + chkStopOnSearchError.setSelected(settingsCtrl.settings.optionsStopOnSearchError); + cbxEpisodeProcessSource.setSelectedItem(settingsCtrl.settings.processEpisodeSource); + chkConfirmProviderMapping.setSelected(settingsCtrl.settings.optionsConfirmProviderMapping); } public void savePreferenceSettings() { - settingsCtrl.getSettings() - .setOptionsAlwaysConfirm(chkAlwaysConfirm.isSelected()) - .setOptionsMinAutomaticSelection(chkMinScoreSelection.isSelected()) - .setOptionsMinAutomaticSelectionValue(sldMinScoreSelection.getValue()) - .setOptionsDefaultSelection(chkDefaultSelection.isSelected()) - .setOptionSubtitleExactMatch(chkSubtitleExactMethod.isSelected()) - .setOptionSubtitleKeywordMatch(chkSubtitleKeywordMethod.isSelected()) - .setOptionSubtitleExcludeHearingImpaired(chkExcludeHearingImpaired.isSelected()) - .setOptionsShowOnlyFound(chkOnlyFound.isSelected()) - .setOptionsStopOnSearchError(chkStopOnSearchError.isSelected()) - .setProcessEpisodeSource(cbxEpisodeProcessSource.getSelectedItem()) - .setOptionsConfirmProviderMapping(chkConfirmProviderMapping.isSelected()); + settingsCtrl.settings.optionsAlwaysConfirm = chkAlwaysConfirm.isSelected(); + settingsCtrl.settings.optionsMinAutomaticSelection = chkMinScoreSelection.isSelected(); + settingsCtrl.settings.optionsMinAutomaticSelectionValue = sldMinScoreSelection.getValue(); + settingsCtrl.settings.optionsDefaultSelection = chkDefaultSelection.isSelected(); + settingsCtrl.settings.optionSubtitleExactMatch = chkSubtitleExactMethod.isSelected(); + settingsCtrl.settings.optionSubtitleKeywordMatch = chkSubtitleKeywordMethod.isSelected(); + settingsCtrl.settings.optionSubtitleExcludeHearingImpaired = chkExcludeHearingImpaired.isSelected(); + settingsCtrl.settings.optionsShowOnlyFound = chkOnlyFound.isSelected(); + settingsCtrl.settings.optionsStopOnSearchError = chkStopOnSearchError.isSelected(); + settingsCtrl.settings.processEpisodeSource = cbxEpisodeProcessSource.getSelectedValue(); + settingsCtrl.settings.optionsConfirmProviderMapping = chkConfirmProviderMapping.isSelected(); pnlDefaultSelection.savePreferenceSettings(); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SerieProvidersPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SerieProvidersPanel.java index 6161d161..8b675691 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SerieProvidersPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SerieProvidersPanel.java @@ -1,40 +1,30 @@ package org.lodder.subtools.multisubdownloader.gui.panels.preference; import static java.util.function.Predicate.*; +import static org.lodder.subtools.multisubdownloader.Messages.*; +import javax.swing.*; import java.io.Serial; import java.nio.file.Path; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - -import org.lodder.subtools.multisubdownloader.Messages; +import net.miginfocom.swing.MigLayout; import org.lodder.subtools.multisubdownloader.gui.extra.JListWithImages; import org.lodder.subtools.multisubdownloader.gui.extra.JListWithImages.LabelPanel; import org.lodder.subtools.multisubdownloader.gui.extra.MemoryFolderChooser; import org.lodder.subtools.multisubdownloader.gui.extra.PanelCheckBox; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyPasswordField; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldString; import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.multisubdownloader.settings.model.PathMatchType; +import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.OpenSubtitlesApi; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JTextFieldExtension.class, JCheckBoxExtension.class, JComponentExtension.class, AbstractButtonExtension.class }) public class SerieProvidersPanel extends JPanel implements PreferencePanelIntf { @Serial private static final long serialVersionUID = -5458593307643063563L; + private final SettingsControl settingsCtrl; private final JCheckBox chkSourceAddic7ed; private final JCheckBox chkUserAddic7edLogin; @@ -47,7 +37,7 @@ public class SerieProvidersPanel extends JPanel implements PreferencePanelIntf { private final JCheckBox chkUserOpenSubtitlesLogin; private final MyTextFieldString txtOpenSubtitlesUsername; private final MyPasswordField txtOpenSubtitlesPassword; - private final JCheckBox chkSourceSubscene; + // private final JCheckBox chkSourceSubscene; private final JCheckBox chkSourceLocal; private final JListWithImages localSourcesFoldersList; @@ -55,99 +45,118 @@ public SerieProvidersPanel(SettingsControl settingsCtrl) { super(new MigLayout("insets 0, fill, nogrid")); this.settingsCtrl = settingsCtrl; - JPanel titelPanel = TitlePanel.title(Messages.getString("PreferenceDialog.SelectPreferredSources")).addTo(this, "span, grow"); + JPanel titlePanel = new TitlePanel(getText("PreferenceDialog.SelectPreferredSources")) + .addToPanel(this, "span, grow"); { // ADDIC7ED this.chkSourceAddic7ed = new JCheckBox("Addic7ed"); - this.chkUserAddic7edLogin = new JCheckBox(Messages.getString("PreferenceDialog.UseAddic7edLogin")); - this.chkSourceAddic7edProxy = new JCheckBox(Messages.getString("PreferenceDialog.Proxy")); - - PanelCheckBox.checkbox(chkSourceAddic7ed).panelOnNewLine().addTo(titelPanel, "wrap") - .addComponent("wrap", chkSourceAddic7edProxy) - .addComponent(PanelCheckBox.checkbox(chkUserAddic7edLogin).panelOnNewLine() - .panelLayout(new MigLayout("insets 0, novisualpadding")).build() - .addComponent(new JLabel(Messages.getString("PreferenceDialog.Username"))) - .addComponent("wrap", this.txtAddic7edUsername = MyTextFieldString.builder().requireValue().build().withColumns(20)) - .addComponent(new JLabel(Messages.getString("PreferenceDialog.Password"))) - .addComponent(this.txtAddic7edPassword = MyPasswordField.builder().requireValue().build().withColumns(20))); + this.chkUserAddic7edLogin = new JCheckBox(getText("PreferenceDialog.UseAddic7edLogin")); + this.chkSourceAddic7edProxy = new JCheckBox(getText("PreferenceDialog.Proxy")); + + new PanelCheckBox(checkbox:chkSourceAddic7ed, panelOnNewLine:true) + .addToPanel(titlePanel, "wrap") + .addComponent("wrap", chkSourceAddic7edProxy) + .addComponent(new PanelCheckBox( + checkbox:chkUserAddic7edLogin, + panelOnNewLine:true, + panelLayout:new MigLayout("insets 0, novisualpadding") + ) + .addComponent(new JLabel(getText("PreferenceDialog.Username"))) + .addComponent("wrap", this.txtAddic7edUsername = + MyTextFieldString.builder().requireValue().build().columns(20)) + .addComponent(new JLabel(getText("PreferenceDialog.Password"))) + .addComponent(this.txtAddic7edPassword = + MyPasswordField.builder().requireValue().build().columns(20))); // TV SUBTITLES - this.chkSourceTvSubtitles = new JCheckBox("Tv Subtitles").addTo(titelPanel, "wrap"); + this.chkSourceTvSubtitles = new JCheckBox("Tv Subtitles").addTo(titlePanel, "wrap"); // PODNAPISI - this.chkSourcePodnapisi = new JCheckBox("Podnapisi").addTo(titelPanel, "wrap"); + this.chkSourcePodnapisi = new JCheckBox("Podnapisi").addTo(titlePanel, "wrap"); // OPENSUBTITLES this.chkSourceOpenSubtitles = new JCheckBox("OpenSubtitles"); - this.chkUserOpenSubtitlesLogin = new JCheckBox(Messages.getString("PreferenceDialog.UseOpenSubtitlesLogin")); - PanelCheckBox.checkbox(chkSourceOpenSubtitles).panelOnNewLine().addTo(titelPanel, "wrap") - .addComponent(PanelCheckBox.checkbox(chkUserOpenSubtitlesLogin).panelOnNewLine() - .panelLayout(new MigLayout("insets 0, novisualpadding")).build() - .addComponent(new JLabel(Messages.getString("PreferenceDialog.Username"))) - .addComponent("wrap", txtOpenSubtitlesUsername = MyTextFieldString.builder().requireValue().build().withColumns(20)) - .addComponent(new JLabel(Messages.getString("PreferenceDialog.Password"))) - .addComponent(txtOpenSubtitlesPassword = MyPasswordField.builder().requireValue().build().withColumns(20))); + this.chkUserOpenSubtitlesLogin = new JCheckBox(getText("PreferenceDialog.UseOpenSubtitlesLogin")); + new PanelCheckBox(checkbox:chkSourceOpenSubtitles, panelOnNewLine:true) + .addTo(titlePanel, "wrap").panel + .addComponent(new PanelCheckBox( + checkbox:chkUserOpenSubtitlesLogin, + panelOnNewLine:true, + panelLayout:new MigLayout("insets 0, novisualpadding") + ) + .addComponent(new JLabel(getText("PreferenceDialog.Username"))) + .addComponent("wrap", txtOpenSubtitlesUsername = + MyTextFieldString.builder().requireValue().build().columns(20)) + .addComponent(new JLabel(getText("PreferenceDialog.Password"))) + .addComponent(txtOpenSubtitlesPassword = + MyPasswordField.builder().requireValue().build().columns(20))); // SUBSCENE - this.chkSourceSubscene = new JCheckBox("Subscene").addTo(titelPanel, "wrap"); +// this.chkSourceSubscene = new JCheckBox("Subscene").addTo(titlePanel, "wrap"); // LOCAL - this.chkSourceLocal = new JCheckBox(Messages.getString("PreferenceDialog.Local")); - JScrollPane scrlPlocalSources = - new JScrollPane().scrollPane(this.localSourcesFoldersList = JListWithImages.createForType(Path.class).distinctValues().build()); - JButton btnBrowseLocalSources = new JButton(Messages.getString("PreferenceDialog.AddFolder")) - .withActionListener(() -> MemoryFolderChooser.getInstance() - .selectDirectory(this, Messages.getString("PreferenceDialog.SelectFolder")) - .map(Path::toAbsolutePath).filter(not(localSourcesFoldersList::contains)) - .ifPresent(path -> localSourcesFoldersList.addItem(PathMatchType.FOLDER.getImage(), path))); - JButton btnRemoveLocalSources = new JButton(Messages.getString("PreferenceDialog.DeleteFolder")) - .withActionListener(localSourcesFoldersList::removeSelectedItem); - - PanelCheckBox.checkbox(chkSourceLocal).panelOnNewLine().addTo(titelPanel) - .addComponent("aligny top, gapy 5px", new JLabel(Messages.getString("PreferenceDialog.LocalFolderWithSubtitles"))) - .addComponent("wrap", new JPanel(new MigLayout("insets 0", "[grow, nogrid]")).addComponent("split", btnBrowseLocalSources) - .addComponent("wrap", btnRemoveLocalSources).addComponent("wrap", scrlPlocalSources)); + this.chkSourceLocal = new JCheckBox(getText("PreferenceDialog.Local")); + JScrollPane scrLocalSources = + new JScrollPane().viewportView(this.localSourcesFoldersList = new JListWithImages<>()); + JButton btnBrowseLocalSources = new JButton(getText("PreferenceDialog.AddFolder")) + .actionListener(() -> MemoryFolderChooser.getInstance() + .selectDirectory(this, getText("PreferenceDialog.SelectFolder")) + .map(Path::toAbsolutePath).filter(not(localSourcesFoldersList::contains)) + .ifPresent(path -> localSourcesFoldersList.addItem(PathMatchType.FOLDER.image, path))); + JButton btnRemoveLocalSources = new JButton(getText("PreferenceDialog.DeleteFolder")) + .actionListener(localSourcesFoldersList::removeSelectedItem); + + new PanelCheckBox(checkbox:chkSourceLocal, panelOnNewLine:true) + .addTo(titlePanel).panel + .addComponent("aligny top, gapy 5px", + new JLabel(getText("PreferenceDialog.LocalFolderWithSubtitles"))) + .addComponent("wrap", + new JPanel(new MigLayout("insets 0", "[grow, nogrid]")) + .addComponent("split", btnBrowseLocalSources) + .addComponent("wrap", btnRemoveLocalSources) + .addComponent("wrap", scrLocalSources)); } loadPreferenceSettings(); } public void loadPreferenceSettings() { - chkSourceAddic7ed.setSelected(settingsCtrl.getSettings().isSerieSourceAddic7ed()); - chkUserAddic7edLogin.setSelected(settingsCtrl.getSettings().isLoginAddic7edEnabled()); - chkSourceAddic7edProxy.setSelected(settingsCtrl.getSettings().isSerieSourceAddic7edProxy()); - // chkSourceAddic7edProxy.setEnabled(settingsCtrl.getSettings().isSerieSourceAddic7ed()); - txtAddic7edUsername.setText(settingsCtrl.getSettings().getLoginAddic7edUsername()); - txtAddic7edPassword.setText(settingsCtrl.getSettings().getLoginAddic7edPassword()); - - chkSourceTvSubtitles.setSelected(settingsCtrl.getSettings().isSerieSourceTvSubtitles()); - chkSourcePodnapisi.setSelected(settingsCtrl.getSettings().isSerieSourcePodnapisi()); - chkSourceOpenSubtitles.setSelected(settingsCtrl.getSettings().isSerieSourceOpensubtitles()); - chkUserOpenSubtitlesLogin.setSelected(settingsCtrl.getSettings().isLoginOpenSubtitlesEnabled()); - txtOpenSubtitlesUsername.setText(settingsCtrl.getSettings().getLoginOpenSubtitlesUsername()); - txtOpenSubtitlesPassword.setText(settingsCtrl.getSettings().getLoginOpenSubtitlesPassword()); - chkSourceSubscene.setSelected(settingsCtrl.getSettings().isSerieSourceSubscene()); - chkSourceLocal.setSelected(settingsCtrl.getSettings().isSerieSourceLocal()); - settingsCtrl.getSettings().getLocalSourcesFolders().forEach(path -> localSourcesFoldersList.addItem(PathMatchType.FOLDER.getImage(), path)); + Settings settings = settingsCtrl.settings; + chkSourceAddic7ed.setSelected(settings.serieSourceAddic7ed); + chkUserAddic7edLogin.setSelected(settings.loginAddic7edEnabled); + chkSourceAddic7edProxy.setSelected(settings.serieSourceAddic7edProxy); + // chkSourceAddic7edProxy.setEnabled(settings.serieSourceAddic7ed); + txtAddic7edUsername.setText(settings.loginAddic7edUsername); + txtAddic7edPassword.setText(settings.loginAddic7edPassword); + + chkSourceTvSubtitles.setSelected(settings.serieSourceTvSubtitles); + chkSourcePodnapisi.setSelected(settings.serieSourcePodnapisi); + chkSourceOpenSubtitles.setSelected(settings.serieSourceOpensubtitles); + chkUserOpenSubtitlesLogin.setSelected(settings.loginOpenSubtitlesEnabled); + txtOpenSubtitlesUsername.setText(settings.loginOpenSubtitlesUsername); + txtOpenSubtitlesPassword.setText(settings.loginOpenSubtitlesPassword); +// chkSourceSubscene.setSelected(settings.serieSourceSubscene); + chkSourceLocal.setSelected(settings.serieSourceLocal); + settings.localSourcesFolders.forEach(path -> localSourcesFoldersList.addItem(PathMatchType.FOLDER.image, path)); } public void savePreferenceSettings() { - settingsCtrl.getSettings() - .setSerieSourceAddic7ed(chkSourceAddic7ed.isSelected()) - .setLoginAddic7edEnabled(chkUserAddic7edLogin.isSelected()) - .setSerieSourceAddic7edProxy(chkSourceAddic7edProxy.isSelected()) - .setLoginAddic7edUsername(txtAddic7edUsername.getText()) - .setLoginAddic7edPassword(new String(txtAddic7edPassword.getPassword())) - .setSerieSourceTvSubtitles(chkSourceTvSubtitles.isSelected()) - .setSerieSourcePodnapisi(chkSourcePodnapisi.isSelected()) - .setSerieSourceOpensubtitles(chkSourceOpenSubtitles.isSelected()) - .setLoginOpenSubtitlesEnabled(chkUserOpenSubtitlesLogin.isSelected()) - .setLoginOpenSubtitlesUsername(txtOpenSubtitlesUsername.getText()) - .setLoginOpenSubtitlesPassword(new String(txtOpenSubtitlesPassword.getPassword())) - .setSerieSourceSubscene(chkSourceSubscene.isSelected()) - .setSerieSourceLocal(chkSourceLocal.isSelected()) - .setLocalSourcesFolders(localSourcesFoldersList.stream().map(LabelPanel::getObject).toList()); + Settings settings = settingsCtrl.settings; + settings.serieSourceAddic7ed = chkSourceAddic7ed.isSelected(); + settings.loginAddic7edEnabled = chkUserAddic7edLogin.isSelected(); + settings.serieSourceAddic7edProxy = chkSourceAddic7edProxy.isSelected(); + settings.loginAddic7edUsername = txtAddic7edUsername.getText(); + settings.loginAddic7edPassword = new String(txtAddic7edPassword.getPassword()); + settings.serieSourceTvSubtitles = chkSourceTvSubtitles.isSelected(); + settings.serieSourcePodnapisi = chkSourcePodnapisi.isSelected(); + settings.serieSourceOpensubtitles = chkSourceOpenSubtitles.isSelected(); + settings.loginOpenSubtitlesEnabled = chkUserOpenSubtitlesLogin.isSelected(); + settings.loginOpenSubtitlesUsername = txtOpenSubtitlesUsername.getText(); + settings.loginOpenSubtitlesPassword = new String(txtOpenSubtitlesPassword.getPassword()); + settings.serieSourceSubscene = false; //chkSourceSubscene.isSelected(); + settings.serieSourceLocal = chkSourceLocal.isSelected(); + settings.localSourcesFolders = localSourcesFoldersList.stream().map(LabelPanel::getObject).toList(); } private boolean hasValidSettingsAddic7ed() { @@ -158,7 +167,8 @@ private boolean hasValidSettingsOpenSubtitles() { if (!txtOpenSubtitlesUsername.hasValidValue() || !txtOpenSubtitlesPassword.hasValidValue()) { return false; } - if (chkUserOpenSubtitlesLogin.isSelected() && !OpenSubtitlesApi.isValidCredentials(txtOpenSubtitlesUsername.getText(), + if (chkUserOpenSubtitlesLogin.isSelected() && + !OpenSubtitlesApi.isValidCredentials(txtOpenSubtitlesUsername.getText(), new String(txtOpenSubtitlesPassword.getPassword()))) { txtOpenSubtitlesUsername.setErrorBorder(); txtOpenSubtitlesPassword.setErrorBorder(); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFilePanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFilePanel.java index 3316f73c..57879ceb 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFilePanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFilePanel.java @@ -1,5 +1,12 @@ package org.lodder.subtools.multisubdownloader.gui.panels.preference; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import java.awt.*; import java.io.Serial; import java.util.LinkedHashMap; import java.util.List; @@ -11,24 +18,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.border.Border; -import javax.swing.border.EmptyBorder; -import javax.swing.border.LineBorder; - -import org.lodder.subtools.multisubdownloader.Messages; +import lombok.extern.slf4j.Slf4j; +import net.miginfocom.swing.MigLayout; import org.lodder.subtools.multisubdownloader.gui.dialog.StructureBuilderDialog; +import org.lodder.subtools.multisubdownloader.gui.extra.BoxModelProperties; import org.lodder.subtools.multisubdownloader.gui.extra.PanelCheckBox; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldString; import org.lodder.subtools.multisubdownloader.lib.library.FilenameLibraryBuilder; import org.lodder.subtools.multisubdownloader.settings.model.LibrarySettings; @@ -37,102 +32,104 @@ import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import java.awt.Color; - -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JTextFieldExtension.class, JComponentExtension.class, JCheckBoxExtension.class, AbstractButtonExtension.class }) +@Slf4j public class StructureFilePanel extends JPanel { - @Serial - private static final long serialVersionUID = -5458593307643063563L; - private final LibrarySettings librarySettings; + @Serial private static final long serialVersionUID = -5458593307643063563L; + private final LibrarySettings librarySettings; private final MyTextFieldString txtFileStructure; private final JCheckBox chkReplaceSpace; - private final MyComboBox cbxReplaceSpaceChar; + private final JComboBox cbxReplaceSpaceChar; private final JCheckBox chkIncludeLanguageCode; private final Supplier addLanguageSupplier; private final LanguageMapping languageMapping = new LanguageMapping(); public StructureFilePanel(LibrarySettings librarySettings, VideoType videoType, Manager manager, - UserInteractionHandler userInteractionHandler) { + UserInteractionHandler userInteractionHandler) { super(new MigLayout("insets 0, fill, nogrid")); this.librarySettings = librarySettings; - JPanel titelPanel = TitlePanel.title(Messages.getString("PreferenceDialog.RenameFiles")) - .margin(0).padding(0).marginLeft(20).paddingLeft(20).addTo(this, "span, grow"); - + JPanel titlePanel = new TitlePanel( + title:getText("PreferenceDialog.RenameFiles"), + margin:new BoxModelProperties(0, 20, 0, 0), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, grow"); + + new JLabel(getText("PreferenceDialog.Structure")).addTo(titlePanel, "shrink"); + this.txtFileStructure = + MyTextFieldString.builder().requireValue().build().columns(20).addTo(titlePanel, "grow"); + new JButton(getText("StructureBuilderDialog.Structure")) + .actionListener(() -> { + StructureBuilderDialog sDialog = + new StructureBuilderDialog(null, getText("PreferenceDialog.StructureBuilderTitle"), + true, videoType, StructureBuilderDialog.StructureType.FILE, manager, + userInteractionHandler, getLibraryStructureBuilder()); + String value = sDialog.showDialog(txtFileStructure.getText()); + if (!value.isEmpty()) { + txtFileStructure.setText(value); + } + + }).addTo(titlePanel, "shrink, wrap"); + + this.chkReplaceSpace = new JCheckBox(getText("PreferenceDialog.ReplaceSpaceWith")); + + new PanelCheckBox(checkbox:chkReplaceSpace, panelOnNewLine:false) + .addToPanel(titlePanel, "wrap") + .addComponent("width pref+10px, wrap", + this.cbxReplaceSpaceChar = JComboBox.create('-', '.', '_')); + + this.chkIncludeLanguageCode = + new JCheckBox(getText("PreferenceDialog.IncludeLanguageInFileName")) + .selectedListener(languageMapping::refreshState).addTo(titlePanel, "wrap"); + + JPanel languagePanelRoot = new PanelCheckBox( + checkbox:chkIncludeLanguageCode, + panelOnNewLine:true, + panelLayout:new MigLayout("insets 0, novisualpadding", "[][][]")) + .addToPanel(titlePanel, "span, growx"); { - new JLabel(Messages.getString("PreferenceDialog.Structure")).addTo(titelPanel, "shrink"); - this.txtFileStructure = MyTextFieldString.builder().requireValue().build().withColumns(20).addTo(titelPanel, "grow"); - new JButton(Messages.getString("StructureBuilderDialog.Structure")) - .withActionListener(() -> { - StructureBuilderDialog sDialog = - new StructureBuilderDialog(null, Messages.getString("PreferenceDialog.StructureBuilderTitle"), true, videoType, - StructureBuilderDialog.StructureType.FILE, manager, userInteractionHandler, getLibraryStructureBuilder()); - String value = sDialog.showDialog(txtFileStructure.getText()); - if (!value.isEmpty()) { - txtFileStructure.setText(value); - } - - }) - .addTo(titelPanel, "shrink, wrap"); - - this.chkReplaceSpace = new JCheckBox(Messages.getString("PreferenceDialog.ReplaceSpaceWith")); - - PanelCheckBox.checkbox(chkReplaceSpace).panelOnSameLine().addTo(titelPanel, "wrap") - .addComponent("width pref+10px, wrap", this.cbxReplaceSpaceChar = MyComboBox.ofValues('-', '.', '_')); - - this.chkIncludeLanguageCode = new JCheckBox(Messages.getString("PreferenceDialog.IncludeLanguageInFileName")) - .withSelectedListener(languageMapping::refreshState).addTo(titelPanel, "wrap"); - - JPanel languagePanelRoot = PanelCheckBox.checkbox(chkIncludeLanguageCode) - .panelOnNewLine().panelLayout(new MigLayout("insets 0, novisualpadding", "[][][]")) - .addTo(titelPanel, "span, growx"); - { - JPanel languagePanel = new JPanel(new MigLayout("insets 0, novisualpadding", "[][][][20px]")); - JScrollPane languageScrollPane = new JScrollPane(languagePanel).addTo(languagePanelRoot, "span, growx, wrap, hidemode 3"); - languageScrollPane.setBorder(new EmptyBorder(0, 0, 0, 0)); - languageScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - languageScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); - languageScrollPane.setVisible(false); - - AtomicInteger langId = new AtomicInteger(); - addLanguageSupplier = () -> { - int id = langId.getAndIncrement(); - MyComboBox cmbLanguage = - new MyComboBox<>(Language.values()).withToMessageStringRenderer(Language::getMsgCode).addTo(languagePanel); - MyTextFieldString txtLanguage = MyTextFieldString.builder().build().withColumns(20).addTo(languagePanel); - JButton btnDelete = new JButton(Messages.getString("StructureFilePanel.Delete")) - .withActionListenerSelf(delBtn -> { - languagePanel.remove(cmbLanguage); - languagePanel.remove(txtLanguage); - languagePanel.remove(delBtn); - languageMapping.remove(id); - languageScrollPane.setVisible(!languageMapping.isEmpty()); - languagePanelRoot.repaint(); - languagePanelRoot.revalidate(); - }) - .addTo(languagePanel, "wrap"); - LanguageComponents languageComponents = new LanguageComponents(cmbLanguage, txtLanguage, btnDelete); - languageMapping.put(id, languageComponents); - - languageScrollPane.setVisible(true); - languagePanelRoot.repaint(); - languagePanelRoot.revalidate(); - return languageComponents; - }; - new JButton(Messages.getString("StructureFilePanel.AddLanguage")) - .withActionListener(addLanguageSupplier::get).addTo(languagePanelRoot); - } + JPanel languagePanel = new JPanel(new MigLayout("insets 0, novisualpadding", "[][][][20px]")); + JScrollPane languageScrollPane = + new JScrollPane(languagePanel).addTo(languagePanelRoot, "span, growx, wrap, hidemode 3"); + languageScrollPane.setBorder(new EmptyBorder(0, 0, 0, 0)); + languageScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + languageScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + languageScrollPane.setVisible(false); + + AtomicInteger langId = new AtomicInteger(); + addLanguageSupplier = () -> { + int id = langId.getAndIncrement(); + JComboBox cmbLanguage = new JComboBox<>(Language.values()) + .toMessageStringRenderer(Language::getMsgCode).addTo(languagePanel); + MyTextFieldString txtLanguage = MyTextFieldString.builder().build().columns(20).addTo(languagePanel); + JButton btnDelete = new JButton(getText("StructureFilePanel.Delete")) + .actionListenerSelf(delBtn -> { + languagePanel.remove(cmbLanguage); + languagePanel.remove(txtLanguage); + languagePanel.remove(delBtn); + languageMapping.remove(id); + languageScrollPane.setVisible(!languageMapping.isEmpty()); + languagePanelRoot.repaint(); + languagePanelRoot.revalidate(); + }).addTo(languagePanel, "wrap"); + LanguageComponents languageComponents = new LanguageComponents(cmbLanguage, txtLanguage, btnDelete); + languageMapping.put(id, languageComponents); + + languageScrollPane.setVisible(true); + languagePanelRoot.repaint(); + languagePanelRoot.revalidate(); + return languageComponents; + }; + new JButton(getText("StructureFilePanel.AddLanguage")).actionListener( + addLanguageSupplier::get).addTo(languagePanelRoot); } loadPreferenceSettings(); } - private record LanguageComponents(MyComboBox cmbLanguage, MyTextFieldString txtLanguage, JButton btnDelete) { + private record LanguageComponents(JComboBox cmbLanguage, MyTextFieldString txtLanguage, + JButton btnDelete) { public void setValue(Language language, String langCode) { cmbLanguage.setSelectedItem(language); @@ -144,7 +141,7 @@ public boolean hasValidValue() { } Language getLanguage() { - return cmbLanguage.getSelectedItem(); + return cmbLanguage.getSelectedValue(); } } @@ -155,16 +152,13 @@ private void addLanguage(Language lang, String langCode) { } private Function getLibraryStructureBuilder() { - return structure -> FilenameLibraryBuilder.builder() - .structure(structure) - .replaceSpace(chkReplaceSpace.isSelected()) - .replacingSpaceChar(cbxReplaceSpaceChar.getSelectedItem()) - .includeLanguageCode(chkIncludeLanguageCode.isSelected()) - .languageTags(languageMapping.toSettingsMap()) - .useTvdbName(false) - .tvdbAdapter(null) - .rename(true) - .build(); + return structure -> new FilenameLibraryBuilder( + structure:structure, + replaceSpace:chkReplaceSpace.isSelected(), + replacingSpaceChar:cbxReplaceSpaceChar.getSelectedValue(), + includeLanguageCode:chkIncludeLanguageCode.isSelected(), + languageTags:languageMapping.toSettingsMap(), + rename:true); } @Override @@ -174,20 +168,18 @@ public void setEnabled(boolean enabled) { } public void loadPreferenceSettings() { - txtFileStructure.setText(librarySettings.getLibraryFilenameStructure()); - chkReplaceSpace.setSelected(librarySettings.isLibraryFilenameReplaceSpace()); - cbxReplaceSpaceChar.setSelectedItem(librarySettings.getLibraryFilenameReplacingSpaceChar()); - chkIncludeLanguageCode.setSelected(librarySettings.isLibraryIncludeLanguageCode()); - librarySettings.getLangCodeMap().forEach(this::addLanguage); + txtFileStructure.setText(librarySettings.filenameStructure); + chkReplaceSpace.setSelected(librarySettings.filenameReplaceSpace); + cbxReplaceSpaceChar.setSelectedItem(librarySettings.filenameReplacingSpaceChar); + chkIncludeLanguageCode.setSelected(librarySettings.includeLanguageCode); + librarySettings.langCodeMap.forEach(this::addLanguage); } public void savePreferenceSettings() { - librarySettings - .setLibraryFilenameStructure(txtFileStructure.getText()) - .setLibraryFilenameReplaceSpace(chkReplaceSpace.isSelected()) - .setLibraryFilenameReplacingSpaceChar(cbxReplaceSpaceChar.getSelectedItem()) - .setLibraryIncludeLanguageCode(chkIncludeLanguageCode.isSelected()) - .setLangCodeMap(languageMapping.toSettingsMap()); + librarySettings.filenameStructure = txtFileStructure.getText(); + librarySettings.filenameReplaceSpace = chkReplaceSpace.isSelected(); + librarySettings.filenameReplacingSpaceChar = cbxReplaceSpaceChar.getSelectedValue(); + librarySettings.langCodeMap = languageMapping.toSettingsMap(); } private static class LanguageMapping { @@ -202,11 +194,11 @@ public void remove(int id) { public void put(int id, LanguageComponents languageComponents) { languageComponentsMap.put(id, languageComponents); - MyComboBox cmbLanguage = languageComponents.cmbLanguage(); + JComboBox cmbLanguage = languageComponents.cmbLanguage(); cmbLanguage.putClientProperty(DEFAULT_BORDER_PROPERTY, cmbLanguage.getBorder()); - cmbLanguage.withSelectedItemConsumer(this::updateBorder); + cmbLanguage.selectedItemConsumer(this::updateBorder); cmbLanguage.addItemListener(e -> updateBorder((Language) e.getItem())); - updateBorder(cmbLanguage.getSelectedItem()); + updateBorder(cmbLanguage.getSelectedValue()); } private void updateBorder(Language lang) { @@ -215,14 +207,14 @@ private void updateBorder(Language lang) { return; } - Border border = componentList.size() > 1 ? ERROR_BORDER : getDefaultBorder(componentList.get(0)); + Border border = componentList.size() > 1 ? ERROR_BORDER : getDefaultBorder(componentList.first); componentList.forEach(components -> components.cmbLanguage.setBorder(border)); } public boolean hasValidSettings() { - return languageComponentsMap.values().stream().allMatch(LanguageComponents::hasValidValue) - && languageComponentsMap.values().stream().map(LanguageComponents::getLanguage).distinct().count() == languageComponentsMap - .size(); + return languageComponentsMap.values().stream().allMatch(LanguageComponents::hasValidValue) && + languageComponentsMap.values().stream().map(LanguageComponents::getLanguage).distinct().count() == + languageComponentsMap.size(); } private Stream getLanguageComponentsForLanguageStream(Language language) { @@ -234,18 +226,22 @@ public Optional getLanguageComponentsForLanguage(Language la } public Map toSettingsMap() { - return languageComponentsMap.values().stream().collect(Collectors.toMap( - langComps -> langComps.cmbLanguage().getSelectedItem(), langComps -> langComps.txtLanguage().getText(), - (v1, v2) -> v1, LinkedHashMap::new)); + return languageComponentsMap.values() + .stream() + .collect(Collectors.toMap(langComps -> langComps.cmbLanguage().getSelectedValue(), + langComps -> langComps.txtLanguage().getText(), (v1, _) -> v1, LinkedHashMap::new)); } public void refreshState(boolean enabled) { if (enabled) { - languageComponentsMap.values().stream().map(langComp -> langComp.cmbLanguage.getSelectedItem()).distinct() - .forEach(this::updateBorder); + languageComponentsMap.values() + .stream() + .map(langComp -> langComp.cmbLanguage.getSelectedValue()) + .distinct() + .forEach(this::updateBorder); } else { languageComponentsMap.values() - .forEach(langComps -> langComps.cmbLanguage.setBorder(getDefaultBorder(langComps))); + .forEach(langComps -> langComps.cmbLanguage.setBorder(getDefaultBorder(langComps))); } } @@ -259,8 +255,7 @@ public boolean isEmpty() { } public boolean hasValidSettings() { - return !isVisible() - || (txtFileStructure.hasValidValue() - && (!chkIncludeLanguageCode.isSelected() || languageMapping.hasValidSettings())); + return !isVisible() || (txtFileStructure.hasValidValue() && + (!chkIncludeLanguageCode.isSelected() || languageMapping.hasValidSettings())); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFolderPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFolderPanel.java index 7848cb42..c371ad53 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFolderPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructureFolderPanel.java @@ -1,23 +1,18 @@ package org.lodder.subtools.multisubdownloader.gui.panels.preference; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; import java.io.Serial; import java.util.function.Function; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; - +import net.miginfocom.swing.MigLayout; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.gui.dialog.StructureBuilderDialog; +import org.lodder.subtools.multisubdownloader.gui.extra.BoxModelProperties; import org.lodder.subtools.multisubdownloader.gui.extra.MemoryFolderChooser; import org.lodder.subtools.multisubdownloader.gui.extra.PanelCheckBox; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldPath; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldString; import org.lodder.subtools.multisubdownloader.lib.library.PathLibraryBuilder; @@ -26,81 +21,80 @@ import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JTextFieldExtension.class, JComponentExtension.class, JCheckBoxExtension.class, AbstractButtonExtension.class }) public class StructureFolderPanel extends JPanel implements PreferencePanelIntf { @Serial private static final long serialVersionUID = 3476596236588408382L; - private final LibrarySettings librarySettings; + private final LibrarySettings librarySettings; private final MyTextFieldPath txtLibraryFolder; private final MyTextFieldString txtFolderStructure; private final JCheckBox chkRemoveEmptyFolder; private final JCheckBox chkReplaceSpace; - private final MyComboBox cbxReplaceSpaceChar; + private final JComboBox cbxReplaceSpaceChar; public StructureFolderPanel(LibrarySettings librarySettings, VideoType videoType, Manager manager, - UserInteractionHandler userInteractionHandler) { + UserInteractionHandler userInteractionHandler) { super(new MigLayout("insets 0, fill, nogrid")); this.librarySettings = librarySettings; - JPanel titelPanel = TitlePanel.title(Messages.getString("PreferenceDialog.MoveToLibrary")) - .margin(0).padding(0).marginLeft(20).paddingLeft(20).useGrid() - .panelColumnConstraints("[shrink][grow][shrink]").addTo(this, "span, grow"); - - { - new JLabel(Messages.getString("PreferenceDialog.Location")).addTo(titelPanel, "shrink"); - this.txtLibraryFolder = MyTextFieldPath.builder().requireValue().build().withColumns(20).addTo(titelPanel, "grow"); - new JButton(Messages.getString("App.Browse")) - .withActionListener(() -> MemoryFolderChooser.getInstance() - .selectDirectory(getRootPane(), Messages.getString("PreferenceDialog.LibraryFolder")) - .ifPresent(txtLibraryFolder::setObject)) - .addTo(titelPanel, "shrink, wrap"); - - new JLabel(Messages.getString("StructureBuilderDialog.Structure")).addTo(titelPanel, "shrink"); - this.txtFolderStructure = - MyTextFieldString.builder().requireValue().build().withColumns(20).withDisabled().addTo(titelPanel, "grow"); - JButton btnStructure = new JButton(Messages.getString("StructureBuilderDialog.Structure")) - .withActionListener(() -> { - StructureBuilderDialog sDialog = new StructureBuilderDialog(null, - Messages.getString("PreferenceDialog.StructureBuilderTitle"), - true, videoType, StructureBuilderDialog.StructureType.FOLDER, manager, userInteractionHandler, - getLibraryStructureBuilder()); - String value = sDialog.showDialog(txtFolderStructure.getText()); - if (!"".equals(value)) { - txtFolderStructure.setText(value); - } - - }) - .withDisabled() - .addTo(titelPanel, "shrink, wrap"); - - this.chkRemoveEmptyFolder = new JCheckBox(Messages.getString("PreferenceDialog.RemoveEmptyFolders")).addTo(titelPanel, "span, wrap"); - - PanelCheckBox.checkbox(this.chkReplaceSpace = new JCheckBox(Messages.getString("PreferenceDialog.ReplaceSpaceWith"))) - .panelOnSameLine().addTo(titelPanel, "span") - .addComponent(this.cbxReplaceSpaceChar = MyComboBox.ofValues('-', '.', '_')); - - // behaviour - txtLibraryFolder.withValidityChangedCallback(txtFolderStructure::setEnabled, btnStructure::setEnabled); - } + JPanel titlePanel = new TitlePanel( + title:Messages.getText("PreferenceDialog.MoveToLibrary"), + margin:new BoxModelProperties(0, 20, 0, 0), + padding:new BoxModelProperties(0, 20, 0, 0), + useGrid:true, + panelColumnConstraints:"[shrink][grow][shrink]") + .addToPanel(this, "span, grow"); + + new JLabel(getText("PreferenceDialog.Location")).addTo(titlePanel, "shrink"); + this.txtLibraryFolder = + MyTextFieldPath.builder().requireValue().build().columns(20).addTo(titlePanel, "grow"); + new JButton(getText("App.Browse")) + .actionListener(() -> MemoryFolderChooser.getInstance() + .selectDirectory(getRootPane(), getText("PreferenceDialog.LibraryFolder")) + .ifPresent(txtLibraryFolder::setObject)) + .addTo(titlePanel, "shrink, wrap"); + + new JLabel(getText("StructureBuilderDialog.Structure")).addTo(titlePanel, "shrink"); + this.txtFolderStructure = + MyTextFieldString.builder().requireValue().build().columns(20).disabled().addTo(titlePanel, "grow"); + JButton btnStructure = new JButton(getText("StructureBuilderDialog.Structure")) + .actionListener(() -> { + StructureBuilderDialog sDialog = new StructureBuilderDialog(null, + getText("PreferenceDialog.StructureBuilderTitle"), + true, videoType, StructureBuilderDialog.StructureType.FOLDER, manager, + userInteractionHandler, getLibraryStructureBuilder()); + String value = sDialog.showDialog(txtFolderStructure.getText()); + if (!"".equals(value)) { + txtFolderStructure.setText(value); + } + }) + .disabled() + .addTo(titlePanel, "shrink, wrap"); + + this.chkRemoveEmptyFolder = new JCheckBox(getText("PreferenceDialog.RemoveEmptyFolders")) + .addTo(titlePanel, "span, wrap"); + + new PanelCheckBox( + checkbox:this.chkReplaceSpace = new JCheckBox(getText("PreferenceDialog.ReplaceSpaceWith")), + panelOnNewLine:false + ) + .addToPanel(titlePanel, "span") + .addComponent(this.cbxReplaceSpaceChar = JComboBox.create('-', '.', '_')); + + // behaviour + txtLibraryFolder.withValidityChangedCallback(txtFolderStructure::setEnabled, btnStructure::setEnabled); loadPreferenceSettings(); } private Function getLibraryStructureBuilder() { - return structure -> PathLibraryBuilder.builder() - .structure(structure) - .replaceSpace(chkReplaceSpace.isSelected()) - .replacingSpaceChar(cbxReplaceSpaceChar.getSelectedItem()) - .useTvdbName(false) - .tvdbAdapter(null) - .libraryFolder(txtLibraryFolder.getObject()) - .move(true) - .build(); + return structure -> new PathLibraryBuilder( + structure:structure, + replaceSpace:chkReplaceSpace.isSelected(), + replacingSpaceChar:cbxReplaceSpaceChar.getSelectedValue(), + libraryFolder:txtLibraryFolder.getObject(), + move:true); } @Override @@ -111,22 +105,19 @@ public void setEnabled(boolean enabled) { } public void loadPreferenceSettings() { - txtLibraryFolder.setObject(librarySettings.getLibraryFolder()); - txtFolderStructure.setText(librarySettings.getLibraryFolderStructure()); - chkRemoveEmptyFolder.setSelected(librarySettings.isLibraryRemoveEmptyFolders()); - chkReplaceSpace.setSelected(librarySettings.isLibraryFolderReplaceSpace()); - cbxReplaceSpaceChar.setSelectedItem(librarySettings.getLibraryFolderReplacingSpaceChar()); + txtLibraryFolder.setObject(librarySettings.folder); + txtFolderStructure.setText(librarySettings.folderStructure); + chkRemoveEmptyFolder.setSelected(librarySettings.removeEmptyFolders); + chkReplaceSpace.setSelected(librarySettings.folderReplaceSpace); + cbxReplaceSpaceChar.setSelectedItem(librarySettings.folderReplacingSpaceChar); } public void savePreferenceSettings() { - librarySettings - .setLibraryFolder(txtLibraryFolder.getObject()) - .setLibraryFolderStructure(txtFolderStructure.getText()) - .setLibraryRemoveEmptyFolders(chkRemoveEmptyFolder.isSelected()) - .setLibraryFolderReplaceSpace(chkReplaceSpace.isSelected()) - // if (pnlStructureFolder.isReplaceSpaceSelected()) { - .setLibraryFolderReplacingSpaceChar(cbxReplaceSpaceChar.getSelectedItem()); - // } + librarySettings.folder = txtLibraryFolder.getObject(); + librarySettings.folderStructure = txtFolderStructure.getText(); + librarySettings.removeEmptyFolders = chkRemoveEmptyFolder.isSelected(); + librarySettings.folderReplaceSpace = chkReplaceSpace.isSelected(); + librarySettings.folderReplacingSpaceChar = cbxReplaceSpaceChar.getSelectedValue(); } @Override diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructurePanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructurePanel.java deleted file mode 100644 index a96f3d32..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/StructurePanel.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.lodder.subtools.multisubdownloader.gui.panels.preference; - -import java.io.Serial; - -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JPanel; - -import org.lodder.subtools.multisubdownloader.Messages; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; - -import java.awt.event.ActionListener; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ JCheckBoxExtension.class }) -public abstract class StructurePanel> extends JPanel implements PreferencePanelIntf { - - @Serial - private static final long serialVersionUID = 7507970016496546514L; - @Getter(value = AccessLevel.PROTECTED) - private final JButton btnBuildStructure; - @Getter(value = AccessLevel.PROTECTED) - private final JCheckBox chkReplaceSpace; - @Getter(value = AccessLevel.PROTECTED) - private final MyComboBox cbxReplaceSpaceChar; - - public StructurePanel() { - this.btnBuildStructure = new JButton(Messages.getString("StructureBuilderDialog.Structure")); - - this.cbxReplaceSpaceChar = new MyComboBox<>(new String[] { "-", ".", "_" }); - - this.chkReplaceSpace = new JCheckBox(Messages.getString("PreferenceDialog.ReplaceSpaceWith")) - .addCheckedChangeListener(cbxReplaceSpaceChar::setEnabled); - } - - @SuppressWarnings("unchecked") - public T addBuildStructureAction(ActionListener buildStructureAction) { - btnBuildStructure.addActionListener(buildStructureAction); - return (T) this; - } - - public String getReplaceSpaceChar() { - return this.getCbxReplaceSpaceChar().getSelectedItem(); - } - - public void setReplaceSpaceChar(String s) { - this.getCbxReplaceSpaceChar().setSelectedItem(s); - } - - public boolean isReplaceSpaceSelected() { - return this.getChkReplaceSpace().isSelected(); - } - - public void setReplaceSpaceSelected(boolean b) { - this.getChkReplaceSpace().setSelected(b); - } - -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SubtitleBackupPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SubtitleBackupPanel.java index cf4982b6..823c8606 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SubtitleBackupPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/SubtitleBackupPanel.java @@ -1,34 +1,24 @@ package org.lodder.subtools.multisubdownloader.gui.panels.preference; -import java.io.Serial; +import static org.lodder.subtools.multisubdownloader.Messages.*; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; +import javax.swing.*; +import java.io.Serial; +import net.miginfocom.swing.MigLayout; import org.lodder.subtools.multisubdownloader.Messages; +import org.lodder.subtools.multisubdownloader.gui.extra.BoxModelProperties; import org.lodder.subtools.multisubdownloader.gui.extra.MemoryFolderChooser; import org.lodder.subtools.multisubdownloader.gui.extra.PanelCheckBox; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.button.AbstractButtonExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.JTextFieldExtension; import org.lodder.subtools.multisubdownloader.gui.jcomponent.jtextfield.MyTextFieldPath; import org.lodder.subtools.multisubdownloader.settings.model.LibrarySettings; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ JCheckBoxExtension.class, JTextFieldExtension.class, JComponentExtension.class, AbstractButtonExtension.class }) public class SubtitleBackupPanel extends JPanel implements PreferencePanelIntf { - @Serial - private static final long serialVersionUID = -1498846730946617177L; + @Serial private static final long serialVersionUID = -1498846730946617177L; private final LibrarySettings librarySettings; - private final JCheckBox chkBackupSubtitle; private final MyTextFieldPath txtBackupSubtitlePath; private final JCheckBox chkBackupUseSourceFileName; @@ -37,38 +27,45 @@ public SubtitleBackupPanel(LibrarySettings librarySettings) { super(new MigLayout("insets 0, fillx, nogrid")); this.librarySettings = librarySettings; - JPanel titelPanel = TitlePanel.title(Messages.getString("PreferenceDialog.SubtitlesBackup")) - .margin(0).padding(0).paddingLeft(20).addTo(this, "span, growx"); - + JPanel titlePanel = new TitlePanel( + title:Messages.getText("PreferenceDialog.SubtitlesBackup"), + margin:new BoxModelProperties(0), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, growx"); + { - this.txtBackupSubtitlePath = MyTextFieldPath.builder().requireValue().build().withColumns(20); - - PanelCheckBox.checkbox(this.chkBackupSubtitle = new JCheckBox(Messages.getString("PreferenceDialog.BackupSubtitles"))) - .panelOnNewLine().addTo(titelPanel, "span, wrap, growx") - .addComponent("split 3, shrink", new JLabel(Messages.getString("PreferenceDialog.Location"))) - .addComponent("growx", txtBackupSubtitlePath) - .addComponent("shrink", new JButton(Messages.getString("App.Browse")) - .withActionListener(l -> MemoryFolderChooser.getInstance() - .selectDirectory(this, Messages.getString("PreferenceDialog.SubtitleBackupFolder")) - .ifPresent(txtBackupSubtitlePath::setObject))); - - chkBackupUseSourceFileName = new JCheckBox(Messages.getString("PreferenceDialog.IncludeSourceInFileName")).addTo(titelPanel); + this.txtBackupSubtitlePath = MyTextFieldPath.builder().requireValue().build().columns(20); + + new PanelCheckBox( + checkbox:this.chkBackupSubtitle = new JCheckBox(getText("PreferenceDialog.BackupSubtitles")), + panelOnNewLine:true + ) + .addToPanel(titlePanel, "span, wrap, growx") + .addComponent("split 3, shrink", new JLabel(getText("PreferenceDialog.Location"))) + .addComponent("growx", txtBackupSubtitlePath) + .addComponent("shrink", + new JButton(getText("App.Browse")) + .actionListener(_ -> MemoryFolderChooser.getInstance() + .selectDirectory(this, getText("PreferenceDialog.SubtitleBackupFolder")) + .ifPresent(txtBackupSubtitlePath::setObject))); + + chkBackupUseSourceFileName = + new JCheckBox(getText("PreferenceDialog.IncludeSourceInFileName")).addTo(titlePanel); } loadPreferenceSettings(); } public void loadPreferenceSettings() { - chkBackupSubtitle.setSelected(librarySettings.isLibraryBackupSubtitle()); - txtBackupSubtitlePath.setObject(librarySettings.getLibraryBackupSubtitlePath()); - chkBackupUseSourceFileName.setSelected(librarySettings.isLibraryBackupUseWebsiteFileName()); + chkBackupSubtitle.setSelected(librarySettings.backupSubtitle); + txtBackupSubtitlePath.setObject(librarySettings.backupSubtitlePath); + chkBackupUseSourceFileName.setSelected(librarySettings.backupUseWebsiteFileName); } public void savePreferenceSettings() { - librarySettings - .setLibraryBackupSubtitle(chkBackupSubtitle.isSelected()) - .setLibraryBackupSubtitlePath(txtBackupSubtitlePath.getObject()) - .setLibraryBackupUseWebsiteFileName(chkBackupUseSourceFileName.isSelected()); + librarySettings.backupSubtitle = chkBackupSubtitle.isSelected(); + librarySettings.backupSubtitlePath = txtBackupSubtitlePath.getObject(); + librarySettings.backupUseWebsiteFileName = chkBackupUseSourceFileName.isSelected(); } @Override diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/VideoLibraryPanel.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/VideoLibraryPanel.java index 6ca98434..b5ed414f 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/VideoLibraryPanel.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/panels/preference/VideoLibraryPanel.java @@ -1,18 +1,17 @@ package org.lodder.subtools.multisubdownloader.gui.panels.preference; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; import java.io.Serial; import java.nio.file.Files; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import org.lodder.subtools.multisubdownloader.Messages; +import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.val; +import net.miginfocom.swing.MigLayout; +import org.lodder.subtools.multisubdownloader.gui.extra.BoxModelProperties; import org.lodder.subtools.multisubdownloader.gui.extra.PartialDisableComboBox; import org.lodder.subtools.multisubdownloader.gui.extra.TitlePanel; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcheckbox.JCheckBoxExtension; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcombobox.MyComboBox; -import org.lodder.subtools.multisubdownloader.gui.jcomponent.jcomponent.JComponentExtension; import org.lodder.subtools.multisubdownloader.lib.library.LibraryActionType; import org.lodder.subtools.multisubdownloader.lib.library.LibraryOtherFileActionType; import org.lodder.subtools.multisubdownloader.settings.model.LibrarySettings; @@ -20,81 +19,82 @@ import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; -import net.miginfocom.swing.MigLayout; - -@ExtensionMethod({ Files.class, JComponentExtension.class, JCheckBoxExtension.class }) -public abstract class VideoLibraryPanel extends JPanel implements PreferencePanelIntf { - - @Serial - private static final long serialVersionUID = -9175813173306481849L; +@ExtensionMethod({ Files.class }) +public abstract sealed class VideoLibraryPanel extends JPanel implements PreferencePanelIntf + permits EpisodeLibraryPanel, MovieLibraryPanel { - @Getter - private final LibrarySettings librarySettings; + @Serial private static final long serialVersionUID = -9175813173306481849L; - protected final StructureFolderPanel pnlStructureFolder; - protected final StructureFilePanel pnlStructureFile; - private final MyComboBox cbxLibraryAction; + @val LibrarySettings librarySettings; + private final JComboBox cbxLibraryAction; private final JCheckBox chkUseTVDBNaming; private final PartialDisableComboBox cbxLibraryOtherFileAction; private final SubtitleBackupPanel pnlBackup; + protected final StructureFolderPanel pnlStructureFolder; + protected final StructureFilePanel pnlStructureFile; - public VideoLibraryPanel(LibrarySettings librarySettings, VideoType videoType, Manager manager, boolean renameMode, - UserInteractionHandler userInteractionHandler) { + VideoLibraryPanel(LibrarySettings librarySettings, VideoType videoType, Manager manager, boolean renameMode, + UserInteractionHandler userInteractionHandler) { super(new MigLayout("fillx, nogrid")); this.librarySettings = librarySettings; this.pnlBackup = renameMode ? null : new SubtitleBackupPanel(librarySettings).addTo(this, "wrap, span, growx"); - JPanel performActionPanel = TitlePanel.title(Messages.getString("PreferenceDialog.PerformActions")) - .margin(0).padding(0).paddingLeft(20).addTo(this, "span, growx"); + JPanel performActionPanel = new TitlePanel( + title:getText("PreferenceDialog.PerformActions"), + margin:new BoxModelProperties(0), + padding:new BoxModelProperties(0, 20, 0, 0)) + .addToPanel(this, "span, growx"); { - this.chkUseTVDBNaming = new JCheckBox(Messages.getString("PreferenceDialog.UseTvdbName")) - .visible(VideoType.EPISODE == videoType) - .addTo(performActionPanel, "hidemode 3, wrap"); + this.chkUseTVDBNaming = new JCheckBox(getText("PreferenceDialog.UseTvdbName")).visible( + VideoType.EPISODE == videoType).addTo(performActionPanel, "hidemode 3, wrap"); - new JLabel(Messages.getString("PreferenceDialog.ActionForShowFiles")).addTo(performActionPanel); - this.cbxLibraryAction = new MyComboBox<>(LibraryActionType.values()) - .withToMessageStringRenderer(LibraryActionType::getMsgCode) - .addTo(performActionPanel, "wrap"); + new JLabel(getText("PreferenceDialog.ActionForShowFiles")).addTo(performActionPanel); + this.cbxLibraryAction = new JComboBox<>(LibraryActionType.values()) + .toMessageStringRenderer(LibraryActionType::getMsgCode) + .addTo(performActionPanel, "wrap"); - this.pnlStructureFolder = new StructureFolderPanel(librarySettings, videoType, manager, userInteractionHandler) + this.pnlStructureFolder = + new StructureFolderPanel(librarySettings, videoType, manager, userInteractionHandler) .addTo(performActionPanel, "hidemode 3, wrap, span, growx"); - this.pnlStructureFile = new StructureFilePanel(librarySettings, videoType, manager, userInteractionHandler) + this.pnlStructureFile = + new StructureFilePanel(librarySettings, videoType, manager, userInteractionHandler) .addTo(performActionPanel, "hidemode 3, wrap, span, growx"); - JLabel lblActionForOtherFiles = new JLabel(Messages.getString("PreferenceDialog.ActionForOtherFiles")).addTo(performActionPanel); - this.cbxLibraryOtherFileAction = PartialDisableComboBox.of(LibraryOtherFileActionType.values()).addTo(performActionPanel); + JLabel lblActionForOtherFiles = + new JLabel(getText("PreferenceDialog.ActionForOtherFiles")).addTo(performActionPanel); + this.cbxLibraryOtherFileAction = + PartialDisableComboBox.of(LibraryOtherFileActionType.values()).addTo(performActionPanel); // - this.cbxLibraryAction.withSelectedItemConsumer(action -> { + this.cbxLibraryAction.selectedItemConsumer(action -> { boolean enable = action != LibraryActionType.NOTHING; cbxLibraryOtherFileAction.setEnabled(enable); lblActionForOtherFiles.setEnabled(enable); }); } - this.cbxLibraryAction - .withItemListener(() -> { - checkEnableStatusPanel(); - checkPossibleOtherFileActions(); - if (!cbxLibraryOtherFileAction.isItemEnabled(cbxLibraryOtherFileAction.getSelectedIndex())) { - cbxLibraryOtherFileAction.setSelectedIndex(0); - } - }); + this.cbxLibraryAction.itemListener(() -> { + checkEnableStatusPanel(); + checkPossibleOtherFileActions(); + if (!cbxLibraryOtherFileAction.isItemEnabled(cbxLibraryOtherFileAction.getSelectedIndex())) { + cbxLibraryOtherFileAction.setSelectedIndex(0); + } + }); loadPreferenceSettings(); } private void checkPossibleOtherFileActions() { - LibraryActionType libraryActionType = cbxLibraryAction.getSelectedItem(); + LibraryActionType libraryActionType = cbxLibraryAction.getSelectedValue(); for (int i = 0; i < cbxLibraryOtherFileAction.getModel().getSize(); i++) { LibraryOtherFileActionType ofa = cbxLibraryOtherFileAction.getItemAt(i); boolean enabled = switch (libraryActionType) { - case MOVE -> LibraryOtherFileActionType.MOVEANDRENAME != ofa && LibraryOtherFileActionType.RENAME != ofa; - case RENAME -> LibraryOtherFileActionType.MOVEANDRENAME != ofa && LibraryOtherFileActionType.MOVE != ofa; + case MOVE -> + LibraryOtherFileActionType.MOVEANDRENAME != ofa && LibraryOtherFileActionType.RENAME != ofa; + case RENAME -> + LibraryOtherFileActionType.MOVEANDRENAME != ofa && LibraryOtherFileActionType.MOVE != ofa; case MOVEANDRENAME -> true; case NOTHING -> LibraryOtherFileActionType.NOTHING == ofa; }; @@ -103,7 +103,7 @@ private void checkPossibleOtherFileActions() { } private void checkEnableStatusPanel() { - LibraryActionType libraryActionType = cbxLibraryAction.getSelectedItem(); + LibraryActionType libraryActionType = cbxLibraryAction.getSelectedValue(); boolean pnlStructureFileVisible = switch (libraryActionType) { case MOVE, NOTHING -> false; case RENAME, MOVEANDRENAME -> true; @@ -122,9 +122,9 @@ private void checkEnableStatus(JPanel panel, boolean status) { } public void loadPreferenceSettings() { - cbxLibraryAction.setSelectedItem(librarySettings.getLibraryAction()); - chkUseTVDBNaming.setSelected(librarySettings.isLibraryUseTVDBNaming()); - cbxLibraryOtherFileAction.setSelectedItem(librarySettings.getLibraryOtherFileAction()); + cbxLibraryAction.setSelectedItem(librarySettings.action); + chkUseTVDBNaming.setSelected(librarySettings.useTVDBNaming); + cbxLibraryOtherFileAction.setSelectedItem(librarySettings.otherFileAction); checkEnableStatusPanel(); checkPossibleOtherFileActions(); @@ -134,9 +134,9 @@ public void savePreferenceSettings() { if (pnlBackup != null) { pnlBackup.savePreferenceSettings(); } - librarySettings.setLibraryAction(this.cbxLibraryAction.getSelectedItem()) - .setLibraryUseTVDBNaming(this.chkUseTVDBNaming.isSelected()) - .setLibraryOtherFileAction((LibraryOtherFileActionType) this.cbxLibraryOtherFileAction.getSelectedItem()); + librarySettings.action = this.cbxLibraryAction.getSelectedValue(); + librarySettings.useTVDBNaming = this.chkUseTVDBNaming.isSelected(); + librarySettings.otherFileAction = this.cbxLibraryOtherFileAction.getSelectedValue(); pnlStructureFolder.savePreferenceSettings(); pnlStructureFile.savePreferenceSettings(); @@ -144,7 +144,8 @@ public void savePreferenceSettings() { @Override public boolean hasValidSettings() { - return pnlStructureFolder.hasValidSettings() && pnlStructureFile.hasValidSettings() && (pnlBackup == null || pnlBackup.hasValidSettings()); + return pnlStructureFolder.hasValidSettings() && pnlStructureFile.hasValidSettings() && + (pnlBackup == null || pnlBackup.hasValidSettings()); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/DownloadWorker.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/DownloadWorker.java index 76a2dbe5..d07100dd 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/DownloadWorker.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/DownloadWorker.java @@ -23,18 +23,18 @@ import org.slf4j.LoggerFactory; /** - * Created by IntelliJ IDEA. User: lodder Date: 4/12/11 Time: 8:52 AM To change this template use - * Path | Settings | Path Templates. + * Created by IntelliJ IDEA. User: lodder Date: 4/12/11 Time: 8:52 AM To change this template use Path | Settings | Path + * Templates. */ public class DownloadWorker extends SwingWorker implements Cancelable { + private static final Logger LOGGER = LoggerFactory.getLogger(DownloadWorker.class); + private final CustomTable table; private final Settings settings; private final DownloadAction downloadAction; private final UserInteractionHandlerAction userInteractionHandlerAction; - private static final Logger LOGGER = LoggerFactory.getLogger(DownloadWorker.class); - public DownloadWorker(CustomTable table, Settings settings, Manager manager, GUI gui) { this.table = table; this.settings = settings; @@ -63,7 +63,7 @@ protected Void doInBackground() { progress = 1; } setProgress(progress); - publish(selectedShow.getFileName()); + publish(selectedShow.fileName); List selection = userInteractionHandlerAction.subtitleSelection(selectedShow, true); try { for (int j = 0; j < selection.size(); j++) { @@ -83,11 +83,12 @@ protected Void doInBackground() { @Override protected void process(List data) { - data.forEach(s -> StatusMessenger.instance.message(Messages.getString("MainWindow.DownloadingSubtitle", s))); + data.forEach(s -> StatusMessenger.instance.message(Messages.getText("MainWindow.DownloadingSubtitle", s))); } private void showErrorMessage(String message) { - JOptionPane.showConfirmDialog(null, message, "JBierSubDownloader", JOptionPane.CLOSED_OPTION, JOptionPane.ERROR_MESSAGE); + JOptionPane.showConfirmDialog(null, message, "JBierSubDownloader", JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/RenameWorker.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/RenameWorker.java index 536989e5..41ffc68d 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/RenameWorker.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/gui/workers/RenameWorker.java @@ -1,9 +1,9 @@ package org.lodder.subtools.multisubdownloader.gui.workers; +import javax.swing.*; import java.util.List; -import javax.swing.SwingWorker; - +import lombok.RequiredArgsConstructor; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.actions.RenameAction; import org.lodder.subtools.multisubdownloader.gui.dialog.Cancelable; @@ -13,16 +13,11 @@ import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.model.Release; -import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import lombok.RequiredArgsConstructor; /** - * Created by IntelliJ IDEA. User: lodder Date: 4/12/11 Time: 8:52 AM To change this template use - * Path | Settings | Path Templates. + * Created by IntelliJ IDEA. User: lodder Date: 4/12/11 Time: 8:52 AM To change this template use Path | Settings | Path + * Templates. */ @RequiredArgsConstructor public class RenameWorker extends SwingWorker implements Cancelable { @@ -32,8 +27,6 @@ public class RenameWorker extends SwingWorker implements Cancelabl private final Manager manager; private final UserInteractionHandler userInteractionHandler; - private static final Logger LOGGER = LoggerFactory.getLogger(RenameWorker.class); - @Override protected Void doInBackground() { final VideoTableModel model = (VideoTableModel) table.getModel(); @@ -53,17 +46,11 @@ protected Void doInBackground() { } setProgress(progress); - RenameAction renameAction = null; - if (selectedShow.getVideoType() == VideoType.EPISODE) { - LOGGER.debug("Treat as EPISODE"); - renameAction = new RenameAction(settings.getEpisodeLibrarySettings(), manager, userInteractionHandler); - } else if (selectedShow.getVideoType() == VideoType.MOVIE) { - LOGGER.debug("Treat as MOVIE"); - renameAction = new RenameAction(settings.getMovieLibrarySettings(), manager, userInteractionHandler); - } - if (renameAction != null) { - renameAction.rename(selectedShow.getPath().resolve(selectedShow.getFileName()), selectedShow); - } + RenameAction renameAction = switch (selectedShow.videoType) { + case EPISODE -> new RenameAction(settings.episodeLibrarySettings, manager, userInteractionHandler); + case MOVIE -> new RenameAction(settings.movieLibrarySettings, manager, userInteractionHandler); + }; + renameAction.rename(selectedShow.getPath().resolve(selectedShow.fileName), selectedShow); model.removeShow(selectedShow); } }); @@ -72,6 +59,6 @@ protected Void doInBackground() { @Override protected void process(List data) { - data.forEach(s -> StatusMessenger.instance.message(Messages.getString("MainWindow.RenamingFile", s))); + data.forEach(s -> StatusMessenger.instance.message(Messages.getText("MainWindow.RenamingFile", s))); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/Info.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/Info.java index be0af20f..879341e2 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/Info.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/Info.java @@ -1,10 +1,13 @@ package org.lodder.subtools.multisubdownloader.lib; +import lombok.experimental.UtilityClass; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@SuppressWarnings("java:S106") +@UtilityClass public class Info { private static final Logger LOGGER = LoggerFactory.getLogger(Info.class); @@ -17,12 +20,12 @@ public static void subtitleSources(Settings settings, boolean isCli) { } for (SubtitleSource source : SubtitleSource.values()) { boolean enabled = switch (source) { - case ADDIC7ED -> settings.isSerieSourceAddic7ed(); - case LOCAL -> settings.isSerieSourceLocal(); - case OPENSUBTITLES -> settings.isSerieSourceOpensubtitles(); - case PODNAPISI -> settings.isSerieSourcePodnapisi(); - case TVSUBTITLES -> settings.isSerieSourceTvSubtitles(); - case SUBSCENE -> settings.isSerieSourceSubscene(); + case ADDIC7ED -> settings.serieSourceAddic7ed; + case LOCAL -> settings.serieSourceLocal; + case OPENSUBTITLES -> settings.serieSourceOpensubtitles; + case PODNAPISI -> settings.serieSourcePodnapisi; + case TVSUBTITLES -> settings.serieSourceTvSubtitles; + case SUBSCENE -> settings.serieSourceSubscene; }; if (isCli) { System.out.println(" - provider : " + source + " enabled: " + enabled); @@ -40,15 +43,16 @@ public static void subtitleSources(Settings settings, boolean isCli) { public static void subtitleFiltering(Settings settings, boolean isCli) { if (isCli) { System.out.println("----- Subtitle Filtering ------"); - System.out.println(" - OptionSubtitleExactMatch : " + settings.isOptionSubtitleExactMatch()); - System.out.println(" - OptionSubtitleKeywordMatch : " + settings.isOptionSubtitleKeywordMatch()); - System.out.println(" - OptionSubtitleExcludeHearingImpaired : " + settings.isOptionSubtitleExcludeHearingImpaired()); + System.out.println(" - OptionSubtitleExactMatch : " + settings.optionSubtitleExactMatch); + System.out.println(" - OptionSubtitleKeywordMatch : " + settings.optionSubtitleKeywordMatch); + System.out.println( + " - OptionSubtitleExcludeHearingImpaired : " + settings.optionSubtitleExcludeHearingImpaired); System.out.println("-------------------------------"); } else { LOGGER.info("----- Subtitle Filtering ------"); - LOGGER.info(" - OptionSubtitleExactMatch: {} ", settings.isOptionSubtitleExactMatch()); - LOGGER.info(" - OptionSubtitleKeywordMatch: {} ", settings.isOptionSubtitleKeywordMatch()); - LOGGER.info(" - OptionSubtitleExcludeHearingImpaired: {} ", settings.isOptionSubtitleExcludeHearingImpaired()); + LOGGER.info(" - OptionSubtitleExactMatch: {} ", settings.optionSubtitleExactMatch); + LOGGER.info(" - OptionSubtitleKeywordMatch: {} ", settings.optionSubtitleKeywordMatch); + LOGGER.info(" - OptionSubtitleExcludeHearingImpaired: {} ", settings.optionSubtitleExcludeHearingImpaired); LOGGER.info("-------------------------------"); } @@ -57,11 +61,11 @@ public static void subtitleFiltering(Settings settings, boolean isCli) { public static void downloadOptions(Settings settings, boolean isCli) { if (isCli) { System.out.println("----- Download Options ------"); - System.out.println(" - OptionsAlwaysConfirm : " + settings.isOptionsAlwaysConfirm()); + System.out.println(" - OptionsAlwaysConfirm : " + settings.optionsAlwaysConfirm); System.out.println("-----------------------------"); } else { LOGGER.info("----- Download Options ------"); - LOGGER.info(" - OptionsAlwaysConfirm : " + settings.isOptionsAlwaysConfirm()); + LOGGER.info(" - OptionsAlwaysConfirm : " + settings.optionsAlwaysConfirm); LOGGER.info("-----------------------------"); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/ReleaseFactory.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/ReleaseFactory.java index e9c91679..6c729d5f 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/ReleaseFactory.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/ReleaseFactory.java @@ -19,27 +19,33 @@ public class ReleaseFactory { - private final ReleaseParser releaseParser; + private static final Logger LOGGER = LoggerFactory.getLogger(ReleaseFactory.class); + + private final ReleaseParser releaseParser = new ReleaseParser(); private final Settings settings; private final Manager manager; - private static final Logger LOGGER = LoggerFactory.getLogger(ReleaseFactory.class); - public ReleaseFactory(Settings settings, Manager manager) { - this.releaseParser = new ReleaseParser(); this.settings = settings; this.manager = manager; } public Release createRelease(Path file, UserInteractionHandler userInteractionHandler) { + return createRelease(file, userInteractionHandler, true); + } + + public Release createRelease(Path file, UserInteractionHandler userInteractionHandler, + boolean validate) { try { - Release r = releaseParser.parse(file); - ReleaseControl releaseControl = switch (r.getVideoType()) { - case EPISODE -> new TvReleaseControl((TvRelease) r, settings, manager, userInteractionHandler); - case MOVIE -> new MovieReleaseControl((MovieRelease) r, settings, manager, userInteractionHandler); + ReleaseControl releaseControl = switch (releaseParser.parse(file)) { + case TvRelease tvRelease -> new TvReleaseControl(tvRelease, settings, manager, userInteractionHandler); + case MovieRelease movieRelease -> new MovieReleaseControl(movieRelease, settings, manager, + userInteractionHandler); }; - releaseControl.process(); - return releaseControl.getVideoFile(); + if (validate) { + releaseControl.process(); + } + return releaseControl.videoFile; } catch (ReleaseParseException | ReleaseControlException e) { LOGGER.error("createRelease: " + e.getMessage(), e); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/MovieReleaseControl.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/MovieReleaseControl.java index b6b6f2c5..e837c242 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/MovieReleaseControl.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/MovieReleaseControl.java @@ -1,7 +1,5 @@ package org.lodder.subtools.multisubdownloader.lib.control; -import java.util.Optional; - import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.sublibrary.Manager; @@ -12,21 +10,18 @@ import org.lodder.subtools.sublibrary.model.MovieRelease; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ OptionalExtension.class }) -public class MovieReleaseControl extends ReleaseControl { +public final class MovieReleaseControl extends ReleaseControl { private final ImdbAdapter imdbAdapter; private final OmdbAdapter omdbAdapter; private final MovieRelease movieRelease; private static final Logger LOGGER = LoggerFactory.getLogger(MovieReleaseControl.class); - public MovieReleaseControl(MovieRelease movieRelease, Settings settings, Manager manager, UserInteractionHandler userInteractionHandler) { + public MovieReleaseControl(MovieRelease movieRelease, Settings settings, Manager manager, + UserInteractionHandler userInteractionHandler) { super(settings, manager); this.movieRelease = movieRelease; this.imdbAdapter = ImdbAdapter.getInstance(manager, userInteractionHandler); @@ -35,22 +30,22 @@ public MovieReleaseControl(MovieRelease movieRelease, Settings settings, Manager @Override public void process() throws ReleaseControlException { - if (StringUtils.isBlank(movieRelease.getName())) { + if (StringUtils.isBlank(movieRelease.name)) { throw new ReleaseControlException("Unable to extract/find title, check file", movieRelease); } else { - int imdbId = imdbAdapter.getImdbId(movieRelease.getName(), movieRelease.getYear()) - .orElseThrow(() -> new ReleaseControlException("Movie not found on IMDB, check file", movieRelease)); - movieRelease.setImdbId(imdbId); + movieRelease.imdbId = imdbAdapter.getImdbId(movieRelease.name, movieRelease.year) + .orElseThrow(() -> new ReleaseControlException("Movie not found on IMDB, check file", movieRelease)); - Optional movieDetails = - movieRelease.getImdbId().mapToObj(imdbAdapter::getMovieDetails).orElseGet(Optional::empty); - if (movieDetails.isEmpty()) { - movieDetails = movieRelease.getImdbId().mapToObj(omdbAdapter::getMovieDetails).orElseGet(Optional::empty); + ReleaseDBIntf movieDetails = imdbAdapter.getMovieDetails(movieRelease.imdbId).orElse(null); + if (movieDetails == null) { + movieDetails = omdbAdapter.getMovieDetails(movieRelease.imdbId).orElse(null); + } + if (movieDetails != null) { + movieRelease.year = movieDetails.year; + movieRelease.name = movieDetails.name; + } else { + LOGGER.error("Unable to get details from OMDB API, continue with filename info $movieRelease"); } - movieDetails.ifPresentDo(info -> { - movieRelease.setYear(info.year()); - movieRelease.setName(info.getName()); - }).ifEmptyDo(() -> LOGGER.error("Unable to get details from OMDB API, continue with filename info {}", movieRelease)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/ReleaseControl.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/ReleaseControl.java index 89a84073..7cabaf5c 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/ReleaseControl.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/ReleaseControl.java @@ -1,22 +1,23 @@ package org.lodder.subtools.multisubdownloader.lib.control; +import static manifold.ext.props.rt.api.PropOption.*; + +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.exception.ReleaseControlException; import org.lodder.subtools.sublibrary.model.Release; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; +public abstract sealed class ReleaseControl permits MovieReleaseControl, TvReleaseControl { -@Getter(value = AccessLevel.PROTECTED) -@AllArgsConstructor -public abstract class ReleaseControl { + @val(Protected) Settings settings; + @val(Protected) Manager manager; + @val(Abstract) Release videoFile; - private final Settings settings; - private final Manager manager; + ReleaseControl(Settings settings, Manager manager) { + this.settings = settings; + this.manager = manager; + } public abstract void process() throws ReleaseControlException; - - public abstract Release getVideoFile(); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/TvReleaseControl.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/TvReleaseControl.java index 620dee1a..faba0cc4 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/TvReleaseControl.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/TvReleaseControl.java @@ -1,5 +1,7 @@ package org.lodder.subtools.multisubdownloader.lib.control; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.settings.model.SettingsProcessEpisodeSource; @@ -9,36 +11,33 @@ import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.TvRelease; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.experimental.ExtensionMethod; +public final class TvReleaseControl extends ReleaseControl { -@ExtensionMethod({ OptionalExtension.class }) -public class TvReleaseControl extends ReleaseControl { + private static final Logger LOGGER = LoggerFactory.getLogger(TvReleaseControl.class); private final TheTvdbAdapter jtvdba; private final TvRelease tvRelease; + @val @override Release videoFile; - private static final Logger LOGGER = LoggerFactory.getLogger(TvReleaseControl.class); - - public TvReleaseControl(TvRelease tvRelease, Settings settings, Manager manager, UserInteractionHandler userInteractionHandler) { + public TvReleaseControl(TvRelease tvRelease, Settings settings, Manager manager, + UserInteractionHandler userInteractionHandler) { super(settings, manager); this.tvRelease = tvRelease; + this.videoFile = tvRelease; this.jtvdba = TheTvdbAdapter.getInstance(manager, userInteractionHandler); } @Override public void process() throws ReleaseControlException { - // return episodeFile; - if (StringUtils.isBlank(tvRelease.getName())) { + if (StringUtils.isBlank(tvRelease.name)) { throw new ReleaseControlException("Unable to extract episode details, check file", tvRelease); } else { - LOGGER.debug("process: show name [{}], season [{}], episode [{}]", tvRelease.getName(), - tvRelease.getSeason(), tvRelease.getEpisodeNumbers()); - - if (tvRelease.isSpecial()) { + LOGGER.debug("process: show name [{}], season [{}], episode [{}]", tvRelease.name, tvRelease.season, + tvRelease.episodes); + if (tvRelease.special) { processSpecial(); } else { processTvdb(); @@ -47,30 +46,25 @@ public void process() throws ReleaseControlException { } private void processTvdb() throws ReleaseControlException { - jtvdba.getSerie(tvRelease.getName()).ifPresentOrThrow(tvdbSerie -> { - tvRelease.setTvdbId(tvdbSerie.getId()); - tvRelease.setOriginalName(tvdbSerie.getSerieName()); - jtvdba.getEpisode(tvdbSerie.getId(), tvRelease.getSeason(), tvRelease.getEpisodeNumbers().get(0)) - .ifPresentOrThrow( - tvRelease::updateTvdbEpisodeInfo, - () -> new ReleaseControlException("Season %s Episode %s not found, check file".formatted(tvRelease.getSeason(), - tvRelease.getEpisodeNumbers().toString()), tvRelease)); - }, () -> new ReleaseControlException("Show not found, check file", tvRelease)); + jtvdba.getSerie(tvRelease.name).useIfPresent(tvdbSerie -> { + tvRelease.tvdbId = tvdbSerie.id; + tvRelease.originalName = tvdbSerie.serieName; + jtvdba.getEpisode(tvdbSerie.id, tvRelease.season, tvRelease.firstEpisode) + .useIfPresent(tvRelease::updateTvdbEpisodeInfo) + .orElseThrow(() -> new ReleaseControlException( + "Season ${tvRelease.season} Episode ${tvRelease.episodes} not found, check file", + tvRelease)); + }).orElseThrow(() -> new ReleaseControlException("Show not found, check file", tvRelease)); } private void processSpecial() throws ReleaseControlException { - jtvdba.getSerie(tvRelease.getName()).ifPresentOrThrow(tvdbSerie -> { - tvRelease.setTvdbId(tvdbSerie.getId()); - tvRelease.setOriginalName(tvdbSerie.getSerieName()); - if (getSettings().getProcessEpisodeSource() == SettingsProcessEpisodeSource.TVDB) { - jtvdba.getEpisode(tvdbSerie.getId(), tvRelease.getSeason(), tvRelease.getEpisodeNumbers().get(0)) + jtvdba.getSerie(tvRelease.name).useIfPresent(tvdbSerie -> { + tvRelease.tvdbId = tvdbSerie.id; + tvRelease.originalName = tvdbSerie.serieName; + if (settings.processEpisodeSource == SettingsProcessEpisodeSource.TVDB) { + jtvdba.getEpisode(tvdbSerie.id, tvRelease.season, tvRelease.firstEpisode) .ifPresent(tvRelease::updateTvdbEpisodeInfo); } - }, () -> new ReleaseControlException("Show not found, check file", tvRelease)); - } - - @Override - public Release getVideoFile() { - return tvRelease; + }).orElseThrow(() -> new ReleaseControlException("Show not found, check file", tvRelease)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFiltering.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFiltering.java index e851496d..3418cbed 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFiltering.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFiltering.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters.ExactNameFilter; import org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters.KeywordFilter; -import org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters.ReleasegroupFilter; +import org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters.ReleaseGroupFilter; import org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters.SubtitleFilter; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.sublibrary.model.Release; @@ -15,7 +15,7 @@ public class SubtitleFiltering { private final Settings settings; private final SubtitleFilter exactName = new ExactNameFilter(); private final SubtitleFilter keyword = new KeywordFilter(); - private final SubtitleFilter releaseGroup = new ReleasegroupFilter(); + private final SubtitleFilter releaseGroup = new ReleaseGroupFilter(); public boolean useSubtitle(Subtitle subtitle, Release release) { return !excludeSubtitle(subtitle, release); @@ -23,20 +23,20 @@ public boolean useSubtitle(Subtitle subtitle, Release release) { public boolean excludeSubtitle(Subtitle subtitle, Release release) { return excludeSubtitleHearingImpaired(subtitle, release) - || excludeSubtitleKeywordMatch(subtitle, release) - || excludeSubtitleExactMatch(subtitle, release); + || excludeSubtitleKeywordMatch(subtitle, release) + || excludeSubtitleExactMatch(subtitle, release); } private boolean excludeSubtitleHearingImpaired(Subtitle subtitle, Release release) { - return settings.isOptionSubtitleExcludeHearingImpaired() && subtitle.isHearingImpaired(); + return settings.optionSubtitleExcludeHearingImpaired && subtitle.hearingImpaired; } private boolean excludeSubtitleKeywordMatch(Subtitle subtitle, Release release) { - return settings.isOptionSubtitleKeywordMatch() && - (keyword.excludeSubtitle(release, subtitle) || releaseGroup.excludeSubtitle(release, subtitle)); + return settings.optionSubtitleKeywordMatch && + (keyword.excludeSubtitle(release, subtitle) || releaseGroup.excludeSubtitle(release, subtitle)); } private boolean excludeSubtitleExactMatch(Subtitle subtitle, Release release) { - return settings.isOptionSubtitleExactMatch() && exactName.excludeSubtitle(release, subtitle); + return settings.optionSubtitleExactMatch && exactName.excludeSubtitle(release, subtitle); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ExactNameFilter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ExactNameFilter.java index 04af46a9..1bb408fe 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ExactNameFilter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ExactNameFilter.java @@ -10,7 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ExactNameFilter extends SubtitleFilter { +public final class ExactNameFilter extends SubtitleFilter { private static final Logger LOGGER = LoggerFactory.getLogger(ExactNameFilter.class); @@ -18,11 +18,11 @@ public class ExactNameFilter extends SubtitleFilter { @Override public boolean useSubtitle(Release release, Subtitle subtitle) { - Pattern p = patterns.computeIfAbsent(getReleaseName(release), k -> + Pattern p = patterns.computeIfAbsent(getReleaseName(release), _ -> Pattern.compile(getReleaseName(release).replace(" ", "[. ]"), Pattern.CASE_INSENSITIVE)); - if (p.matcher(subtitle.getFileName().toLowerCase().replace(".srt", "")).matches()) { - LOGGER.debug("getSubtitlesFiltered: found EXACT match [{}] ", subtitle.getFileName()); - subtitle.setSubtitleMatchType(SubtitleMatchType.EXACT); + if (p.matcher(subtitle.fileName.toLowerCase().replace(".srt", "")).matches()) { + LOGGER.debug("getSubtitlesFiltered: found EXACT match [{}] ", subtitle.fileName); + subtitle.subtitleMatchType = SubtitleMatchType.EXACT; return true; } return false; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/KeywordFilter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/KeywordFilter.java index e0136721..2e47d5df 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/KeywordFilter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/KeywordFilter.java @@ -1,5 +1,6 @@ package org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters; +import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.sublibrary.control.ReleaseParser; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; @@ -7,24 +8,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class KeywordFilter extends SubtitleFilter { +public final class KeywordFilter extends SubtitleFilter { private static final Logger LOGGER = LoggerFactory.getLogger(KeywordFilter.class); - @Override public boolean useSubtitle(Release release, Subtitle subtitle) { String keywordsFile = ReleaseParser.getQualityKeyword(getReleaseName(release)); - if (subtitle.getQuality().isEmpty()) { - subtitle.setQuality(ReleaseParser.getQualityKeyword(subtitle.getFileName())); + if (StringUtils.isEmpty(subtitle.quality)) { + subtitle.quality = ReleaseParser.getQualityKeyword(subtitle.fileName); } - if(!checkKeywordSubtitleMatch(subtitle, keywordsFile)){ + if (!checkKeywordSubtitleMatch(subtitle, keywordsFile)) { return false; } - LOGGER.debug("getSubtitlesFiltered: found KEYWORD match [{}] ", subtitle.getFileName()); - subtitle.setSubtitleMatchType(SubtitleMatchType.KEYWORD); - return true; + LOGGER.debug("getSubtitlesFiltered: found KEYWORD match [{}] ", subtitle.fileName); + subtitle.subtitleMatchType = SubtitleMatchType.KEYWORD; + return true; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ReleasegroupFilter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ReleaseGroupFilter.java similarity index 54% rename from MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ReleasegroupFilter.java rename to MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ReleaseGroupFilter.java index efc3f00e..7add5191 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ReleasegroupFilter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/ReleaseGroupFilter.java @@ -1,6 +1,5 @@ package org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters; -import lombok.experimental.ExtensionMethod; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.sublibrary.control.ReleaseParser; import org.lodder.subtools.sublibrary.model.Release; @@ -9,21 +8,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@ExtensionMethod({StringUtils.class}) -public class ReleasegroupFilter extends SubtitleFilter { +public final class ReleaseGroupFilter extends SubtitleFilter { - private static final Logger LOGGER = LoggerFactory.getLogger(ReleasegroupFilter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ReleaseGroupFilter.class); @Override public boolean useSubtitle(Release release, Subtitle subtitle) { - if (subtitle.getReleaseGroup().isEmpty()) { - subtitle.setReleaseGroup(ReleaseParser.extractReleasegroup(subtitle.getFileName(), subtitle.getFileName().endsWith(".srt"))); + if (StringUtils.isEmpty(subtitle.releaseGroup)) { + subtitle.releaseGroup = + ReleaseParser.extractReleaseGroup(subtitle.fileName, StringUtils.endsWith(subtitle.fileName, ".srt")); } - if(!StringUtils.containsAnyIgnoreCase(subtitle.getReleaseGroup(), release.getReleaseGroup(), subtitle.getReleaseGroup())){ + if (!StringUtils.containsAnyIgnoreCase(subtitle.releaseGroup, release.releaseGroup, subtitle.releaseGroup)) { return false; } - LOGGER.debug("getSubtitlesFiltered: found KEYWORD based TEAM match [{}] ", subtitle.getFileName()); - subtitle.setSubtitleMatchType(SubtitleMatchType.TEAM); + LOGGER.debug("getSubtitlesFiltered: found KEYWORD based TEAM match [{}] ", subtitle.fileName); + subtitle.subtitleMatchType = SubtitleMatchType.TEAM; return true; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/SubtitleFilter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/SubtitleFilter.java index b951626a..c0a474c2 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/SubtitleFilter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/filters/SubtitleFilter.java @@ -1,25 +1,23 @@ package org.lodder.subtools.multisubdownloader.lib.control.subtitles.filters; -import java.util.Arrays; - import org.lodder.subtools.sublibrary.control.ReleaseParser; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -public abstract class SubtitleFilter { +public abstract sealed class SubtitleFilter permits ExactNameFilter, KeywordFilter, ReleaseGroupFilter { public abstract boolean useSubtitle(Release release, Subtitle subtitle); - public boolean excludeSubtitle(Release release, Subtitle subtitle){ + public boolean excludeSubtitle(Release release, Subtitle subtitle) { return !useSubtitle(release, subtitle); } protected String getReleaseName(Release release) { - return release.getFileName() == null ? "" : release.getFileName().toLowerCase().replace("." + release.getExtension(), ""); + return release.fileName == null ? "" : release.fileName.toLowerCase().replace("." + release.extension, ""); } protected boolean checkKeywordSubtitleMatch(Subtitle subtitle, String keywordsFile) { - String keywordsSub = ReleaseParser.getQualityKeyword(subtitle.getFileName()); + String keywordsSub = ReleaseParser.getQualityKeyword(subtitle.fileName); return keywordsFile.equalsIgnoreCase(keywordsSub) || keywordCheck(keywordsFile, keywordsSub); } @@ -54,6 +52,6 @@ private boolean keywordCheck(String videoFileName, String subFileName) { } private boolean containsBoth(String string1, String string2, String... values) { - return Arrays.stream(values).allMatch(string1::contains) && Arrays.stream(values).allMatch(string2::contains); + return values.stream().allMatch(string1::contains) && values.stream().allMatch(string2::contains); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculator.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculator.java index c50555e7..9eb701c9 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculator.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculator.java @@ -11,13 +11,13 @@ public ScoreCalculator(SortWeight weights) { } public int calculate(Subtitle subtitle) { - if (weights.getMaxScore() <= 0) { + if (weights.maxScore <= 0) { return 0; } - - String subtitleInfo = "%s %s %s".formatted(subtitle.getFileName(), subtitle.getQuality(), subtitle.getReleaseGroup()).trim().toLowerCase(); - - int score = weights.getWeights().keySet().stream().filter(subtitleInfo::contains).mapToInt(weights.getWeights()::get).sum(); - return (int) Math.ceil((float) score / weights.getMaxScore() * 100); + String subtitleInfo = + "%s %s %s".formatted(subtitle.fileName, subtitle.quality, subtitle.releaseGroup).trim().toLowerCase(); + int score = + weights.weights.keySet().stream().filter(subtitleInfo::contains).mapToInt(weights.weights::get).sum(); + return (int) Math.ceil((float) score / weights.maxScore * 100); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeight.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeight.java index fc732bc0..50855a3b 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeight.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeight.java @@ -1,23 +1,27 @@ package org.lodder.subtools.multisubdownloader.lib.control.subtitles.sorting; +import static manifold.ext.props.rt.api.PropOption.*; + +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; -import lombok.Getter; +import manifold.ext.props.rt.api.get; +import manifold.ext.props.rt.api.set; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.lib.control.subtitles.sorting.replacers.GroupReplacer; import org.lodder.subtools.multisubdownloader.lib.control.subtitles.sorting.replacers.KeywordReplacer; import org.lodder.subtools.sublibrary.control.ReleaseParser; import org.lodder.subtools.sublibrary.model.Release; -@Getter public class SortWeight { private static final List KEYWORD_REPLACERS = List.of(new GroupReplacer()); - private Map weights; - protected int maxScore; + @get Map weights = new HashMap<>(); + @get @set(Private) int maxScore; public SortWeight(Release release, Map defaultWeights) { this.setWeights(release, defaultWeights); @@ -25,28 +29,30 @@ public SortWeight(Release release, Map defaultWeights) { protected void setWeights(Release release, Map defaultWeights) { this.maxScore = 0; - this.weights = new HashMap<>(); + this.weights.clear(); /* make a clone, so we can't mess up the defined weights */ - defaultWeights = new HashMap<>(defaultWeights); // clone + Map defaultWeightsNew = new HashMap<>(defaultWeights); // clone - replaceReservedKeywords(release, defaultWeights); + replaceReservedKeywords(release, defaultWeightsNew); /* get a list of tags */ - List tags = ReleaseParser.getQualityKeyWords(release.getQuality()); - if (StringUtils.isNotBlank(release.getReleaseGroup())) { - tags.add(release.getReleaseGroup().toLowerCase()); + List tags = new ArrayList<>(ReleaseParser.getQualityKeyWords(release.quality)); + if (StringUtils.isNotBlank(release.releaseGroup)) { + tags.add(release.releaseGroup.toLowerCase()); } - /* only store tags for which we have a weight defined */ - tags.retainAll(defaultWeights.keySet()); - /* store weights for this release */ - for (String tag : tags) { - int weight = defaultWeights.get(tag); - this.maxScore += weight; - this.weights.put(tag, weight); - } + tags.forEach(tag -> + defaultWeightsNew.entrySet().stream() + /* only store tags for which we have a weight defined */ + .filter(entry -> Pattern.compile(entry.getKey()).matcher(tag).find()) + .map(Map.Entry::getValue) + .findFirst() + .ifPresent(weight -> { + this.maxScore += weight; + this.weights.put(tag, weight); + })); } private void replaceReservedKeywords(Release release, Map weights) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SubtitleComparator.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SubtitleComparator.java index 09ad5f68..a83f0b39 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SubtitleComparator.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SubtitleComparator.java @@ -14,6 +14,6 @@ public class SubtitleComparator implements Comparator, Serializable { @Override public int compare(Subtitle a, Subtitle b) { /* inverse sorting */ - return Integer.compare(b.getScore(), a.getScore()); + return Integer.compare(b.score, a.score); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacer.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacer.java index cf46a3ef..be7a412b 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacer.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacer.java @@ -19,7 +19,7 @@ public void replace(Release release, Map weights) { weights.remove(reservedKey); /* add replaced value */ - String group = StringUtils.lowerCase(release.getReleaseGroup()); + String group = StringUtils.lowerCase(release.releaseGroup); weights.put(group, weight); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/FilenameLibraryBuilder.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/FilenameLibraryBuilder.java index 9414e3e4..70b5e18b 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/FilenameLibraryBuilder.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/FilenameLibraryBuilder.java @@ -4,21 +4,20 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.settings.model.LibrarySettings; +import org.lodder.subtools.multisubdownloader.settings.model.structure.MovieStructureTag; import org.lodder.subtools.multisubdownloader.settings.model.structure.SerieStructureTag; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.data.tvdb.TheTvdbAdapter; +import org.lodder.subtools.sublibrary.model.MovieRelease; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; import org.lodder.subtools.sublibrary.model.TvRelease; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.StringUtil; -import lombok.Setter; -import lombok.experimental.Accessors; - -public class FilenameLibraryBuilder extends LibraryBuilder { +public final class FilenameLibraryBuilder extends LibraryBuilder { private final String structure; private final boolean replaceSpace; @@ -27,9 +26,10 @@ public class FilenameLibraryBuilder extends LibraryBuilder { private final Map languageTags; private final boolean rename; - private FilenameLibraryBuilder(String structure, boolean replaceSpace, char replacingSpaceChar, boolean includeLanguageCode, - Map languageTags, boolean useTvdb, TheTvdbAdapter tvdbAdapter, boolean rename) { - super(useTvdb, tvdbAdapter); + public FilenameLibraryBuilder(String structure, boolean replaceSpace, char replacingSpaceChar, + boolean includeLanguageCode, Map languageTags, TheTvdbAdapter tvdbAdapter=null, + boolean rename) { + super(tvdbAdapter); this.structure = structure; this.replaceSpace = replaceSpace; this.replacingSpaceChar = replacingSpaceChar; @@ -38,145 +38,86 @@ private FilenameLibraryBuilder(String structure, boolean replaceSpace, char repl this.rename = rename; } - public static FilenameLibraryBuilder fromSettings(LibrarySettings librarySettings, Manager manager, - UserInteractionHandler userInteractionHandler) { - return FilenameLibraryBuilder.builder() - .structure(librarySettings.getLibraryFolderStructure()) - .replaceSpace(librarySettings.isLibraryFolderReplaceSpace()) - .replacingSpaceChar(librarySettings.getLibraryFolderReplacingSpaceChar()) - .includeLanguageCode(librarySettings.isLibraryIncludeLanguageCode()) - .languageTags(librarySettings.getLangCodeMap()) - .useTvdbName(librarySettings.isLibraryUseTVDBNaming()) - .tvdbAdapter(TheTvdbAdapter.getInstance(manager, userInteractionHandler)) - .rename(librarySettings.hasAnyLibraryAction(LibraryActionType.RENAME, LibraryActionType.MOVEANDRENAME)) - .build(); - } - - public static FilenameLibraryBuilderStructureIntf builder() { - return new FilenameLibraryBuilderBuilder(); - } - - public interface FilenameLibraryBuilderStructureIntf { - FilenameLibraryBuilderReplaceSpaceIntf structure(String structure); - } - - public interface FilenameLibraryBuilderReplaceSpaceIntf { - FilenameLibraryBuilderReplaceSpaceCharIntf replaceSpace(boolean replaceSpace); - } - - public interface FilenameLibraryBuilderReplaceSpaceCharIntf { - FilenameLibraryBuilderIncludeLanguageCodeIntf replacingSpaceChar(char replacingSpaceChar); - } - - public interface FilenameLibraryBuilderIncludeLanguageCodeIntf { - FilenameLibraryBuilderLanguageTagIntf includeLanguageCode(boolean includeLanguageCode); - } - - public interface FilenameLibraryBuilderLanguageTagIntf { - FilenameLibraryBuilderUseTvdbNameIntf languageTags(Map languageTags); - } - - public interface FilenameLibraryBuilderUseTvdbNameIntf extends FilenameLibraryBuilderBuildIntf { - FilenameLibraryBuilderTvdbAdapterIntf useTvdbName(boolean useTvdbName); - } - - public interface FilenameLibraryBuilderTvdbAdapterIntf { - FilenameLibraryBuilderRenameIntf tvdbAdapter(TheTvdbAdapter tvdbAdapter); - } - - public interface FilenameLibraryBuilderRenameIntf { - FilenameLibraryBuilderBuildIntf rename(boolean rename); - } - - public interface FilenameLibraryBuilderBuildIntf { - FilenameLibraryBuilder build(); - } - - @Setter - @Accessors(chain = true, fluent = true) - public static class FilenameLibraryBuilderBuilder implements - FilenameLibraryBuilderStructureIntf, - FilenameLibraryBuilderReplaceSpaceIntf, - FilenameLibraryBuilderReplaceSpaceCharIntf, - FilenameLibraryBuilderIncludeLanguageCodeIntf, - FilenameLibraryBuilderLanguageTagIntf, - FilenameLibraryBuilderUseTvdbNameIntf, - FilenameLibraryBuilderTvdbAdapterIntf, - FilenameLibraryBuilderRenameIntf, - FilenameLibraryBuilderBuildIntf { - private String structure; - - private boolean replaceSpace; - private char replacingSpaceChar; - - private boolean includeLanguageCode; - private Map languageTags; - - private boolean useTvdbName; - private TheTvdbAdapter tvdbAdapter; - - private boolean rename; - - @Override - public FilenameLibraryBuilder build() { - return new FilenameLibraryBuilder(structure, replaceSpace, replacingSpaceChar, includeLanguageCode, languageTags, useTvdbName, - tvdbAdapter, rename); - } + public static FilenameLibraryBuilder fromSettings(LibrarySettings libSettings, Manager manager, + UserInteractionHandler userInteractionHandler) { + return new FilenameLibraryBuilder( + structure:libSettings.folderStructure, + replaceSpace:libSettings.folderReplaceSpace, + replacingSpaceChar:libSettings.folderReplacingSpaceChar, + includeLanguageCode:libSettings.includeLanguageCode, + languageTags:libSettings.langCodeMap, + tvdbAdapter:libSettings.useTVDBNaming ? TheTvdbAdapter.getInstance(manager, userInteractionHandler) : null, + rename:libSettings.hasAnyLibraryAction(LibraryActionType.RENAME, LibraryActionType.MOVEANDRENAME)); } @Override public Path build(Release release) { - if (rename) { - String filename; - if (release instanceof TvRelease tvRelease && StringUtils.isNotBlank(structure)) { - filename = structure; - // order is important! - filename = replace(filename, SerieStructureTag.SHOW_NAME, getShowName(tvRelease.getName())); - filename = replaceFormattedEpisodeNumber(filename, SerieStructureTag.EPISODES_LONG, tvRelease.getEpisodeNumbers(), true); - filename = replaceFormattedEpisodeNumber(filename, SerieStructureTag.EPISODES_SHORT, tvRelease.getEpisodeNumbers(), false); - filename = replace(filename, SerieStructureTag.SEASON_LONG, formattedNumber(tvRelease.getSeason(), true)); - filename = replace(filename, SerieStructureTag.SEASON_SHORT, formattedNumber(tvRelease.getSeason(), false)); - filename = replace(filename, SerieStructureTag.EPISODE_LONG, formattedNumber(tvRelease.getEpisodeNumbers().get(0), true)); - filename = replace(filename, SerieStructureTag.EPISODE_SHORT, formattedNumber(tvRelease.getEpisodeNumbers().get(0), false)); - filename = replace(filename, SerieStructureTag.TITLE, tvRelease.getTitle()); - filename = replace(filename, SerieStructureTag.QUALITY, release.getQuality()); - filename = replace(filename, SerieStructureTag.DESCRIPTION, release.getDescription()); - - filename += "." + release.getExtension(); - } else { - filename = release.getFileName(); - } - filename = StringUtil.removeIllegalWindowsChars(filename); + if (rename && StringUtils.isNotBlank(structure)) { + String filename = switch (release) { + case TvRelease tvRelease -> { + String fName = structure; + // order is important! + fName = replace(fName, SerieStructureTag.SHOW_NAME, getShowName(tvRelease.name)); + fName = + replaceFormattedEpisodeNumber(fName, SerieStructureTag.EPISODES_LONG, tvRelease.episodes, true); + fName = replaceFormattedEpisodeNumber(fName, SerieStructureTag.EPISODES_SHORT, tvRelease.episodes, + false); + fName = replace(fName, SerieStructureTag.SEASON_LONG, formatNumber(tvRelease.season, true)); + fName = replace(fName, SerieStructureTag.SEASON_SHORT, formatNumber(tvRelease.season, false)); + fName = replace(fName, SerieStructureTag.EPISODE_LONG, formatNumber(tvRelease.firstEpisode, true)); + fName = + replace(fName, SerieStructureTag.EPISODE_SHORT, formatNumber(tvRelease.firstEpisode, false)); + fName = replace(fName, SerieStructureTag.TITLE, tvRelease.title); + fName = replace(fName, SerieStructureTag.QUALITY, release.quality); + fName = replace(fName, SerieStructureTag.RELEASE_GROUP, release.releaseGroup); + + fName += "." + release.extension; + yield fName; + } + case MovieRelease movieRelease -> { + String fName = structure; + // order is important! + fName = replace(fName, MovieStructureTag.MOVIE_TITLE, getShowName(movieRelease.name)); + fName = replace(fName, MovieStructureTag.YEAR, formatNumber(movieRelease.year, false)); + fName = replace(fName, MovieStructureTag.QUALITY, release.quality); + fName = replace(fName, MovieStructureTag.RELEASE_GROUP, release.releaseGroup); + + fName += "." + release.extension; + yield fName; + } + }; + + filename = filename.removeIllegalWindowsChars(); if (replaceSpace) { filename = filename.replace(' ', replacingSpaceChar); } return Path.of(filename); } else { - return Path.of(release.getFileName()); + return Path.of(release.fileName); } } - public String buildSubtitle(Release release, Subtitle sub, String filename, Integer version) { - return buildSubtitle(release, filename, sub.getLanguage(), version); + public String buildSubtitle(Release release, Subtitle sub, String filename, @Nullable Integer version) { + return buildSubtitle(release, filename, sub.language, version); } - public String buildSubtitle(Release release, String filename, Language language, Integer version) { - final String extension = "." + release.getExtension(); + public String buildSubtitle(Release release, String filename, Language language, @Nullable Integer version) { + final String extension = "." + release.extension; + String subFileName = filename; if (version != null) { - filename = filename.substring(0, filename.indexOf(extension)) + "-v" + version + "." + release.getExtension(); + subFileName = subFileName.substring(0, subFileName.indexOf(extension)) + "-v$version.${release.extension}"; } if (includeLanguageCode) { - String langCode = language == null ? "" : languageTags.getOrDefault(language, language.getLangCode()); - filename = changeExtension(filename, !"".equals(langCode) ? ".%s.srt".formatted(langCode) : ".srt"); + String langCode = language == null ? "" : languageTags.getOrDefault(language, language.langCode); + subFileName = changeExtension(subFileName, !"".equals(langCode) ? ".$langCode.srt" : ".srt"); } else { - filename = changeExtension(filename, ".srt"); + subFileName = changeExtension(subFileName, ".srt"); } - - filename = StringUtil.removeIllegalWindowsChars(filename); + subFileName = subFileName.removeIllegalWindowsChars(); if (replaceSpace) { - filename = filename.replace(' ', replacingSpaceChar); + subFileName = subFileName.replace(' ', replacingSpaceChar); } - return filename; + return subFileName; } /** diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryActionType.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryActionType.java index 2d7bff39..22e877ef 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryActionType.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryActionType.java @@ -1,24 +1,21 @@ package org.lodder.subtools.multisubdownloader.lib.library; -import java.util.Arrays; - import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum LibraryActionType { NOTHING("PreferenceDialog.Action.Nothing"), RENAME("PreferenceDialog.Action.Rename"), MOVE("PreferenceDialog.Action.Move"), MOVEANDRENAME("PreferenceDialog.Action.MoveAndRename"); - @Getter - private final String msgCode; + @val String msgCode; @Deprecated(since = "Settings version 2") public static LibraryActionType fromString(String description) { - return Arrays.stream(LibraryActionType.values()) + return LibraryActionType.values().stream() .filter(v -> description.equalsIgnoreCase(v.toString())).findAny() .orElse(LibraryActionType.NOTHING); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryBuilder.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryBuilder.java index f0ccd5f9..98eba8d0 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryBuilder.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryBuilder.java @@ -4,50 +4,47 @@ import java.util.List; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.settings.model.structure.StructureTag; import org.lodder.subtools.sublibrary.data.tvdb.TheTvdbAdapter; import org.lodder.subtools.sublibrary.data.tvdb.model.TheTvdbSerie; import org.lodder.subtools.sublibrary.model.Release; -import org.lodder.subtools.sublibrary.util.OptionalExtension; - -import lombok.RequiredArgsConstructor; -import lombok.experimental.ExtensionMethod; @RequiredArgsConstructor -@ExtensionMethod({ OptionalExtension.class, StringUtils.class }) -public abstract class LibraryBuilder { +public abstract sealed class LibraryBuilder permits FilenameLibraryBuilder, PathLibraryBuilder { - private final boolean useTvdb; - private final TheTvdbAdapter tvdbAdapter; + private final @Nullable TheTvdbAdapter tvdbAdapter; public abstract Path build(Release release); protected String getShowName(String name) { - return useTvdb ? tvdbAdapter.getSerie(name).map(TheTvdbSerie::getSerieName).orElse(name) : name; + return tvdbAdapter != null ? tvdbAdapter.getSerie(name).map(TheTvdbSerie::getSerieName).orElse(name) : name; } protected String replace(String structure, StructureTag tag, String value) { - return structure.replace(tag.getLabel(), value); + return structure.replace(tag.label, value); } - protected String replaceFormattedEpisodeNumber(String structure, StructureTag tag, List episodeNumbers, boolean leadingZero) { - if (structure.contains(tag.getLabel())) { - String afterLabel = structure.substringAfter(tag.getLabel()); - String separator = afterLabel.isNotEmpty() ? afterLabel.substring(0, 1) : ""; + protected String replaceFormattedEpisodeNumber(String structure, StructureTag tag, List episodeNumbers, + boolean leadingZero) { + if (structure.contains(tag.label)) { + String afterLabel = StringUtils.substringAfter(structure, tag.label); + String separator = StringUtils.isNotEmpty(afterLabel) ? afterLabel.substring(0, 1) : ""; if ("%".equals(separator)) { separator = ""; } String formattedEpisodeNumber = episodeNumbers.stream() - .map(episode -> formattedNumber(episode, leadingZero)) - .collect(Collectors.joining(separator)); - return structure.replace(tag.getLabel(), formattedEpisodeNumber); + .map(episode -> formatNumber(episode, leadingZero)) + .collect(Collectors.joining(separator)); + return structure.replace(tag.label, formattedEpisodeNumber); } return structure; } - protected String formattedNumber(int number, boolean leadingZero) { + protected String formatNumber(int number, boolean leadingZero) { return number < 10 && leadingZero ? "0" + number : Integer.toString(number); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryOtherFileActionType.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryOtherFileActionType.java index 8a8410a0..9dcbe428 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryOtherFileActionType.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/LibraryOtherFileActionType.java @@ -1,11 +1,8 @@ package org.lodder.subtools.multisubdownloader.lib.library; -import java.util.Arrays; - -import org.lodder.subtools.multisubdownloader.Messages; - import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import org.lodder.subtools.multisubdownloader.Messages; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum LibraryOtherFileActionType { @@ -20,12 +17,12 @@ public enum LibraryOtherFileActionType { @Override public String toString() { - return Messages.getString(msgCode); + return Messages.getText(msgCode); } @Deprecated(since = "Settings version 2") public static LibraryOtherFileActionType fromString(String description) { - return Arrays.stream(LibraryOtherFileActionType.values()) + return LibraryOtherFileActionType.values().stream() .filter(v -> description.equalsIgnoreCase(v.toString())).findAny() .orElse(LibraryOtherFileActionType.NOTHING); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/PathLibraryBuilder.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/PathLibraryBuilder.java index 5aa2b9fc..c16f8952 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/PathLibraryBuilder.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/lib/library/PathLibraryBuilder.java @@ -3,6 +3,7 @@ import java.nio.file.Path; import java.nio.file.Paths; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.settings.model.LibrarySettings; import org.lodder.subtools.multisubdownloader.settings.model.structure.FolderStructureTag; import org.lodder.subtools.multisubdownloader.settings.model.structure.MovieStructureTag; @@ -13,12 +14,8 @@ import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.TvRelease; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.StringUtil; -import lombok.Setter; -import lombok.experimental.Accessors; - -public class PathLibraryBuilder extends LibraryBuilder { +public final class PathLibraryBuilder extends LibraryBuilder { private final String structure; private final boolean replaceSpace; @@ -26,9 +23,9 @@ public class PathLibraryBuilder extends LibraryBuilder { private final Path libraryFolder; private final boolean move; - private PathLibraryBuilder(String structure, boolean replaceSpace, char replacingSpaceChar, boolean useTvdb, TheTvdbAdapter tvdbAdapter, - Path libraryFolder, boolean move) { - super(useTvdb, tvdbAdapter); + public PathLibraryBuilder(String structure, boolean replaceSpace, char replacingSpaceChar, + @Nullable TheTvdbAdapter tvdbAdapter=null, Path libraryFolder, boolean move) { + super(tvdbAdapter); this.structure = structure; this.replaceSpace = replaceSpace; this.replacingSpaceChar = replacingSpaceChar; @@ -36,94 +33,24 @@ private PathLibraryBuilder(String structure, boolean replaceSpace, char replacin this.move = move; } - public static PathLibraryBuilder fromSettings(LibrarySettings librarySettings, Manager manager, UserInteractionHandler userInteractionHandler) { - return PathLibraryBuilder.builder() - .structure(librarySettings.getLibraryFolderStructure()) - .replaceSpace(librarySettings.isLibraryFolderReplaceSpace()) - .replacingSpaceChar(librarySettings.getLibraryFolderReplacingSpaceChar()) - .useTvdbName(librarySettings.isLibraryUseTVDBNaming()) - .tvdbAdapter(TheTvdbAdapter.getInstance(manager, userInteractionHandler)) - .libraryFolder(librarySettings.getLibraryFolder()) - .move(librarySettings.hasAnyLibraryAction(LibraryActionType.MOVE, LibraryActionType.MOVEANDRENAME)) - .build(); - } - - public static PathLibraryBuilderStructureIntf builder() { - return new PathLibraryBuilderBuilder(); - } - - public interface PathLibraryBuilderStructureIntf { - PathLibraryBuilderReplaceSpaceIntf structure(String structure); - } - - public interface PathLibraryBuilderReplaceSpaceIntf { - PathLibraryBuilderReplaceSpaceCharIntf replaceSpace(boolean replaceSpace); - } - - public interface PathLibraryBuilderReplaceSpaceCharIntf { - PathLibraryBuilderUseTvdbNameIntf replacingSpaceChar(char replacingSpaceChar); - } - - public interface PathLibraryBuilderUseTvdbNameIntf extends PathLibraryBuilderBuildIntf { - PathLibraryBuilderTvdbAdapterIntf useTvdbName(boolean useTvdbName); - } - - public interface PathLibraryBuilderTvdbAdapterIntf { - PathLibraryBuilderLibraryFolderIntf tvdbAdapter(TheTvdbAdapter tvdbAdapter); - } - - public interface PathLibraryBuilderLibraryFolderIntf { - PathLibraryBuilderMoveIntf libraryFolder(Path libraryFolder); - } - - public interface PathLibraryBuilderMoveIntf { - PathLibraryBuilderBuildIntf move(boolean move); - } - - public interface PathLibraryBuilderBuildIntf { - PathLibraryBuilder build(); - } - - @Setter - @Accessors(chain = true, fluent = true) - public static class PathLibraryBuilderBuilder implements - PathLibraryBuilderStructureIntf, - PathLibraryBuilderReplaceSpaceIntf, - PathLibraryBuilderReplaceSpaceCharIntf, - PathLibraryBuilderUseTvdbNameIntf, - PathLibraryBuilderTvdbAdapterIntf, - PathLibraryBuilderLibraryFolderIntf, - PathLibraryBuilderMoveIntf, - PathLibraryBuilderBuildIntf { - private String structure; - - private boolean replaceSpace; - private char replacingSpaceChar; - - private boolean useTvdbName; - private TheTvdbAdapter tvdbAdapter; - - private Path libraryFolder; - - private boolean move; - - @Override - public PathLibraryBuilder build() { - return new PathLibraryBuilder(structure, replaceSpace, replacingSpaceChar, useTvdbName, tvdbAdapter, libraryFolder, move); - } + public static PathLibraryBuilder fromSettings(LibrarySettings libSettings, Manager manager, + UserInteractionHandler userInteractionHandler) { + return new PathLibraryBuilder( + structure:libSettings.folderStructure, + replaceSpace:libSettings.folderReplaceSpace, + replacingSpaceChar:libSettings.folderReplacingSpaceChar, + tvdbAdapter:libSettings.useTVDBNaming ? TheTvdbAdapter.getInstance(manager, userInteractionHandler) : null, + libraryFolder:libSettings.folder, + move:libSettings.hasAnyLibraryAction(LibraryActionType.MOVE, LibraryActionType.MOVEANDRENAME)); } @Override public Path build(Release release) { if (move) { - Path subpath; - if (release instanceof TvRelease tvRelease) { - subpath = buildEpisode(tvRelease); - } else if (release instanceof MovieRelease movieRelease) { - subpath = buildMovie(movieRelease); - } else { - subpath = Path.of(""); - } + Path subpath = switch (release) { + case TvRelease tvRelease -> buildEpisode(tvRelease); + case MovieRelease movieRelease -> buildMovie(movieRelease); + }; return libraryFolder.resolve(subpath); } else { return release.getPath(); @@ -133,34 +60,35 @@ public Path build(Release release) { private Path buildEpisode(TvRelease tvRelease) { String folder = structure; - folder = folder.replace(SerieStructureTag.SHOW_NAME.getLabel(), StringUtil.removeIllegalWindowsChars(getShowName(tvRelease.getName()))); + folder = folder.replace(SerieStructureTag.SHOW_NAME.label, getShowName(tvRelease.name)) + .removeIllegalWindowsChars(); // order is important! - folder = replaceFormattedEpisodeNumber(folder, SerieStructureTag.EPISODES_LONG, tvRelease.getEpisodeNumbers(), true); - folder = replaceFormattedEpisodeNumber(folder, SerieStructureTag.EPISODES_SHORT, tvRelease.getEpisodeNumbers(), false); - folder = replace(folder, SerieStructureTag.SEASON_LONG, formattedNumber(tvRelease.getSeason(), true)); - folder = replace(folder, SerieStructureTag.SEASON_SHORT, formattedNumber(tvRelease.getSeason(), false)); - folder = replace(folder, SerieStructureTag.EPISODE_LONG, formattedNumber(tvRelease.getEpisodeNumbers().get(0), true)); - folder = replace(folder, SerieStructureTag.EPISODE_SHORT, formattedNumber(tvRelease.getEpisodeNumbers().get(0), false)); - folder = replace(folder, SerieStructureTag.TITLE, tvRelease.getTitle()); - folder = replace(folder, SerieStructureTag.QUALITY, tvRelease.getQuality()); - folder = replace(folder, SerieStructureTag.DESCRIPTION, tvRelease.getDescription()); + folder = replaceFormattedEpisodeNumber(folder, SerieStructureTag.EPISODES_LONG, tvRelease.episodes, true); + folder = replaceFormattedEpisodeNumber(folder, SerieStructureTag.EPISODES_SHORT, tvRelease.episodes, false); + folder = replace(folder, SerieStructureTag.SEASON_LONG, formatNumber(tvRelease.season, true)); + folder = replace(folder, SerieStructureTag.SEASON_SHORT, formatNumber(tvRelease.season, false)); + folder = replace(folder, SerieStructureTag.EPISODE_LONG, formatNumber(tvRelease.firstEpisode, true)); + folder = replace(folder, SerieStructureTag.EPISODE_SHORT, formatNumber(tvRelease.firstEpisode, false)); + folder = replace(folder, SerieStructureTag.TITLE, tvRelease.title); + folder = replace(folder, SerieStructureTag.QUALITY, tvRelease.quality); + folder = replace(folder, SerieStructureTag.RELEASE_GROUP, tvRelease.releaseGroup); if (replaceSpace) { folder = folder.replace(' ', replacingSpaceChar); } folder = folder.trim(); - return Paths.get("", folder.split(FolderStructureTag.SEPARATOR.getLabel())); + return Paths.get("", folder.split(FolderStructureTag.SEPARATOR.label)); } private Path buildMovie(MovieRelease movieRelease) { String folder = structure; - folder = replace(folder, MovieStructureTag.MOVIE_TITLE, StringUtil.removeIllegalWindowsChars(movieRelease.getName())); - folder = replace(folder, MovieStructureTag.YEAR, Integer.toString(movieRelease.getYear())); - folder = replace(folder, MovieStructureTag.QUALITY, movieRelease.getQuality()); + folder = replace(folder, MovieStructureTag.MOVIE_TITLE, movieRelease.name.removeIllegalWindowsChars()); + folder = replace(folder, MovieStructureTag.YEAR, Integer.toString(movieRelease.year)); + folder = replace(folder, MovieStructureTag.QUALITY, movieRelease.quality); if (replaceSpace) { folder = folder.replace(' ', replacingSpaceChar); } folder = folder.trim(); - return Paths.get("", folder.split(FolderStructureTag.SEPARATOR.getLabel())); + return Paths.get("", folder.split(FolderStructureTag.SEPARATOR.label)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/Addic7edServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/Addic7edServiceProvider.java index 1ad0d64c..729b4802 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/Addic7edServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/Addic7edServiceProvider.java @@ -1,36 +1,33 @@ package org.lodder.subtools.multisubdownloader.serviceproviders; -import java.util.prefs.Preferences; - +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; +import org.lodder.subtools.multisubdownloader.cli.CliOption; import org.lodder.subtools.multisubdownloader.framework.Container; -import org.lodder.subtools.multisubdownloader.framework.event.Emitter; import org.lodder.subtools.multisubdownloader.framework.service.providers.ServiceProvider; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.multisubdownloader.subtitleproviders.adapters.JAddic7edAdapter; import org.lodder.subtools.multisubdownloader.subtitleproviders.adapters.JAddic7edViaProxyAdapter; +import org.lodder.subtools.sublibrary.Credentials; import org.lodder.subtools.sublibrary.Manager; public class Addic7edServiceProvider implements ServiceProvider { protected Container app; protected SubtitleProvider subtitleProvider; - - @Override - public int getPriority() { - /* We define a priority lower than SubtitleServiceProvider */ - return 1; - } + /* We define a priority lower than SubtitleServiceProvider */ + @val @override int priority = 1; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { this.app = app; /* Resolve the SubtitleProviderStore from the IoC Container */ - final SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) app.make("SubtitleProviderStore"); + final SubtitleProviderStore subtitleProviderStore = app.makeSubtitleProviderStore(); /* Create the SubtitleProvider */ subtitleProvider = createProvider(userInteractionHandler); @@ -43,34 +40,34 @@ public void register(Container app, UserInteractionHandler userInteractionHandle } private SubtitleProvider createProvider(UserInteractionHandler userInteractionHandler) { - Settings settings = (Settings) this.app.make("Settings"); - Preferences preferences = (Preferences) this.app.make("Preferences"); - Manager manager = (Manager) this.app.make("Manager"); + Settings settings = app.makeSettings(); + Manager manager = app.makeManager(); boolean loginEnabled = false; - String username = ""; - String password = ""; - if (settings.isLoginAddic7edEnabled()) { - username = StringUtils.trim(settings.getLoginAddic7edUsername()); - password = StringUtils.trim(settings.getLoginAddic7edPassword()); + Credentials credentials = null; + if (settings.loginAddic7edEnabled) { + String username = StringUtils.trim(settings.loginAddic7edUsername); + String password = StringUtils.trim(settings.loginAddic7edPassword); /* Protect against empty login */ - loginEnabled = !username.isEmpty() && !password.isEmpty(); + if (!username.isEmpty() && !password.isEmpty()) { + credentials = new Credentials(username, password); + } } - if (settings.isSerieSourceAddic7edProxy()) { + if (settings.serieSourceAddic7edProxy) { return new JAddic7edViaProxyAdapter(manager, userInteractionHandler); } else { - return new JAddic7edAdapter(loginEnabled, username, password, preferences.getBoolean("speedy", false), manager, userInteractionHandler); + boolean speedy = app.makePreferences().getBoolean(CliOption.SPEEDY.value, false); + return new JAddic7edAdapter(manager, speedy, credentials, userInteractionHandler); } } // TODO is this still needed? - private void registerListener(SubtitleProviderStore subtitleProviderStore, UserInteractionHandler userInteractionHandler) { - /* Resolve the EventEmitter from the IoC Container */ - Emitter emitter = (Emitter) app.make("EventEmitter"); + private void registerListener(SubtitleProviderStore subtitleProviderStore, + UserInteractionHandler userInteractionHandler) { /* Listen for settings-change */ - emitter.listen("providers.settings.change", event -> { + app.makeEventEmitter().listen("providers.settings.change", _ -> { /* Change occurred, delete outdated provider from store */ subtitleProviderStore.deleteProvider(subtitleProvider); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/LocalServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/LocalServiceProvider.java index d72dd431..e577cafa 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/LocalServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/LocalServiceProvider.java @@ -1,26 +1,21 @@ package org.lodder.subtools.multisubdownloader.serviceproviders; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; -import org.lodder.subtools.multisubdownloader.framework.event.Emitter; import org.lodder.subtools.multisubdownloader.framework.service.providers.ServiceProvider; -import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.Local; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; -import org.lodder.subtools.sublibrary.Manager; public class LocalServiceProvider implements ServiceProvider { - protected Container app; - protected SubtitleProvider subtitleProvider; private UserInteractionHandler userInteractionHandler; - - @Override - public int getPriority() { - /* We define a priority lower than SubtitleServiceProvider */ - return 1; - } + private Container app; + private SubtitleProvider subtitleProvider; + /* We define a priority lower than SubtitleServiceProvider */ + @val @override int priority = 1; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { @@ -28,7 +23,7 @@ public void register(Container app, UserInteractionHandler userInteractionHandle this.userInteractionHandler = userInteractionHandler; /* Resolve the SubtitleProviderStore from the IoC Container */ - final SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) app.make("SubtitleProviderStore"); + final SubtitleProviderStore subtitleProviderStore = app.makeSubtitleProviderStore(); /* Create the SubtitleProvider */ subtitleProvider = createProvider(); @@ -41,17 +36,12 @@ public void register(Container app, UserInteractionHandler userInteractionHandle } private SubtitleProvider createProvider() { - Settings settings = (Settings) this.app.make("Settings"); - Manager manager = (Manager) app.make("Manager"); - return new Local(settings, manager, userInteractionHandler); + return new Local(app.makeSettings(), app.makeManager(), userInteractionHandler); } private void registerListener(final SubtitleProviderStore subtitleProviderStore) { - /* Resolve the EventEmitter from the IoC Container */ - Emitter emitter = (Emitter) app.make("EventEmitter"); - /* Listen for settings-change */ - emitter.listen("providers.settings.change", event -> { + app.makeEventEmitter().listen("providers.settings.change", _ -> { /* Change occurred, delete outdated provider from store */ subtitleProviderStore.deleteProvider(subtitleProvider); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/OpenSubtitlesServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/OpenSubtitlesServiceProvider.java index 535722a6..3e88fe88 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/OpenSubtitlesServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/OpenSubtitlesServiceProvider.java @@ -1,33 +1,30 @@ package org.lodder.subtools.multisubdownloader.serviceproviders; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.StringUtils; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; -import org.lodder.subtools.multisubdownloader.framework.event.Emitter; import org.lodder.subtools.multisubdownloader.framework.service.providers.ServiceProvider; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.multisubdownloader.subtitleproviders.adapters.JOpenSubAdapter; -import org.lodder.subtools.sublibrary.Manager; +import org.lodder.subtools.sublibrary.Credentials; public class OpenSubtitlesServiceProvider implements ServiceProvider { protected Container app; protected SubtitleProvider subtitleProvider; - - @Override - public int getPriority() { - /* We define a priority lower than SubtitleServiceProvider */ - return 1; - } + /* We define a priority lower than SubtitleServiceProvider */ + @val @override int priority = 1; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { this.app = app; /* Resolve the SubtitleProviderStore from the IoC Container */ - SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) app.make("SubtitleProviderStore"); + SubtitleProviderStore subtitleProviderStore = app.makeSubtitleProviderStore(); /* Create the SubtitleProvider */ subtitleProvider = createProvider(userInteractionHandler); @@ -40,27 +37,25 @@ public void register(Container app, UserInteractionHandler userInteractionHandle } private SubtitleProvider createProvider(UserInteractionHandler userInteractionHandler) { - Settings settings = (Settings) this.app.make("Settings"); - Manager manager = (Manager) this.app.make("Manager"); + Settings settings = app.makeSettings(); - boolean loginEnabled = false; - String username = ""; - String password = ""; - if (settings.isLoginOpenSubtitlesEnabled()) { - username = StringUtils.trim(settings.getLoginOpenSubtitlesUsername()); - password = StringUtils.trim(settings.getLoginOpenSubtitlesPassword()); + Credentials credentials = null; + if (settings.loginOpenSubtitlesEnabled) { + String username = StringUtils.trim(settings.loginOpenSubtitlesUsername); + String password = StringUtils.trim(settings.loginOpenSubtitlesPassword); /* Protect against empty login */ - loginEnabled = !username.isEmpty() && !password.isEmpty(); + if (!username.isEmpty() && !password.isEmpty()) { + credentials = new Credentials(username, password); + } } - return new JOpenSubAdapter(loginEnabled, username, password, manager, userInteractionHandler); + return new JOpenSubAdapter(app.makeManager(), credentials, userInteractionHandler); } - private void registerListener(SubtitleProviderStore subtitleProviderStore, UserInteractionHandler userInteractionHandler) { - /* Resolve the EventEmitter from the IoC Container */ - Emitter emitter = (Emitter) app.make("EventEmitter"); + private void registerListener(SubtitleProviderStore subtitleProviderStore, + UserInteractionHandler userInteractionHandler) { /* Listen for settings-change */ - emitter.listen("providers.settings.change", event -> { + app.makeEventEmitter().listen("providers.settings.change", _ -> { /* Change occurred, delete outdated provider from store */ subtitleProviderStore.deleteProvider(subtitleProvider); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/PodnapisiServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/PodnapisiServiceProvider.java index d64e1261..bc7a1c48 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/PodnapisiServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/PodnapisiServiceProvider.java @@ -1,29 +1,19 @@ package org.lodder.subtools.multisubdownloader.serviceproviders; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; import org.lodder.subtools.multisubdownloader.framework.service.providers.ServiceProvider; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.multisubdownloader.subtitleproviders.adapters.JPodnapisiAdapter; -import org.lodder.subtools.sublibrary.Manager; + public class PodnapisiServiceProvider implements ServiceProvider { - @Override - public int getPriority() { - /* We define a priority lower than SubtitleServiceProvider */ - return 1; - } + /* We define a priority lower than SubtitleServiceProvider */ + @val @override int priority = 1; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { - /* Resolve the SubtitleProviderStore from the IoC Container */ - SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) app.make("SubtitleProviderStore"); - - /* Create the SubtitleProvider */ - Manager manager = (Manager) app.make("Manager"); - SubtitleProvider podnapisiAdapter = new JPodnapisiAdapter(manager, userInteractionHandler); - /* Add the SubtitleProvider to the store */ - subtitleProviderStore.addProvider(podnapisiAdapter); + app.makeSubtitleProviderStore().addProvider(new JPodnapisiAdapter(app.makeManager(), userInteractionHandler)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubsceneServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubsceneServiceProvider.java index bfd8e652..4a03775a 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubsceneServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubsceneServiceProvider.java @@ -1,34 +1,23 @@ package org.lodder.subtools.multisubdownloader.serviceproviders; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; import org.lodder.subtools.multisubdownloader.framework.service.providers.ServiceProvider; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.multisubdownloader.subtitleproviders.adapters.JSubsceneAdapter; -import org.lodder.subtools.sublibrary.Manager; public class SubsceneServiceProvider implements ServiceProvider { protected Container app; protected SubtitleProvider subtitleProvider; - - @Override - public int getPriority() { - /* We define a priority lower than SubtitleServiceProvider */ - return 1; - } + /* We define a priority lower than SubtitleServiceProvider */ + @val @override int priority = 1; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { - /* Resolve the SubtitleProviderStore from the IoC Container */ - SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) app.make("SubtitleProviderStore"); - - /* Create the SubtitleProvider */ - Manager manager = (Manager) app.make("Manager"); - JSubsceneAdapter subsceneAdapter = new JSubsceneAdapter(manager, userInteractionHandler); - /* Add the SubtitleProvider to the store */ - subtitleProviderStore.addProvider(subsceneAdapter); + app.makeSubtitleProviderStore().addProvider(new JSubsceneAdapter(app.makeManager(), userInteractionHandler)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubtitleServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubtitleServiceProvider.java index 8af65377..68ae2a7d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubtitleServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/SubtitleServiceProvider.java @@ -1,5 +1,7 @@ package org.lodder.subtools.multisubdownloader.serviceproviders; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; import org.lodder.subtools.multisubdownloader.framework.service.providers.ServiceProvider; @@ -8,10 +10,7 @@ public class SubtitleServiceProvider implements ServiceProvider { - @Override - public int getPriority() { - return 0; - } + @val @override int priority = 0; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/TvSubtitlesServiceProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/TvSubtitlesServiceProvider.java index 67c3edd1..3a86b3a3 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/TvSubtitlesServiceProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/serviceproviders/TvSubtitlesServiceProvider.java @@ -1,30 +1,20 @@ package org.lodder.subtools.multisubdownloader.serviceproviders; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.framework.Container; import org.lodder.subtools.multisubdownloader.framework.service.providers.ServiceProvider; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; -import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProviderStore; import org.lodder.subtools.multisubdownloader.subtitleproviders.adapters.JTVsubtitlesAdapter; -import org.lodder.subtools.sublibrary.Manager; public class TvSubtitlesServiceProvider implements ServiceProvider { - @Override - public int getPriority() { - /* We define a priority lower than SubtitleServiceProvider */ - return 1; - } + + /* We define a priority lower than SubtitleServiceProvider */ + @val @override int priority = 1; @Override public void register(Container app, UserInteractionHandler userInteractionHandler) { - /* Resolve the SubtitleProviderStore from the IoC Container */ - SubtitleProviderStore subtitleProviderStore = (SubtitleProviderStore) app.make("SubtitleProviderStore"); - - /* Create the SubtitleProvider */ - Manager manager = (Manager) app.make("Manager"); - SubtitleProvider tvsubtitlesAdapter = new JTVsubtitlesAdapter(manager, userInteractionHandler); - /* Add the SubtitleProvider to the store */ - subtitleProviderStore.addProvider(tvsubtitlesAdapter); + app.makeSubtitleProviderStore().addProvider(new JTVsubtitlesAdapter(app.makeManager(), userInteractionHandler)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingValue.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingValue.java index 80ca9b49..5c56675a 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingValue.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingValue.java @@ -1,7 +1,6 @@ package org.lodder.subtools.multisubdownloader.settings; import java.nio.file.Path; -import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -13,6 +12,7 @@ import com.google.common.base.CaseFormat; import com.google.common.base.Objects; +import extensions.java.nio.file.Path.PathExt; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -29,402 +29,401 @@ import org.lodder.subtools.multisubdownloader.settings.model.UpdateType; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.control.VideoPatterns; -import org.lodder.subtools.sublibrary.util.FileUtils; -import org.lodder.subtools.sublibrary.util.TriConsumer; +import org.lodder.subtools.sublibrary.util.function.TriConsumer; public enum SettingValue { // SETTINGS SETTINGS_VERSION(createSettingInt() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getSettingsVersion) - .valueSetter(Settings::setSettingsVersion) - .defaultValue(0)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getSettingsVersion) + .valueSetter(Settings::setSettingsVersion) + .defaultValue(0)), LAST_OUTPUT_DIR(createSettingPath() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(settings -> MemoryFolderChooser.getInstance().getMemory()) - .valueSetter(Settings::setLastOutputDir) - .defaultValue(Path.of(""))), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(settings -> MemoryFolderChooser.getInstance().memory) + .valueSetter(Settings::setLastOutputDir) + .defaultValue(Path.of(""))), GENERAL_DEFAULT_INCOMING_FOLDER(createSettingPath() - .rootElementFunction(SettingsControl::getSettings) - .collectionGetter(Settings::getDefaultIncomingFolders)), + .rootElementFunction(SettingsControl::getSettings) + .collectionGetter(Settings::getDefaultIncomingFolders)), LOCAL_SUBTITLES_SOURCES_FOLDERS(createSettingPath() - .rootElementFunction(SettingsControl::getSettings) - .collectionGetter(Settings::getLocalSourcesFolders)), + .rootElementFunction(SettingsControl::getSettings) + .collectionGetter(Settings::getLocalSourcesFolders)), EXCLUDE_ITEM(createSetting(PathOrRegex.class) - .toStringMapper(PathOrRegex::getValue) - .toObjectMapper(PathOrRegex::new) - .rootElementFunction(SettingsControl::getSettings) - .collectionGetter(Settings::getExcludeList)), + .toStringMapper(PathOrRegex::getValue) + .toObjectMapper(PathOrRegex::new) + .rootElementFunction(SettingsControl::getSettings) + .collectionGetter(Settings::getExcludeList)), DEFAULT_SELECTION_QUALITY(createSettingEnum(VideoPatterns.Source.class) - .rootElementFunction(SettingsControl::getSettings) - .collectionGetter(Settings::getOptionsDefaultSelectionQualityList)), + .rootElementFunction(SettingsControl::getSettings) + .collectionGetter(Settings::getOptionsDefaultSelectionQualityList)), DEFAULT_SELECTION_QUALITY_ENABLED(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionsDefaultSelection) - .valueSetter(Settings::setOptionsDefaultSelection) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionsDefaultSelection) + .valueSetter(Settings::setOptionsDefaultSelection) + .defaultValue(false)), OPTIONS_LANGUAGE(createSettingEnum(Language.class) - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getLanguage) - .valueSetter(Settings::setLanguage) - .defaultValue(Language.ENGLISH)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getLanguage) + .valueSetter(Settings::setLanguage) + .defaultValue(Language.ENGLISH)), OPTIONS_ALWAYS_CONFIRM(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionsAlwaysConfirm) - .valueSetter(Settings::setOptionsAlwaysConfirm) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionsAlwaysConfirm) + .valueSetter(Settings::setOptionsAlwaysConfirm) + .defaultValue(false)), OPTIONS_CONFIRM_MAPPING(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionsConfirmProviderMapping) - .valueSetter(Settings::setOptionsConfirmProviderMapping) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionsConfirmProviderMapping) + .valueSetter(Settings::setOptionsConfirmProviderMapping) + .defaultValue(true)), OPTIONS_MIN_AUTOMATIC_SELECTION(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionsMinAutomaticSelection) - .valueSetter(Settings::setOptionsMinAutomaticSelection) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionsMinAutomaticSelection) + .valueSetter(Settings::setOptionsMinAutomaticSelection) + .defaultValue(false)), OPTIONS_MIN_AUTOMATIC_SELECTION_VALUE(createSettingInt() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getOptionsMinAutomaticSelectionValue) - .valueSetter(Settings::setOptionsMinAutomaticSelectionValue) - .defaultValue(0)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getOptionsMinAutomaticSelectionValue) + .valueSetter(Settings::setOptionsMinAutomaticSelectionValue) + .defaultValue(0)), OPTION_SUBTITLE_EXACT_MATCH(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionSubtitleExactMatch) - .valueSetter(Settings::setOptionSubtitleExactMatch) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionSubtitleExactMatch) + .valueSetter(Settings::setOptionSubtitleExactMatch) + .defaultValue(true)), OPTION_SUBTITLE_KEYWORD_MATCH(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionSubtitleKeywordMatch) - .valueSetter(Settings::setOptionSubtitleKeywordMatch) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionSubtitleKeywordMatch) + .valueSetter(Settings::setOptionSubtitleKeywordMatch) + .defaultValue(true)), OPTION_SUBTITLE_EXCLUDE_HEARING_IMPAIRED(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionSubtitleExcludeHearingImpaired) - .valueSetter(Settings::setOptionSubtitleExcludeHearingImpaired) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionSubtitleExcludeHearingImpaired) + .valueSetter(Settings::setOptionSubtitleExcludeHearingImpaired) + .defaultValue(true)), OPTIONS_SHOW_ONLY_FOUND(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionsShowOnlyFound) - .valueSetter(Settings::setOptionsShowOnlyFound) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionsShowOnlyFound) + .valueSetter(Settings::setOptionsShowOnlyFound) + .defaultValue(true)), OPTIONS_STOP_ON_SEARCH_ERROR(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionsStopOnSearchError) - .valueSetter(Settings::setOptionsStopOnSearchError) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionsStopOnSearchError) + .valueSetter(Settings::setOptionsStopOnSearchError) + .defaultValue(false)), OPTION_RECURSIVE(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isOptionRecursive) - .valueSetter(Settings::setOptionRecursive) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isOptionRecursive) + .valueSetter(Settings::setOptionRecursive) + .defaultValue(false)), PROCESS_EPISODE_SOURCE(createSettingEnum(SettingsProcessEpisodeSource.class) - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getProcessEpisodeSource) - .valueSetter(Settings::setProcessEpisodeSource) - .defaultValue(SettingsProcessEpisodeSource.TVDB)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getProcessEpisodeSource) + .valueSetter(Settings::setProcessEpisodeSource) + .defaultValue(SettingsProcessEpisodeSource.TVDB)), UPDATE_CHECK_PERIOD(createSettingEnum(UpdateCheckPeriod.class) - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getUpdateCheckPeriod) - .valueSetter(Settings::setUpdateCheckPeriod) - .defaultValue(UpdateCheckPeriod.WEEKLY)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getUpdateCheckPeriod) + .valueSetter(Settings::setUpdateCheckPeriod) + .defaultValue(UpdateCheckPeriod.WEEKLY)), USE_NIGHTLY(createSettingEnum(UpdateType.class) - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getUpdateType) - .valueSetter(Settings::setUpdateType) - .defaultValue(UpdateType.STABLE)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getUpdateType) + .valueSetter(Settings::setUpdateType) + .defaultValue(UpdateType.STABLE)), SUBTITLE_LANGUAGE(createSettingEnum(Language.class) - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getSubtitleLanguage) - .valueSetter(Settings::setSubtitleLanguage) - .defaultValue(Language.DUTCH)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getSubtitleLanguage) + .valueSetter(Settings::setSubtitleLanguage) + .defaultValue(Language.DUTCH)), // SCREEN SETTINGS SCREEN_HIDE_EPISODE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getScreenSettings()) - .valueGetter(ScreenSettings::isHideEpisode) - .valueSetter(ScreenSettings::setHideEpisode) - .defaultValue(true)), + .rootElementFunction(sCtr -> sCtr.settings.screenSettings) + .valueGetter(ScreenSettings::isHideEpisode) + .valueSetter(ScreenSettings::setHideEpisode) + .defaultValue(true)), SCREEN_HIDE_FILENAME(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getScreenSettings()) - .valueGetter(ScreenSettings::isHideFilename) - .valueSetter(ScreenSettings::setHideFilename) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.screenSettings) + .valueGetter(ScreenSettings::isHideFilename) + .valueSetter(ScreenSettings::setHideFilename) + .defaultValue(false)), SCREEN_HIDE_SEASON(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getScreenSettings()) - .valueGetter(ScreenSettings::isHideSeason) - .valueSetter(ScreenSettings::setHideSeason) - .defaultValue(true)), + .rootElementFunction(sCtr -> sCtr.settings.screenSettings) + .valueGetter(ScreenSettings::isHideSeason) + .valueSetter(ScreenSettings::setHideSeason) + .defaultValue(true)), SCREEN_HIDE_TITLE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getScreenSettings()) - .valueGetter(ScreenSettings::isHideTitle) - .valueSetter(ScreenSettings::setHideTitle) - .defaultValue(true)), + .rootElementFunction(sCtr -> sCtr.settings.screenSettings) + .valueGetter(ScreenSettings::isHideTitle) + .valueSetter(ScreenSettings::setHideTitle) + .defaultValue(true)), SCREEN_HIDE_TYPE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getScreenSettings()) - .valueGetter(ScreenSettings::isHideType) - .valueSetter(ScreenSettings::setHideType) - .defaultValue(true)), + .rootElementFunction(sCtr -> sCtr.settings.screenSettings) + .valueGetter(ScreenSettings::isHideType) + .valueSetter(ScreenSettings::setHideType) + .defaultValue(true)), SCREEN_HIDE_W_I_P(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getScreenSettings()) - .valueGetter(ScreenSettings::isHideWIP) - .valueSetter(ScreenSettings::setHideWIP) - .defaultValue(true)), + .rootElementFunction(sCtr -> sCtr.settings.screenSettings) + .valueGetter(ScreenSettings::isHideWIP) + .valueSetter(ScreenSettings::setHideWIP) + .defaultValue(true)), // PROXY SETTINGS GENERAL_PROXY_ENABLED(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isGeneralProxyEnabled) - .valueSetter(Settings::setGeneralProxyEnabled) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isGeneralProxyEnabled) + .valueSetter(Settings::setGeneralProxyEnabled) + .defaultValue(false)), GENERAL_PROXY_HOST(createSettingString() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getGeneralProxyHost) - .valueSetter(Settings::setGeneralProxyHost) - .defaultValue("")), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getGeneralProxyHost) + .valueSetter(Settings::setGeneralProxyHost) + .defaultValue("")), GENERAL_PROXY_PORT(createSettingInt() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getGeneralProxyPort) - .valueSetter(Settings::setGeneralProxyPort) - .defaultValue(80)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getGeneralProxyPort) + .valueSetter(Settings::setGeneralProxyPort) + .defaultValue(80)), // LIBRARY SERIE EPISODE_LIBRARY_BACKUP_SUBTITLE_PATH(createSettingPath() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryBackupSubtitlePath) - .valueSetter(LibrarySettings::setLibraryBackupSubtitlePath) - .defaultValue(null)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getBackupSubtitlePath) + .valueSetter(LibrarySettings::setBackupSubtitlePath) + .defaultValue(null)), EPISODE_LIBRARY_BACKUP_SUBTITLE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryBackupSubtitle) - .valueSetter(LibrarySettings::setLibraryBackupSubtitle) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::isBackupSubtitle) + .valueSetter(LibrarySettings::setBackupSubtitle) + .defaultValue(false)), EPISODE_LIBRARY_BACKUP_USE_WEBSITE_FILE_NAME(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryBackupUseWebsiteFileName) - .valueSetter(LibrarySettings::setLibraryBackupUseWebsiteFileName) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::isBackupUseWebsiteFileName) + .valueSetter(LibrarySettings::setBackupUseWebsiteFileName) + .defaultValue(false)), EPISODE_LIBRARY_ACTION(createSettingEnum(LibraryActionType.class) - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryAction) - .valueSetter(LibrarySettings::setLibraryAction) - .defaultValue(LibraryActionType.NOTHING)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getAction) + .valueSetter(LibrarySettings::setAction) + .defaultValue(LibraryActionType.NOTHING)), EPISODE_LIBRARY_USE_T_V_D_B_NAMING(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryUseTVDBNaming) - .valueSetter(LibrarySettings::setLibraryUseTVDBNaming) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::isUseTVDBNaming) + .valueSetter(LibrarySettings::setUseTVDBNaming) + .defaultValue(false)), EPISODE_LIBRARY_OTHER_FILE_ACTION(createSettingEnum(LibraryOtherFileActionType.class) - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryOtherFileAction) - .valueSetter(LibrarySettings::setLibraryOtherFileAction) - .defaultValue(LibraryOtherFileActionType.NOTHING)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getOtherFileAction) + .valueSetter(LibrarySettings::setOtherFileAction) + .defaultValue(LibraryOtherFileActionType.NOTHING)), EPISODE_LIBRARY_FOLDER(createSettingPath() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFolder) - .valueSetter(LibrarySettings::setLibraryFolder) - .defaultValue(null)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getFolder) + .valueSetter(LibrarySettings::setFolder) + .defaultValue(null)), EPISODE_LIBRARY_FOLDER_STRUCTURE(createSettingString() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFolderStructure) - .valueSetter(LibrarySettings::setLibraryFolderStructure) - .defaultValue("")), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getFolderStructure) + .valueSetter(LibrarySettings::setFolderStructure) + .defaultValue("")), EPISODE_LIBRARY_REMOVE_EMPTY_FOLDERS(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryRemoveEmptyFolders) - .valueSetter(LibrarySettings::setLibraryRemoveEmptyFolders) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::isRemoveEmptyFolders) + .valueSetter(LibrarySettings::setRemoveEmptyFolders) + .defaultValue(false)), EPISODE_LIBRARY_FILENAME_STRUCTURE(createSettingString() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFilenameStructure) - .valueSetter(LibrarySettings::setLibraryFilenameStructure) - .defaultValue("")), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getFilenameStructure) + .valueSetter(LibrarySettings::setFilenameStructure) + .defaultValue("")), EPISODE_LIBRARY_REPLACE_SPACE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryFilenameReplaceSpace) - .valueSetter(LibrarySettings::setLibraryFilenameReplaceSpace) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::isFilenameReplaceSpace) + .valueSetter(LibrarySettings::setFilenameReplaceSpace) + .defaultValue(false)), EPISODE_LIBRARY_REPLACING_SIGN(createSettingCharacter() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFilenameReplacingSpaceChar) - .valueSetter(LibrarySettings::setLibraryFilenameReplacingSpaceChar) - .defaultValue('_')), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getFilenameReplacingSpaceChar) + .valueSetter(LibrarySettings::setFilenameReplacingSpaceChar) + .defaultValue('_')), EPISODE_LIBRARY_FOLDER_REPLACE_SPACE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryFolderReplaceSpace) - .valueSetter(LibrarySettings::setLibraryFolderReplaceSpace) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::isFolderReplaceSpace) + .valueSetter(LibrarySettings::setFolderReplaceSpace) + .defaultValue(false)), EPISODE_LIBRARY_FOLDER_REPLACING_SIGN(createSettingCharacter() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFolderReplacingSpaceChar) - .valueSetter(LibrarySettings::setLibraryFolderReplacingSpaceChar) - .defaultValue('_')), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::getFolderReplacingSpaceChar) + .valueSetter(LibrarySettings::setFolderReplacingSpaceChar) + .defaultValue('_')), EPISODE_LIBRARY_INCLUDE_LANGUAGE_CODE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryIncludeLanguageCode) - .valueSetter(LibrarySettings::setLibraryIncludeLanguageCode) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .valueGetter(LibrarySettings::isIncludeLanguageCode) + .valueSetter(LibrarySettings::setIncludeLanguageCode) + .defaultValue(false)), EPISODE_LIBRARY_LANG_CODE_MAPPING(createSettingMap() - .toStringMapperKey(Language::name) - .toObjectMapperKey(Language::valueOf) - .toStringMapperValue(Function.identity()) - .toObjectMapperValue(Function.identity()) - .rootElementFunction(sCtr -> sCtr.getSettings().getEpisodeLibrarySettings()) - .mapGetter(LibrarySettings::getLangCodeMap)), + .toStringMapperKey(Language::name) + .toObjectMapperKey(Language::valueOf) + .toStringMapperValue(Function.identity()) + .toObjectMapperValue(Function.identity()) + .rootElementFunction(sCtr -> sCtr.settings.episodeLibrarySettings) + .mapGetter(LibrarySettings::getLangCodeMap)), // LIBRARY MOVIE MOVIE_LIBRARY_BACKUP_SUBTITLE_PATH(createSettingPath() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryBackupSubtitlePath) - .valueSetter(LibrarySettings::setLibraryBackupSubtitlePath) - .defaultValue(null)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getBackupSubtitlePath) + .valueSetter(LibrarySettings::setBackupSubtitlePath) + .defaultValue(null)), MOVIE_LIBRARY_BACKUP_SUBTITLE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryBackupSubtitle) - .valueSetter(LibrarySettings::setLibraryBackupSubtitle) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::isBackupSubtitle) + .valueSetter(LibrarySettings::setBackupSubtitle) + .defaultValue(false)), MOVIE_LIBRARY_BACKUP_USE_WEBSITE_FILE_NAME(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryBackupUseWebsiteFileName) - .valueSetter(LibrarySettings::setLibraryBackupUseWebsiteFileName) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::isBackupUseWebsiteFileName) + .valueSetter(LibrarySettings::setBackupUseWebsiteFileName) + .defaultValue(false)), MOVIE_LIBRARY_ACTION(createSettingEnum(LibraryActionType.class) - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryAction) - .valueSetter(LibrarySettings::setLibraryAction) - .defaultValue(LibraryActionType.NOTHING)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getAction) + .valueSetter(LibrarySettings::setAction) + .defaultValue(LibraryActionType.NOTHING)), MOVIE_LIBRARY_USE_T_V_D_B_NAMING(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryUseTVDBNaming) - .valueSetter(LibrarySettings::setLibraryUseTVDBNaming) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::isUseTVDBNaming) + .valueSetter(LibrarySettings::setUseTVDBNaming) + .defaultValue(false)), MOVIE_LIBRARY_OTHER_FILE_ACTION(createSettingEnum(LibraryOtherFileActionType.class) - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryOtherFileAction) - .valueSetter(LibrarySettings::setLibraryOtherFileAction) - .defaultValue(LibraryOtherFileActionType.NOTHING)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getOtherFileAction) + .valueSetter(LibrarySettings::setOtherFileAction) + .defaultValue(LibraryOtherFileActionType.NOTHING)), MOVIE_LIBRARY_FOLDER(createSettingPath() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFolder) - .valueSetter(LibrarySettings::setLibraryFolder) - .defaultValue(null)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getFolder) + .valueSetter(LibrarySettings::setFolder) + .defaultValue(null)), MOVIE_LIBRARY_FOLDER_STRUCTURE(createSettingString() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFolderStructure) - .valueSetter(LibrarySettings::setLibraryFolderStructure) - .defaultValue("")), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getFolderStructure) + .valueSetter(LibrarySettings::setFolderStructure) + .defaultValue("")), MOVIE_LIBRARY_REMOVE_EMPTY_FOLDERS(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryRemoveEmptyFolders) - .valueSetter(LibrarySettings::setLibraryRemoveEmptyFolders) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::isRemoveEmptyFolders) + .valueSetter(LibrarySettings::setRemoveEmptyFolders) + .defaultValue(false)), MOVIE_LIBRARY_FILENAME_STRUCTURE(createSettingString() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFilenameStructure) - .valueSetter(LibrarySettings::setLibraryFilenameStructure) - .defaultValue("")), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getFilenameStructure) + .valueSetter(LibrarySettings::setFilenameStructure) + .defaultValue("")), MOVIE_LIBRARY_REPLACE_SPACE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryFilenameReplaceSpace) - .valueSetter(LibrarySettings::setLibraryFilenameReplaceSpace) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::isFilenameReplaceSpace) + .valueSetter(LibrarySettings::setFilenameReplaceSpace) + .defaultValue(false)), MOVIE_LIBRARY_REPLACING_SIGN(createSettingCharacter() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFilenameReplacingSpaceChar) - .valueSetter(LibrarySettings::setLibraryFilenameReplacingSpaceChar) - .defaultValue('_')), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getFilenameReplacingSpaceChar) + .valueSetter(LibrarySettings::setFilenameReplacingSpaceChar) + .defaultValue('_')), MOVIE_LIBRARY_FOLDER_REPLACE_SPACE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryFolderReplaceSpace) - .valueSetter(LibrarySettings::setLibraryFolderReplaceSpace) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::isFolderReplaceSpace) + .valueSetter(LibrarySettings::setFolderReplaceSpace) + .defaultValue(false)), MOVIE_LIBRARY_FOLDER_REPLACING_SIGN(createSettingCharacter() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::getLibraryFolderReplacingSpaceChar) - .valueSetter(LibrarySettings::setLibraryFolderReplacingSpaceChar) - .defaultValue('_')), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::getFolderReplacingSpaceChar) + .valueSetter(LibrarySettings::setFolderReplacingSpaceChar) + .defaultValue('_')), MOVIE_LIBRARY_INCLUDE_LANGUAGE_CODE(createSettingBoolean() - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .valueGetter(LibrarySettings::isLibraryIncludeLanguageCode) - .valueSetter(LibrarySettings::setLibraryIncludeLanguageCode) - .defaultValue(false)), + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .valueGetter(LibrarySettings::isIncludeLanguageCode) + .valueSetter(LibrarySettings::setIncludeLanguageCode) + .defaultValue(false)), MOVIE_LIBRARY_LANG_CODE_MAPPING(createSettingMap() - .toStringMapperKey(Language::name) - .toObjectMapperKey(Language::valueOf) - .toStringMapperValue(Function.identity()) - .toObjectMapperValue(Function.identity()) - .rootElementFunction(sCtr -> sCtr.getSettings().getMovieLibrarySettings()) - .mapGetter(LibrarySettings::getLangCodeMap)), + .toStringMapperKey(Language::name) + .toObjectMapperKey(Language::valueOf) + .toStringMapperValue(Function.identity()) + .toObjectMapperValue(Function.identity()) + .rootElementFunction(sCtr -> sCtr.settings.movieLibrarySettings) + .mapGetter(LibrarySettings::getLangCodeMap)), // SERIE SOURCE SETTINGS LOGIN_ADDIC7ED_ENABLED(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isLoginAddic7edEnabled) - .valueSetter(Settings::setLoginAddic7edEnabled) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isLoginAddic7edEnabled) + .valueSetter(Settings::setLoginAddic7edEnabled) + .defaultValue(false)), LOGIN_ADDIC7ED_USERNAME(createSettingString() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getLoginAddic7edUsername) - .valueSetter(Settings::setLoginAddic7edUsername) - .defaultValue("")), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getLoginAddic7edUsername) + .valueSetter(Settings::setLoginAddic7edUsername) + .defaultValue("")), LOGIN_ADDIC7ED_PASSWORD(createSettingString() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getLoginAddic7edPassword) - .valueSetter(Settings::setLoginAddic7edPassword) - .defaultValue("")), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getLoginAddic7edPassword) + .valueSetter(Settings::setLoginAddic7edPassword) + .defaultValue("")), LOGIN_OPEN_SUBTITLES_ENABLED(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isLoginOpenSubtitlesEnabled) - .valueSetter(Settings::setLoginOpenSubtitlesEnabled) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isLoginOpenSubtitlesEnabled) + .valueSetter(Settings::setLoginOpenSubtitlesEnabled) + .defaultValue(false)), LOGIN_OPEN_SUBTITLES_USERNAME(createSettingString() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getLoginOpenSubtitlesUsername) - .valueSetter(Settings::setLoginOpenSubtitlesUsername) - .defaultValue("")), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getLoginOpenSubtitlesUsername) + .valueSetter(Settings::setLoginOpenSubtitlesUsername) + .defaultValue("")), LOGIN_OPEN_SUBTITLES_PASSWORD(createSettingString() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::getLoginOpenSubtitlesPassword) - .valueSetter(Settings::setLoginOpenSubtitlesPassword) - .defaultValue("")), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::getLoginOpenSubtitlesPassword) + .valueSetter(Settings::setLoginOpenSubtitlesPassword) + .defaultValue("")), SERIE_SOURCE_ADDIC7ED(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isSerieSourceAddic7ed) - .valueSetter(Settings::setSerieSourceAddic7ed) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isSerieSourceAddic7ed) + .valueSetter(Settings::setSerieSourceAddic7ed) + .defaultValue(true)), SERIE_SOURCE_ADDIC7ED_PROXY(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isSerieSourceAddic7edProxy) - .valueSetter(Settings::setSerieSourceAddic7edProxy) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isSerieSourceAddic7edProxy) + .valueSetter(Settings::setSerieSourceAddic7edProxy) + .defaultValue(true)), SERIE_SOURCE_LOCAL(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isSerieSourceLocal) - .valueSetter(Settings::setSerieSourceLocal) - .defaultValue(false)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isSerieSourceLocal) + .valueSetter(Settings::setSerieSourceLocal) + .defaultValue(false)), SERIE_SOURCE_OPENSUBTITLES(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isSerieSourceOpensubtitles) - .valueSetter(Settings::setSerieSourceOpensubtitles) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isSerieSourceOpensubtitles) + .valueSetter(Settings::setSerieSourceOpensubtitles) + .defaultValue(true)), SERIE_SOURCE_PODNAPISI(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isSerieSourcePodnapisi) - .valueSetter(Settings::setSerieSourcePodnapisi) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isSerieSourcePodnapisi) + .valueSetter(Settings::setSerieSourcePodnapisi) + .defaultValue(true)), SERIE_SOURCE_TV_SUBTITLES(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isSerieSourceTvSubtitles) - .valueSetter(Settings::setSerieSourceTvSubtitles) - .defaultValue(true)), + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isSerieSourceTvSubtitles) + .valueSetter(Settings::setSerieSourceTvSubtitles) + .defaultValue(true)), SERIE_SOURCE_SUBSCENE(createSettingBoolean() - .rootElementFunction(SettingsControl::getSettings) - .valueGetter(Settings::isSerieSourceSubscene) - .valueSetter(Settings::setSerieSourceSubscene) - .defaultValue(true)); + .rootElementFunction(SettingsControl::getSettings) + .valueGetter(Settings::isSerieSourceSubscene) + .valueSetter(Settings::setSerieSourceSubscene) + .defaultValue(true)); private final BiConsumer storeValueFunction; private final BiConsumer loadValueFunction; @@ -448,7 +447,7 @@ public void load(SettingsControl settingsControl, Preferences preferences) { } public static void loadAll(SettingsControl settingsControl, Preferences preferences) { - Arrays.stream(SettingValue.values()).forEach(sv -> sv.load(settingsControl, preferences)); + SettingValue.values().forEach(sv -> sv.load(settingsControl, preferences)); } @@ -460,38 +459,38 @@ private interface SettingIntf { private static SettingTypedRootElementFunctionIntf createSettingString() { return createSetting(String.class) - .toStringMapper(Function.identity()) - .toObjectMapper(Function.identity()); + .toStringMapper(Function.identity()) + .toObjectMapper(Function.identity()); } private static SettingTypedRootElementFunctionIntf createSettingCharacter() { return createSetting(Character.class) - .toStringMapper(String::valueOf) - .toObjectMapper(s -> s.charAt(0)); + .toStringMapper(String::valueOf) + .toObjectMapper(s -> s.charAt(0)); } private static SettingTypedRootElementFunctionIntf createSettingInt() { return createSetting(Integer.class) - .preferencesSetter(Preferences::putInt) - .preferencesGetter(Preferences::getInt); + .preferencesSetter(Preferences::putInt) + .preferencesGetter(Preferences::getInt); } private static SettingTypedRootElementFunctionIntf createSettingBoolean() { return createSetting(Boolean.class) - .preferencesSetter(Preferences::putBoolean) - .preferencesGetter(Preferences::getBoolean); + .preferencesSetter(Preferences::putBoolean) + .preferencesGetter(Preferences::getBoolean); } private static SettingTypedRootElementFunctionIntf createSettingPath() { return createSetting(Path.class) - .toStringMapper(FileUtils::toAbsolutePathAsString) - .toObjectMapper(Path::of); + .toStringMapper(PathExt::toAbsolutePathAsString) + .toObjectMapper(Path::of); } private static > SettingTypedRootElementFunctionIntf createSettingEnum(Class type) { return createSetting(type) - .toStringMapper(Enum::name) - .toObjectMapper(s -> Enum.valueOf(type, s)); + .toStringMapper(Enum::name) + .toObjectMapper(s -> Enum.valueOf(type, s)); } private static SettingTypedToStringMapperIntf createSetting(Class type) { @@ -509,7 +508,8 @@ private interface SettingTypedToObjectMapperIntf { } private interface SettingTypedPreferenceGetterIntf { - SettingTypedRootElementFunctionIntf preferencesGetter(TriFunction preferencesGetter); + SettingTypedRootElementFunctionIntf preferencesGetter( + TriFunction preferencesGetter); } private interface SettingTypedRootElementFunctionIntf { @@ -542,15 +542,15 @@ private enum SettingType { @Setter @Accessors(chain = true, fluent = true) private static class SettingTyped extends SettingCommon> - implements - SettingTypedToStringMapperIntf, - SettingTypedToObjectMapperIntf, - SettingTypedPreferenceGetterIntf, - SettingTypedRootElementFunctionIntf, - SettingTypedValueGetterIntf, - SettingTypedValueSetterIntf, - SettingTypedDefaultValueIntf, - SettingBuildIntf { + implements + SettingTypedToStringMapperIntf, + SettingTypedToObjectMapperIntf, + SettingTypedPreferenceGetterIntf, + SettingTypedRootElementFunctionIntf, + SettingTypedValueGetterIntf, + SettingTypedValueSetterIntf, + SettingTypedDefaultValueIntf, + SettingBuildIntf { private SettingType settingType = SettingType.SINGLE_VALUE; @@ -597,10 +597,10 @@ public SettingTyped toStringMapper(Function toStringMapper) { public SettingTyped toObjectMapper(Function toObjectMapper) { this.toObjectMapper = toObjectMapper; this.preferencesGetter = - (preferences, key, defaultValue) -> { - String value = preferences.get(key, null); - return value != null ? toObjectMapper.apply(value) : getDefaultValue(); - }; + (preferences, key, defaultValue) -> { + String value = preferences.get(key, null); + return value != null ? toObjectMapper.apply(value) : getDefaultValue(); + }; return this; } @@ -619,19 +619,21 @@ public SettingIntf build(String key) { case SINGLE_VALUE -> { super.storeValueFunction((settingsControl, preferences) -> { T value = valueGetter.apply(getRootElement(settingsControl)); - if (!Objects.equal(value, getDefaultValue()) && !(value instanceof String text && "".equals(text))) { + if (!Objects.equal(value, getDefaultValue()) && + !(value instanceof String text && text.isEmpty())) { preferencesSetter.accept(preferences, key, value); } }); - super.loadValueFunction((settingsControl, preferences) -> valueSetter.accept(getRootElement(settingsControl), + super.loadValueFunction( + (settingsControl, preferences) -> valueSetter.accept(getRootElement(settingsControl), preferencesGetter.apply(preferences, key, getDefaultValue()))); } case COLLECTION -> { super.storeValueFunction((settingsControl, preferences) -> { AtomicInteger i = new AtomicInteger(-1); valueConsumer.accept(getRootElement(settingsControl), - value -> preferences.put(key + i.incrementAndGet(), toStringMapper.apply(value))); + value -> preferences.put(key + i.incrementAndGet(), toStringMapper.apply(value))); if (i.get() > -1) { preferences.putInt(key + "Size", i.get() + 1); } @@ -641,7 +643,8 @@ public SettingIntf build(String key) { R rootElement = getRootElement(settingsControl); listCleaner.accept(rootElement); IntStream.range(0, numberOfItems) - .forEach(i -> valueAdder.accept(rootElement, toObjectMapper.apply(preferences.get(key + i, "")))); + .forEach(i -> valueAdder.accept(rootElement, + toObjectMapper.apply(preferences.get(key + i, "")))); }); } default -> throw new IllegalArgumentException("Unexpected value: " + settingType); @@ -683,14 +686,14 @@ private interface SettingMapTypedMapGetterIntf { @Setter @Accessors(chain = true, fluent = true) private static class SettingMapTyped - implements SettingIntf, - SettingMapTypedToStringMapperKeyIntf, - SettingMapTypedToObjectMapperKeyIntf, - SettingMapTypedToStringMapperValueIntf, - SettingMapTypedToObjectMapperValueIntf, - SettingMapTypedRootElementFunctionIntf, - SettingMapTypedMapGetterIntf, - SettingBuildIntf { + implements SettingIntf, + SettingMapTypedToStringMapperKeyIntf, + SettingMapTypedToObjectMapperKeyIntf, + SettingMapTypedToStringMapperValueIntf, + SettingMapTypedToObjectMapperValueIntf, + SettingMapTypedRootElementFunctionIntf, + SettingMapTypedMapGetterIntf, + SettingBuildIntf { private Function toStringMapperKey; private Function toObjectMapperKey; @@ -745,22 +748,22 @@ public SettingIntf build(String key) { this.storeValueFunction = (settingsControl, preferences) -> { AtomicInteger i = new AtomicInteger(-1); valueConsumer.accept(getRootElement(settingsControl), - (k, v) -> { - int idx = i.incrementAndGet(); - preferences.put(getKeyString(key, idx), toStringMapperKey.apply(k)); - preferences.put(getValueString(key, idx), toStringMapperValue.apply(v)); - }); + (k, v) -> { + int idx = i.incrementAndGet(); + preferences.put(getKeyString(key, idx), toStringMapperKey.apply(k)); + preferences.put(getValueString(key, idx), toStringMapperValue.apply(v)); + }); if (i.get() > -1) { preferences.putInt(key + "Size", i.get() + 1); } }; this.loadValueFunction = (settingsControl, preferences) -> { int numberOfItems = preferences.getInt(key + "Size", 0); - IntStream.range(0, numberOfItems).forEach(idx -> { + IntStream.range(0, numberOfItems).forEach(idx -> valueAdder.accept(getRootElement(settingsControl), - toObjectMapperKey.apply(preferences.get(getKeyString(key, idx), "")), - toObjectMapperValue.apply(preferences.get(getValueString(key, idx), ""))); - }); + toObjectMapperKey.apply(preferences.get(getKeyString(key, idx), "")), + toObjectMapperValue.apply(preferences.get(getValueString(key, idx), ""))) + ); }; return this; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingsControl.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingsControl.java index 96507d9b..ecfd8264 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingsControl.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/SettingsControl.java @@ -1,6 +1,8 @@ package org.lodder.subtools.multisubdownloader.settings; +import static manifold.ext.props.rt.api.PropOption.*; import static org.lodder.subtools.multisubdownloader.settings.SettingValue.*; +import static org.lodder.subtools.sublibrary.cache.CacheType.*; import java.io.BufferedInputStream; import java.io.IOException; @@ -8,13 +10,15 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; import java.util.prefs.BackingStoreException; import java.util.prefs.InvalidPreferencesFormatException; import java.util.prefs.Preferences; import java.util.stream.IntStream; +import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.get; +import manifold.ext.props.rt.api.set; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.lodder.subtools.multisubdownloader.gui.dialog.MappingEpisodeNameDialog.MappingType; @@ -26,28 +30,25 @@ import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.OpenSubtitlesApi; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; -import org.lodder.subtools.sublibrary.cache.CacheType; +import org.lodder.subtools.sublibrary.Manager.Value; import org.lodder.subtools.sublibrary.control.VideoPatterns; import org.lodder.subtools.sublibrary.control.VideoPatterns.Source; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import org.lodder.subtools.sublibrary.util.TriConsumer; +import org.lodder.subtools.sublibrary.util.function.TriConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ Files.class }) +@ExtensionMethod({Files.class}) public class SettingsControl { + private static final Logger LOGGER = LoggerFactory.getLogger(SettingsControl.class); + private static final String BACKING_STORE_AVAIL = "BackingStoreAvail"; + public static final String DATABASE_VERSION_KEY = "DATABSE_VERSION"; + private final Manager manager; private final Preferences preferences; - @Getter - private Settings settings; - @Getter - private State state; - private static final String BACKING_STORE_AVAIL = "BackingStoreAvail"; - private static final Logger LOGGER = LoggerFactory.getLogger(SettingsControl.class); + @get @set(Private) Settings settings; + @get @set(Private) State state; public SettingsControl(Manager manager) { if (!backingStoreAvailable()) { @@ -77,7 +78,7 @@ public void store() { try { // clean up preferences.clear(); - Arrays.stream(SettingValue.values()).forEach(sv -> sv.store(this, preferences)); + SettingValue.values().forEach(sv -> sv.store(this, preferences)); updateProxySettings(); } catch (BackingStoreException e) { LOGGER.error(e.getMessage(), e); @@ -98,7 +99,8 @@ public void exportPreferences(Path file) throws IOException, BackingStoreExcepti } } - public void importPreferences(Path file) throws IOException, BackingStoreException, InvalidPreferencesFormatException { + public void importPreferences(Path file) + throws IOException, BackingStoreException, InvalidPreferencesFormatException { try (InputStream is = new BufferedInputStream(file.newInputStream())) { preferences.clear(); Preferences.importPreferences(is); @@ -107,10 +109,10 @@ public void importPreferences(Path file) throws IOException, BackingStoreExcepti } public void updateProxySettings() { - if (settings.isGeneralProxyEnabled()) { + if (settings.generalProxyEnabled) { System.getProperties().put("proxySet", "true"); - System.getProperties().put("proxyHost", settings.getGeneralProxyHost()); - System.getProperties().put("proxyPort", settings.getGeneralProxyPort()); + System.getProperties().put("proxyHost", settings.generalProxyHost); + System.getProperties().put("proxyPort", settings.generalProxyPort); } else { System.getProperties().put("proxySet", "false"); } @@ -121,7 +123,7 @@ public void updateProxySettings() { */ private void migrateSettings() { SettingValue.loadAll(this, preferences); - int version = settings.getSettingsVersion(); + int version = settings.settingsVersion; if (version == 0) { migrateSettingsV0ToV1(); settings = new Settings(); @@ -153,7 +155,8 @@ private void migrateSettings() { public void migrateSettingsV0ToV1() { preferences.putInt("GeneralDefaultIncomingFolderSize", preferences.getInt("lastDefaultIncomingFolder", 0)); - preferences.putInt("LocalSubtitlesSourcesFoldersSize", preferences.getInt("lastLocalSubtitlesSourcesFolder", 0)); + preferences.putInt("LocalSubtitlesSourcesFoldersSize", + preferences.getInt("lastLocalSubtitlesSourcesFolder", 0)); preferences.putInt("GeneralDefaultIncomingFolderSize", preferences.getInt("lastDefaultIncomingFolder", 0)); preferences.putInt("DefaultSelectionQualitySize", preferences.getInt("lastItemDefaultSelectionQuality", 0)); preferences.putInt("DefaultSelectionQualitySize", preferences.getInt("lastItemDefaultSelectionQuality", 0)); @@ -164,7 +167,7 @@ public void migrateSettingsV0ToV1() { // int lastItemExclude = preferences.getInt("lastItemExclude", 0); // if (lastItemExclude > 0) { - // List excludeList = settings.getExcludeList(); + // List excludeList = settings.excludeList; // IntStream.range(0, preferences.getInt("lastItemExclude", 0)).forEach(i -> { // String description = preferences.get("ExcludeDescription" + i, ""); // PathMatchType type; @@ -179,27 +182,27 @@ public void migrateSettingsV0ToV1() { // } EPISODE_LIBRARY_FOLDER_STRUCTURE.load(this, preferences); - settings.getEpisodeLibrarySettings() - .setLibraryFolderStructure(migrateLibraryStructureV0(settings.getEpisodeLibrarySettings().getLibraryFolderStructure())); + settings.episodeLibrarySettings.folderStructure = + migrateLibraryStructureV0(settings.episodeLibrarySettings.folderStructure); EPISODE_LIBRARY_FOLDER_STRUCTURE.store(this, preferences); EPISODE_LIBRARY_FILENAME_STRUCTURE.load(this, preferences); - settings.getEpisodeLibrarySettings() - .setLibraryFilenameStructure(migrateLibraryStructureV0(settings.getEpisodeLibrarySettings().getLibraryFilenameStructure())); + settings.episodeLibrarySettings.filenameStructure = + migrateLibraryStructureV0(settings.episodeLibrarySettings.filenameStructure); EPISODE_LIBRARY_FILENAME_STRUCTURE.store(this, preferences); MOVIE_LIBRARY_FOLDER_STRUCTURE.load(this, preferences); - settings.getEpisodeLibrarySettings() - .setLibraryFolderStructure(migrateLibraryStructureV0(settings.getEpisodeLibrarySettings().getLibraryFolderStructure())); + settings.episodeLibrarySettings.folderStructure = + migrateLibraryStructureV0(settings.episodeLibrarySettings.folderStructure); MOVIE_LIBRARY_FOLDER_STRUCTURE.store(this, preferences); MOVIE_LIBRARY_FILENAME_STRUCTURE.load(this, preferences); - settings.getEpisodeLibrarySettings() - .setLibraryFilenameStructure(migrateLibraryStructureV0(settings.getEpisodeLibrarySettings().getLibraryFilenameStructure())); + settings.episodeLibrarySettings.filenameStructure = + migrateLibraryStructureV0(settings.episodeLibrarySettings.filenameStructure); MOVIE_LIBRARY_FILENAME_STRUCTURE.store(this, preferences); try { - Arrays.stream(preferences.keys()).forEach(key -> { + preferences.keys().forEach(key -> { String value = preferences.get(key, ""); preferences.remove(key); preferences.put(StringUtils.capitalize(key), value); @@ -208,29 +211,29 @@ public void migrateSettingsV0ToV1() { LOGGER.error("Error during migration of settings, ignoring..."); } - settings.setSettingsVersion(1); + settings.settingsVersion = 1; SETTINGS_VERSION.store(this, preferences); } @SuppressWarnings("deprecation") public void migrateSettingsV1ToV2() { - settings.getEpisodeLibrarySettings() - .setLibraryOtherFileAction(LibraryOtherFileActionType.fromString(preferences.get(EPISODE_LIBRARY_OTHER_FILE_ACTION.getKey(), ""))); + settings.episodeLibrarySettings.otherFileAction = + LibraryOtherFileActionType.fromString(preferences.get(EPISODE_LIBRARY_OTHER_FILE_ACTION.getKey(), "")); EPISODE_LIBRARY_OTHER_FILE_ACTION.store(this, preferences); - settings.getMovieLibrarySettings() - .setLibraryOtherFileAction(LibraryOtherFileActionType.fromString(preferences.get(MOVIE_LIBRARY_OTHER_FILE_ACTION.getKey(), ""))); + settings.movieLibrarySettings.otherFileAction = + LibraryOtherFileActionType.fromString(preferences.get(MOVIE_LIBRARY_OTHER_FILE_ACTION.getKey(), "")); MOVIE_LIBRARY_OTHER_FILE_ACTION.store(this, preferences); - settings.getEpisodeLibrarySettings() - .setLibraryAction(LibraryActionType.fromString(preferences.get(EPISODE_LIBRARY_ACTION.getKey(), ""))); + settings.episodeLibrarySettings.action = + LibraryActionType.fromString(preferences.get(EPISODE_LIBRARY_ACTION.getKey(), "")); EPISODE_LIBRARY_ACTION.store(this, preferences); - settings.getMovieLibrarySettings() - .setLibraryAction(LibraryActionType.fromString(preferences.get(MOVIE_LIBRARY_ACTION.getKey(), ""))); + settings.movieLibrarySettings.action = + LibraryActionType.fromString(preferences.get(MOVIE_LIBRARY_ACTION.getKey(), "")); MOVIE_LIBRARY_ACTION.store(this, preferences); - settings.setSettingsVersion(2); + settings.settingsVersion = 2; SETTINGS_VERSION.store(this, preferences); } @@ -252,7 +255,7 @@ public void migrateSettingsV2ToV3() { // }); // preferences.remove("DictionarySize"); - settings.setSettingsVersion(3); + settings.settingsVersion = 3; SETTINGS_VERSION.store(this, preferences); } @@ -269,44 +272,43 @@ public void migrateSettingsV3ToV4() { // preferences.put("ExcludeItem" + i, newValue); // }); // EXCLUDE_ITEM.store(this, preferences); - settings.setSettingsVersion(4); + settings.settingsVersion = 4; SETTINGS_VERSION.store(this, preferences); } public void migrateSettingsV4ToV5() { - Arrays.stream(MappingType.ADDIC7ED_PROXY.getSelectionForKeyPrefixList()) - .forEach(selectionForKeyPrefix -> MappingType.MAPPING_SUPPLIER.apply(manager, selectionForKeyPrefix) - .forEach(serieMappingPair -> { - manager.valueBuilder() - .cacheType(CacheType.DISK) - .key(serieMappingPair.getKey()) - .remove(); - })); - settings.setSettingsVersion(5); + MappingType.ADDIC7ED_PROXY.selectionForKeyPrefixList + .forEach(selectionForKeyPrefix -> MappingType.MAPPING_SUPPLIER.apply(manager, selectionForKeyPrefix) + .forEach(serieMappingPair -> manager.getCache(DISK, serieMappingPair.getKey()).remove())); + settings.settingsVersion = 5; SETTINGS_VERSION.store(this, preferences); } public void migrateSettingsV5ToV6() { IntStream.range(0, preferences.getInt("ExcludeItemSize", 0)) - .forEach(i -> preferences.put("ExcludeItem" + i, preferences.get("ExcludeItem" + i, "").split("//", 2)[1])); + .forEach(i -> preferences.put("ExcludeItem" + i, + preferences.get("ExcludeItem" + i, "").split("//", 2)[1])); EXCLUDE_ITEM.store(this, preferences); // Conversion from String to enum + remove duplicates int defaultSelectionQualitySize = preferences.getInt("DefaultSelectionQualitySize", 0); if (defaultSelectionQualitySize > 0) { List defaultSelectionQualitySizes = IntStream.range(0, defaultSelectionQualitySize) - .mapToObj(i -> VideoPatterns.Source.fromValue(preferences.get("DefaultSelectionQuality" + i, ""))).distinct().toList(); + .mapToObj(i -> VideoPatterns.Source.fromValue(preferences.get("DefaultSelectionQuality" + i, ""))) + .distinct() + .toList(); IntStream.range(0, defaultSelectionQualitySizes.size()) - .forEach(i -> preferences.put("DefaultSelectionQuality" + i, defaultSelectionQualitySizes.get(i).name())); + .forEach(i -> preferences.put("DefaultSelectionQuality" + i, + defaultSelectionQualitySizes.get(i).name())); if (defaultSelectionQualitySize != defaultSelectionQualitySizes.size()) { preferences.putInt("DefaultSelectionQualitySize", defaultSelectionQualitySizes.size()); IntStream.range(defaultSelectionQualitySize, defaultSelectionQualitySizes.size()) - .forEach(i -> preferences.remove("DefaultSelectionQuality" + i)); + .forEach(i -> preferences.remove("DefaultSelectionQuality" + i)); } } DEFAULT_SELECTION_QUALITY.store(this, preferences); - settings.setSettingsVersion(6); + settings.settingsVersion = 6; SETTINGS_VERSION.store(this, preferences); } @@ -315,47 +317,50 @@ public void migrateSettingsV6ToV7() { String value = preferences.get(label, null); preferences.remove(label); if (StringUtils.isNotBlank(value)) { - librarySettings.getLangCodeMap().put(language, value.trim()); + librarySettings.langCodeMap.put(language, value.trim()); } }; - consumer.accept("EpisodeLibraryDefaultNlText", Language.DUTCH, settings.getEpisodeLibrarySettings()); - consumer.accept("EpisodeLibraryDefaultEnText", Language.ENGLISH, settings.getEpisodeLibrarySettings()); - consumer.accept("MovieLibraryDefaultNlText", Language.DUTCH, settings.getMovieLibrarySettings()); - consumer.accept("MovieLibraryDefaultENText", Language.ENGLISH, settings.getMovieLibrarySettings()); + consumer.accept("EpisodeLibraryDefaultNlText", Language.DUTCH, settings.episodeLibrarySettings); + consumer.accept("EpisodeLibraryDefaultEnText", Language.ENGLISH, settings.episodeLibrarySettings); + consumer.accept("MovieLibraryDefaultNlText", Language.DUTCH, settings.movieLibrarySettings); + consumer.accept("MovieLibraryDefaultENText", Language.ENGLISH, settings.movieLibrarySettings); EPISODE_LIBRARY_LANG_CODE_MAPPING.store(this, preferences); MOVIE_LIBRARY_LANG_CODE_MAPPING.store(this, preferences); - if (settings.getEpisodeLibrarySettings().hasAnyLibraryAction(LibraryActionType.RENAME, LibraryActionType.MOVEANDRENAME)) { - if (StringUtils.isBlank(settings.getEpisodeLibrarySettings().getLibraryFilenameStructure())) { - settings.getMovieLibrarySettings().setLibraryFilenameStructure("%SHOW NAME%%SEPARATOR%%Season %S%"); + if (settings.episodeLibrarySettings.hasAnyLibraryAction(LibraryActionType.RENAME, + LibraryActionType.MOVEANDRENAME)) { + if (StringUtils.isBlank(settings.episodeLibrarySettings.filenameStructure)) { + settings.movieLibrarySettings.filenameStructure = "%SHOW NAME%%SEPARATOR%%Season %S%"; MOVIE_LIBRARY_FILENAME_STRUCTURE.store(this, preferences); EPISODE_LIBRARY_FILENAME_STRUCTURE.store(this, preferences); } - if (StringUtils.isBlank(settings.getEpisodeLibrarySettings().getLibraryFolderStructure())) { - settings.getMovieLibrarySettings().setLibraryFolderStructure("%SHOW NAME%.S%SS%E%EE%.%TITLE%"); + if (StringUtils.isBlank(settings.episodeLibrarySettings.folderStructure)) { + settings.movieLibrarySettings.folderStructure = "%SHOW NAME%.S%SS%E%EE%.%TITLE%"; EPISODE_LIBRARY_FOLDER_STRUCTURE.store(this, preferences); } } - if (settings.getMovieLibrarySettings().hasAnyLibraryAction(LibraryActionType.RENAME, LibraryActionType.MOVEANDRENAME)) { - if (StringUtils.isBlank(settings.getMovieLibrarySettings().getLibraryFilenameStructure())) { - settings.getMovieLibrarySettings().setLibraryFilenameStructure("%MOVIE TITLE% (%YEAR%)"); + if (settings.movieLibrarySettings.hasAnyLibraryAction(LibraryActionType.RENAME, + LibraryActionType.MOVEANDRENAME)) { + if (StringUtils.isBlank(settings.movieLibrarySettings.filenameStructure)) { + settings.movieLibrarySettings.filenameStructure = "%MOVIE TITLE% (%YEAR%)"; MOVIE_LIBRARY_FILENAME_STRUCTURE.store(this, preferences); } } - settings.setSettingsVersion(7); + settings.settingsVersion = 7; SETTINGS_VERSION.store(this, preferences); } public void migrateSettingsV7ToV8() { - if (settings.isLoginOpenSubtitlesEnabled() - && !OpenSubtitlesApi.isValidCredentials(settings.getLoginOpenSubtitlesUsername(), settings.getLoginOpenSubtitlesPassword())) { - settings.setLoginOpenSubtitlesEnabled(false); + if (settings.loginOpenSubtitlesEnabled && + !OpenSubtitlesApi.isValidCredentials(settings.loginOpenSubtitlesUsername, + settings.loginOpenSubtitlesPassword)) { + settings.loginOpenSubtitlesEnabled = false; LOGIN_OPEN_SUBTITLES_ENABLED.store(this, preferences); } - settings.setSettingsVersion(8); + settings.settingsVersion = 8; SETTINGS_VERSION.store(this, preferences); } @@ -376,7 +381,7 @@ private static String migrateLibraryStructureV0(String oldStructure) { } private void migrateDatabase() { - int version = manager.valueBuilder().cacheType(CacheType.DISK).key("DATABSE_VERSION").valueSupplier(() -> 0).get(); + int version = manager.getCache(DISK, DATABASE_VERSION_KEY).get(() -> 0); if (version == 0) { migrateDatabaseV0ToV1(); } @@ -386,33 +391,33 @@ private void migrateDatabase() { } private void migrateDatabaseV0ToV1() { - manager.valueBuilder().cacheType(CacheType.DISK).keyFilter(k -> k.startsWith("TVDB-SerieMapping-")).remove(); - manager.valueBuilder().cacheType(CacheType.DISK).keyFilter(k -> k.startsWith("TVDB-SerieId-")).remove(); - manager.valueBuilder().cacheType(CacheType.DISK).key("DATABSE_VERSION").value(1).store(); + manager.getCache(DISK, k -> k.startsWith("TVDB-SerieMapping-")).remove(); + manager.getCache(DISK, k -> k.startsWith("TVDB-SerieId-")).remove(); + manager.getCache(DISK, DATABASE_VERSION_KEY).store(Value.of(1)); } private void migrateDatabaseV1ToV2() { - List> entries = manager.valueBuilder().cacheType(CacheType.DISK) - .keyFilter(k -> k.startsWith("SUBSCENE-serieName-")).returnType(SerieMapping.class).getEntries(); - List> editedEntries = entries.stream().map(pair -> { - int lastIndexOfDash = pair.getKey().lastIndexOf("-"); - int season; - try { - season = Integer.parseInt(pair.getKey().substring(lastIndexOfDash + 1)); - } catch (NumberFormatException e) { - season = -1; - } - String name = pair.getValue().getName(); - String providerId = pair.getValue().getProviderId(); - String providerName = pair.getValue().getProviderName(); - SerieMapping serieMapping = new SerieMapping(name, providerId, providerName, season); - System.out.println(serieMapping); - return Pair.of(pair.getKey(), serieMapping); - }).toList(); + List> editedEntries = + manager.getCache(DISK, k -> k.startsWith("SUBSCENE-serieName-")) + .getEntries(SerieMapping.class) + .stream().map(pair -> { + int lastIndexOfDash = pair.getKey().lastIndexOf("-"); + int season; + try { + season = Integer.parseInt(pair.getKey().substring(lastIndexOfDash + 1)); + } catch (NumberFormatException e) { + season = -1; + } + String name = pair.getValue().name; + String providerId = pair.getValue().providerId; + String providerName = pair.getValue().providerName; + SerieMapping serieMapping = new SerieMapping(name, providerId, providerName, season); + return Pair.of(pair.getKey(), serieMapping); + }).toList(); editedEntries.forEach(entry -> { - manager.valueBuilder().cacheType(CacheType.DISK).key(entry.getKey()).remove(); - manager.valueBuilder().cacheType(CacheType.DISK).key(entry.getKey()).value(entry.getValue()).store(); + manager.getCache(DISK, entry.key).remove(); + manager.getCache(DISK, entry.key).store(Value.of(entry.getValue())); }); - manager.valueBuilder().cacheType(CacheType.DISK).key("DATABSE_VERSION").value(2).store(); + manager.getCache(DISK, DATABASE_VERSION_KEY).store(Value.of(2)); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/LibrarySettings.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/LibrarySettings.java index c1301277..95b0335f 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/LibrarySettings.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/LibrarySettings.java @@ -1,49 +1,42 @@ package org.lodder.subtools.multisubdownloader.settings.model; import java.nio.file.Path; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; +import manifold.ext.props.rt.api.var; import org.lodder.subtools.multisubdownloader.lib.library.LibraryActionType; import org.lodder.subtools.multisubdownloader.lib.library.LibraryOtherFileActionType; import org.lodder.subtools.sublibrary.Language; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; - -@Getter -@Setter -@Accessors(chain = true) public class LibrarySettings { - private String libraryFilenameStructure = ""; - private String libraryFolderStructure = ""; - private Path libraryFolder; - private boolean libraryFilenameReplaceSpace; - private boolean libraryFolderReplaceSpace; - private boolean libraryIncludeLanguageCode; - private boolean libraryRemoveEmptyFolders; - private boolean libraryUseTVDBNaming; - private LibraryActionType libraryAction = LibraryActionType.NOTHING; - private LibraryOtherFileActionType libraryOtherFileAction = LibraryOtherFileActionType.NOTHING; - private Character libraryFilenameReplacingSpaceChar; - private Character libraryFolderReplacingSpaceChar; - private boolean libraryBackupSubtitle; - private boolean libraryBackupUseWebsiteFileName; - private Path libraryBackupSubtitlePath; - private Map langCodeMap = new LinkedHashMap<>(); + @var String filenameStructure = ""; + @var String folderStructure = ""; + @var Path folder; + @var boolean filenameReplaceSpace; + @var boolean folderReplaceSpace; + @var boolean includeLanguageCode; + @var boolean removeEmptyFolders; + @var boolean useTVDBNaming; + @var LibraryActionType action = LibraryActionType.NOTHING; + @var LibraryOtherFileActionType otherFileAction = LibraryOtherFileActionType.NOTHING; + @var Character filenameReplacingSpaceChar; + @var Character folderReplacingSpaceChar; + @var boolean backupSubtitle; + @var boolean backupUseWebsiteFileName; + @var Path backupSubtitlePath; + @var Map langCodeMap = new LinkedHashMap<>(); public boolean hasLibraryAction(LibraryActionType libraryAction) { - return this.libraryAction == libraryAction; + return this.action == libraryAction; } public boolean hasAnyLibraryAction(LibraryActionType... libraryActions) { - return Arrays.stream(libraryActions).anyMatch(this::hasLibraryAction); + return libraryActions.stream().anyMatch(this::hasLibraryAction); } public boolean hasLibraryOtherFileAction(LibraryOtherFileActionType libraryOtherFileAction) { - return this.libraryOtherFileAction == libraryOtherFileAction; + return this.otherFileAction == libraryOtherFileAction; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathMatchType.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathMatchType.java index 7d587202..e1d831a8 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathMatchType.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathMatchType.java @@ -1,17 +1,15 @@ package org.lodder.subtools.multisubdownloader.settings.model; -import java.awt.Image; -import java.awt.Toolkit; +import java.awt.*; -import lombok.Getter; +import manifold.ext.props.rt.api.val; -@Getter public enum PathMatchType { FOLDER("/folder.png"), REGEX("/regex.gif"), FILE("/file.jpg"); - private final Image image; + @val Image image; PathMatchType(String imagePath) { this.image = Toolkit.getDefaultToolkit().getImage(getClass().getResource(imagePath)); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathOrRegex.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathOrRegex.java index de770f93..474772fc 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathOrRegex.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/PathOrRegex.java @@ -1,5 +1,7 @@ package org.lodder.subtools.multisubdownloader.settings.model; +import java.awt.*; +import java.io.Serial; import java.io.Serializable; import java.nio.file.Files; import java.nio.file.InvalidPathException; @@ -8,19 +10,15 @@ import java.util.function.Predicate; import java.util.regex.Pattern; -import org.lodder.subtools.sublibrary.util.NamedPattern; - -import java.awt.Image; - -import lombok.Getter; +import manifold.ext.props.rt.api.val; +import manifold.ext.rt.api.Self; public class PathOrRegex implements Serializable { - private static final long serialVersionUID = 1L; - @Getter - private final String value; - @Getter - private final transient Image image; + @Serial private static final long serialVersionUID = 1L; + + @val String value; + @val transient Image image; private final transient Predicate isExcludedPathPredicate; public PathOrRegex(Path path) { @@ -40,9 +38,9 @@ public PathOrRegex(String value) { regex = true; } if (regex) { - this.image = PathMatchType.REGEX.getImage(); - NamedPattern np = NamedPattern.compile(value.replace("*", ".*") + ".*$", Pattern.CASE_INSENSITIVE); - this.isExcludedPathPredicate = p -> np.matcher(p.getFileName().toString()).find(); + this.image = PathMatchType.REGEX.image; + Pattern pattern = Pattern.compile(value.replace("*", ".*") + ".*$", Pattern.CASE_INSENSITIVE); + this.isExcludedPathPredicate = p -> pattern.matcher(p.getFileName().toString()).find(); } else { this.image = getImage(path); this.isExcludedPathPredicate = path::equals; @@ -50,7 +48,7 @@ public PathOrRegex(String value) { } private Image getImage(Path path) { - return Files.isDirectory(path) ? PathMatchType.FOLDER.getImage() : PathMatchType.FILE.getImage(); + return Files.isDirectory(path) ? PathMatchType.FOLDER.image : PathMatchType.FILE.image; } public boolean isExcludedPath(Path path) { @@ -68,7 +66,7 @@ public int hashCode() { } @Override - public boolean equals(Object obj) { - return obj instanceof PathOrRegex other && Objects.equals(value, other.getValue()); + public boolean equals(@Self Object obj) { + return obj instanceof PathOrRegex other && Objects.equals(value, other.value); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/ScreenSettings.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/ScreenSettings.java index 336c461d..fbb676f7 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/ScreenSettings.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/ScreenSettings.java @@ -1,16 +1,14 @@ package org.lodder.subtools.multisubdownloader.settings.model; -import lombok.Getter; -import lombok.Setter; -@Getter -@Setter +import manifold.ext.props.rt.api.var; + public class ScreenSettings { - private boolean hideEpisode; - private boolean hideSeason; - private boolean hideTitle; - private boolean hideWIP; - private boolean hideType; - private boolean hideFilename; + @var boolean hideEpisode; + @var boolean hideSeason; + @var boolean hideTitle; + @var boolean hideWIP; + @var boolean hideType; + @var boolean hideFilename; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/Settings.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/Settings.java index ccce4693..79af3894 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/Settings.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/Settings.java @@ -2,7 +2,6 @@ import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -11,97 +10,89 @@ import java.util.Map; import java.util.Set; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import manifold.ext.props.rt.api.var; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.control.VideoPatterns; import org.lodder.subtools.sublibrary.data.UserInteractionSettingsIntf; import org.lodder.subtools.sublibrary.model.SubtitleSource; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; - -@Getter -@Setter -@Accessors(chain = true) -@ExtensionMethod({ Arrays.class }) public class Settings implements UserInteractionSettingsIntf { - private Path lastOutputDir; - private boolean optionsAlwaysConfirm; - private boolean optionSubtitleExactMatch = true; - private boolean optionSubtitleKeywordMatch = true; - private boolean optionSubtitleExcludeHearingImpaired; - private boolean optionsShowOnlyFound, optionsStopOnSearchError; - private final Set excludeList = new LinkedHashSet<>(); - private final LibrarySettings movieLibrarySettings = new LibrarySettings(); - private final LibrarySettings episodeLibrarySettings = new LibrarySettings(); - private String generalProxyHost = ""; - private int generalProxyPort = 80; - private boolean generalProxyEnabled; - private List defaultIncomingFolders = new ArrayList<>(); - private List localSourcesFolders = new ArrayList<>(); - private boolean optionRecursive; - private final ScreenSettings screenSettings = new ScreenSettings(); - private boolean loginAddic7edEnabled; - private String loginAddic7edUsername; - private String loginAddic7edPassword; - private boolean loginOpenSubtitlesEnabled; - private String loginOpenSubtitlesUsername; - private String loginOpenSubtitlesPassword; - private boolean serieSourceAddic7ed = true; - private boolean serieSourceAddic7edProxy = true; - private boolean serieSourceTvSubtitles = true; - private boolean serieSourcePodnapisi = true; - private boolean serieSourceOpensubtitles = true; - private boolean serieSourceLocal = true; - private boolean serieSourceSubscene = true; - private SettingsProcessEpisodeSource processEpisodeSource = SettingsProcessEpisodeSource.TVDB; - private final Map sortWeights; - private Language subtitleLanguage; - private boolean optionsMinAutomaticSelection; - private int optionsMinAutomaticSelectionValue; - private UpdateCheckPeriod updateCheckPeriod; - private UpdateType updateType; - private boolean optionsDefaultSelection; - private List optionsDefaultSelectionQualityList = new ArrayList<>(); - private int settingsVersion; - private boolean optionsConfirmProviderMapping; - private Language language; + @var Path lastOutputDir; + @override @var boolean optionsAlwaysConfirm; + @var boolean optionSubtitleExactMatch = true; + @var boolean optionSubtitleKeywordMatch = true; + @var boolean optionSubtitleExcludeHearingImpaired; + @var boolean optionsShowOnlyFound; + @var boolean optionsStopOnSearchError; + @val Set excludeList = new LinkedHashSet<>(); + @val LibrarySettings movieLibrarySettings = new LibrarySettings(); + @val LibrarySettings episodeLibrarySettings = new LibrarySettings(); + @var String generalProxyHost = ""; + @var int generalProxyPort = 80; + @var boolean generalProxyEnabled; + @var List defaultIncomingFolders = new ArrayList<>(); + @var List localSourcesFolders = new ArrayList<>(); + @var boolean optionRecursive; + @val ScreenSettings screenSettings = new ScreenSettings(); + @var boolean loginAddic7edEnabled; + @var String loginAddic7edUsername; + @var String loginAddic7edPassword; + @var boolean loginOpenSubtitlesEnabled; + @var String loginOpenSubtitlesUsername; + @var String loginOpenSubtitlesPassword; + @var boolean serieSourceAddic7ed = true; + @var boolean serieSourceAddic7edProxy = true; + @var boolean serieSourceTvSubtitles = true; + @var boolean serieSourcePodnapisi = true; + @var boolean serieSourceOpensubtitles = true; + @var boolean serieSourceLocal = true; + @var boolean serieSourceSubscene = true; + @var SettingsProcessEpisodeSource processEpisodeSource = SettingsProcessEpisodeSource.TVDB; + @val Map sortWeights; + @var Language subtitleLanguage; + @override @var boolean optionsMinAutomaticSelection; + @override @var int optionsMinAutomaticSelectionValue; + @var UpdateCheckPeriod updateCheckPeriod; + @var UpdateType updateType; + @override @var boolean optionsDefaultSelection; + @override @var List optionsDefaultSelectionQualityList = new ArrayList<>(); + @var int settingsVersion; + @override @var boolean optionsConfirmProviderMapping; + @var Language language; public Settings() { // TODO: user should be able to edit/add these through a panel Map sortWeightsTemp = new HashMap<>(); sortWeightsTemp.put("%GROUP%", 5); - VideoPatterns.Source.values().stream() - .forEach(source -> source.getValues().stream() - .forEach(keyword -> sortWeightsTemp.put(keyword, source.isManyDifferentSources() ? 1 : 2))); - VideoPatterns.AudioEncoding.values().stream() - .forEach(encoding -> encoding.getValues().stream().forEach(keyword -> sortWeightsTemp.put(keyword, 2))); + VideoPatterns.Source.values().forEach(source -> sortWeightsTemp.put(source.regex, 2)); + VideoPatterns.AudioEncoding.values().forEach(encoding -> sortWeightsTemp.put(encoding.regex, 2)); this.sortWeights = Collections.unmodifiableMap(sortWeightsTemp); } public List getDefaultFolders() { - return getDefaultIncomingFolders(); + return defaultIncomingFolders; } public boolean hasDefaultFolders() { - return !getDefaultIncomingFolders().isEmpty(); + return !defaultIncomingFolders.isEmpty(); } - public boolean isSerieSource(SubtitleSource sbtitleSource) { + public boolean useSerieSource(SubtitleSource subtitleSource) { // TODO: dynamically inject SubtitleProvider to settings - return switch (sbtitleSource) { - case ADDIC7ED -> this.isSerieSourceAddic7ed(); - case OPENSUBTITLES -> this.isSerieSourceOpensubtitles(); - case PODNAPISI -> this.isSerieSourcePodnapisi(); - case TVSUBTITLES -> this.isSerieSourceTvSubtitles(); - case LOCAL -> this.isSerieSourceLocal(); - case SUBSCENE -> this.isSerieSourceSubscene(); + return switch (subtitleSource) { + case ADDIC7ED -> serieSourceAddic7ed; + case OPENSUBTITLES -> serieSourceOpensubtitles; + case PODNAPISI -> serieSourcePodnapisi; + case TVSUBTITLES -> serieSourceTvSubtitles; + case LOCAL -> serieSourceLocal; + case SUBSCENE -> serieSourceSubscene; }; } - public Settings setExcludeList(Collection exclusions) { + public Settings replaceExcludeList(Collection exclusions) { excludeList.clear(); excludeList.addAll(exclusions); return this; diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/SettingsExcludeItem.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/SettingsExcludeItem.java index ef44ad9b..4fe62ee8 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/SettingsExcludeItem.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/SettingsExcludeItem.java @@ -4,17 +4,14 @@ import java.util.function.Predicate; import java.util.regex.Pattern; -import org.lodder.subtools.sublibrary.util.NamedPattern; - -import lombok.Getter; +import manifold.ext.props.rt.api.get; @Deprecated(since = "Settings version 6") -@Getter public class SettingsExcludeItem { - private final String description; - private final PathMatchType type; - private final Predicate isExcludedPredicate; + @get String description; + @get PathMatchType type; + @get Predicate isExcludedPredicate; public SettingsExcludeItem(String description, PathMatchType type) { this.description = description; @@ -22,8 +19,8 @@ public SettingsExcludeItem(String description, PathMatchType type) { this.isExcludedPredicate = switch (type) { case FOLDER, FILE -> Path.of(description)::equals; case REGEX -> { - NamedPattern np = NamedPattern.compile(description.replace("*", ".*") + ".*$", Pattern.CASE_INSENSITIVE); - yield file -> np.matcher(file.getFileName().toString()).find(); + Pattern pattern = Pattern.compile(description.replace("*", ".*") + ".*$", Pattern.CASE_INSENSITIVE); + yield file -> pattern.matcher(file.getFileName().toString()).find(); } }; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/State.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/State.java index edd95b5d..1cffb265 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/State.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/State.java @@ -2,12 +2,9 @@ import java.time.LocalDate; -import lombok.Getter; -import lombok.Setter; +import manifold.ext.props.rt.api.var; -@Getter -@Setter public class State { - private LocalDate latestUpdateCheck; + @var LocalDate latestUpdateCheck; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateCheckPeriod.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateCheckPeriod.java index db2f1d7b..de586eb4 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateCheckPeriod.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateCheckPeriod.java @@ -1,16 +1,15 @@ package org.lodder.subtools.multisubdownloader.settings.model; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum UpdateCheckPeriod { MANUAL("InputPanel.UpdateInterval.Manual"), DAILY("InputPanel.UpdateInterval.Daily"), WEEKLY("InputPanel.UpdateInterval.Weekly"), MONTHLY("InputPanel.UpdateInterval.Monthly"); - private final String langCode; + @val String langCode; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateType.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateType.java index 8a3f0e2e..07959558 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateType.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/UpdateType.java @@ -1,13 +1,12 @@ package org.lodder.subtools.multisubdownloader.settings.model; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; -@RequiredArgsConstructor -@Getter +@AllArgsConstructor public enum UpdateType { STABLE("InputPanel.UpdateType.Stable"), NIGHTLY("InputPanel.UpdateType.Nightly"); - private final String msgCode; + @val String msgCode; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/FolderStructureTag.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/FolderStructureTag.java index f805a643..23cc2ff4 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/FolderStructureTag.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/FolderStructureTag.java @@ -1,17 +1,16 @@ package org.lodder.subtools.multisubdownloader.settings.model.structure; -import org.lodder.subtools.multisubdownloader.Messages; - import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import org.lodder.subtools.multisubdownloader.Messages; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum FolderStructureTag implements StructureTag { - SEPARATOR("%SEPARATOR%", Messages.getString("StructureBuilderDialog.SystemdependendSeparator")); + SEPARATOR("%SEPARATOR%", Messages.getText("StructureBuilderDialog.SystemDependentSeparator")); - private final String label; - private final String description; + @val @override String label; + @val @override String description; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/MovieStructureTag.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/MovieStructureTag.java index b3c29d54..bb628a45 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/MovieStructureTag.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/MovieStructureTag.java @@ -1,21 +1,21 @@ package org.lodder.subtools.multisubdownloader.settings.model.structure; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.Messages; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum MovieStructureTag implements StructureTag { - MOVIE_TITLE("%MOVIE TITLE%", Messages.getString("StructureBuilderDialog.MovieName")), - QUALITY("%QUALITY%", Messages.getString("StructureBuilderDialog.QualityOfMovie")), - DESCRIPTION("%DESCRIPTION%", Messages.getString("StructureBuilderDialog.MovieDescription")), - YEAR("%YEAR%", Messages.getString("StructureBuilderDialog.MovieYear")); + MOVIE_TITLE("%MOVIE TITLE%", "StructureBuilderDialog.MovieName"), + QUALITY("%QUALITY%", "StructureBuilderDialog.QualityOfMovie"), + YEAR("%YEAR%", "StructureBuilderDialog.MovieYear"), + RELEASE_GROUP("%RELEASE GROUP%", "StructureBuilderDialog.ReleaseGroup"); - private final String label; - private final String description; + @val @override String label; + @val @override String description; + MovieStructureTag(String label, String descriptionMessage) { + this.label = label; + this.description = Messages.getText(descriptionMessage); + } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/SerieStructureTag.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/SerieStructureTag.java index f092f23d..716a0224 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/SerieStructureTag.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/SerieStructureTag.java @@ -1,29 +1,28 @@ package org.lodder.subtools.multisubdownloader.settings.model.structure; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.Messages; -import lombok.Getter; - -@Getter public enum SerieStructureTag implements StructureTag { SHOW_NAME("%SHOW NAME%", "StructureBuilderDialog.NameTvShow"), TITLE("%TITLE%", "StructureBuilderDialog.EpisodeTitle"), EPISODE_LONG("%EE%", "StructureBuilderDialog.NumberOfEpisodeLeadingZero"), - EPISODES_LONG("%EEX%", "StructureBuilderDialog.NumberOfEpisodeLeadingZeroForMultipe"), + EPISODES_LONG("%EEX%", "StructureBuilderDialog.NumberOfEpisodeLeadingZeroForMultiple"), EPISODE_SHORT("%E%", "StructureBuilderDialog.NumberOfEpisodeWithoutLeadingZero"), EPISODES_SHORT("%EX%", "StructureBuilderDialog.NumberOfEpisodeLeadingZeroMultiple"), SEASON_LONG("%SS%", "StructureBuilderDialog.NumberOfSeasonLeading"), SEASON_SHORT("%S%", "StructureBuilderDialog.NumberOfSeasonsWithoutLeading"), QUALITY("%QUALITY%", "StructureBuilderDialog.QualityOfRelease"), - DESCRIPTION("%DESCRIPTION%", "StructureBuilderDialog.Description"); + RELEASE_GROUP("%RELEASE GROUP%", "StructureBuilderDialog.ReleaseGroup"); - private final String label; - private final String description; + @val @override String label; + @val @override String description; SerieStructureTag(String label, String descriptionMessage) { this.label = label; - this.description = Messages.getString(descriptionMessage); + this.description = Messages.getText(descriptionMessage); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/StructureTag.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/StructureTag.java index 9a38a334..811aac95 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/StructureTag.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/settings/model/structure/StructureTag.java @@ -1,9 +1,10 @@ package org.lodder.subtools.multisubdownloader.settings.model.structure; -public interface StructureTag { +import manifold.ext.props.rt.api.val; - String getLabel(); +public interface StructureTag { - String getDescription(); + @val String label; + @val String description; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/Local.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/Local.java index 09bd5449..9d16c0bf 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/Local.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/Local.java @@ -5,9 +5,13 @@ import java.nio.file.Path; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.NotImplementedException; import org.lodder.subtools.multisubdownloader.lib.control.MovieReleaseControl; import org.lodder.subtools.multisubdownloader.lib.control.TvReleaseControl; @@ -27,23 +31,18 @@ import org.lodder.subtools.sublibrary.model.VideoType; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler; -import org.lodder.subtools.sublibrary.util.FileUtils; -import org.lodder.subtools.sublibrary.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ FileUtils.class, Files.class }) +@ExtensionMethod({ Files.class }) public class Local implements SubtitleProvider { private static final Logger LOGGER = LoggerFactory.getLogger(Local.class); private final Settings settings; - @Getter - private final Manager manager; private final UserInteractionHandler userInteractionHandler; + @val @override Manager manager; + @val @override SubtitleSource subtitleSource = SubtitleSource.LOCAL; public Local(Settings settings, Manager manager, UserInteractionHandler userInteractionHandler) { this.settings = settings; @@ -51,15 +50,10 @@ public Local(Settings settings, Manager manager, UserInteractionHandler userInte this.userInteractionHandler = userInteractionHandler; } - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.LOCAL; - } - private List getPossibleSubtitles(String filter) { - return settings.getLocalSourcesFolders().stream() - .flatMap(local -> getAllSubtitlesFiles(local, filter).stream()) - .toList(); + return settings.localSourcesFolders.stream() + .flatMap(local -> getAllSubtitlesFiles(local, filter).stream()) + .toList(); } @Override @@ -67,36 +61,33 @@ public Set searchSubtitles(TvRelease tvRelease, Language language) { Set listFoundSubtitles = new HashSet<>(); ReleaseParser vfp = new ReleaseParser(); - String filter; - if (tvRelease.getOriginalName().length() > 0) { - filter = tvRelease.getOriginalName().replaceAll("[^A-Za-z]", "").trim(); - } else { - filter = tvRelease.getName().replaceAll("[^A-Za-z]", "").trim(); - } + String name = !tvRelease.originalName.isEmpty() ? tvRelease.originalName : tvRelease.name; + String filter = name.replaceAll("[^A-Za-z]", "").trim(); for (Path fileSub : getPossibleSubtitles(filter)) { try { Release release = vfp.parse(fileSub); - if ((release.getVideoType() == VideoType.EPISODE) - && (((TvRelease) release).getSeason() == tvRelease.getSeason() && Utils.containsAll( - ((TvRelease) release).getEpisodeNumbers(), tvRelease.getEpisodeNumbers()))) { + if ((release.videoType == VideoType.EPISODE) + && (((TvRelease) release).season == tvRelease.season && + new HashSet<>(((TvRelease) release).episodes).containsAll(tvRelease.episodes))) { - TvReleaseControl epCtrl = new TvReleaseControl((TvRelease) release, settings, manager, userInteractionHandler); + TvReleaseControl epCtrl = + new TvReleaseControl((TvRelease) release, settings, manager, userInteractionHandler); epCtrl.process(); - if (((TvRelease) release).getTvdbId().equals(tvRelease.getTvdbId())) { + if (Objects.equals(release.tvdbId, tvRelease.tvdbId)) { Language detectedLang = DetectLanguage.execute(fileSub); if (detectedLang == language) { LOGGER.debug("Local Sub found, adding [{}]", fileSub); - listFoundSubtitles.add( - Subtitle.downloadSource(fileSub) - .subtitleSource(getSubtitleSource()) - .fileName(fileSub.getFileNameAsString()) - .language(language) - .quality(ReleaseParser.getQualityKeyword(fileSub.getFileNameAsString())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(ReleaseParser.extractReleasegroup(fileSub.getFileNameAsString(), true)) - .uploader(fileSub.toAbsolutePath().toString()) - .hearingImpaired(false)); + listFoundSubtitles.add(new Subtitle( + downloadSource:Subtitle.DownloadSource.of(fileSub), + subtitleSource:subtitleSource, + fileName:fileSub.fileNameAsString, + language:language, + quality:ReleaseParser.getQualityKeyword(fileSub.fileNameAsString), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:ReleaseParser.extractReleaseGroup(fileSub.fileNameAsString, true), + uploader:fileSub.toAbsolutePath().toString(), + hearingImpaired:false)); } } } @@ -117,30 +108,32 @@ public Set searchSubtitles(MovieRelease movieRelease, Language languag Set listFoundSubtitles = new HashSet<>(); ReleaseParser releaseParser = new ReleaseParser(); - String filter = movieRelease.getName(); + String filter = movieRelease.name; for (Path fileSub : getPossibleSubtitles(filter)) { try { - Release release = releaseParser.parse(fileSub); - if (release.getVideoType() == VideoType.MOVIE) { - MovieReleaseControl movieCtrl = new MovieReleaseControl((MovieRelease) release, settings, manager, userInteractionHandler); - movieCtrl.process(); - if (((MovieRelease) release).getImdbId().equals(movieRelease.getImdbId())) { - Language detectedLang = DetectLanguage.execute(fileSub); - if (detectedLang == language) { + switch (releaseParser.parse(fileSub)) { + case MovieRelease release -> { + MovieReleaseControl movieCtrl = + new MovieReleaseControl(release, settings, manager, userInteractionHandler); + movieCtrl.process(); + if (Objects.equals(release.imdbId, movieRelease.imdbId) + && DetectLanguage.execute(fileSub) == language) { LOGGER.debug("Local Sub found, adding {}", fileSub); - listFoundSubtitles.add( - Subtitle.downloadSource(fileSub) - .subtitleSource(getSubtitleSource()) - .fileName(fileSub.getFileNameAsString()) - .language(language) // TODO previously: language(""). This was not correct? - .quality(ReleaseParser.getQualityKeyword(fileSub.getFileNameAsString())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(ReleaseParser.extractReleasegroup(fileSub.getFileNameAsString(), true)) - .uploader(fileSub.toAbsolutePath().toString()) - .hearingImpaired(false)); + listFoundSubtitles.add(new Subtitle( + downloadSource:Subtitle.DownloadSource.of(fileSub), + subtitleSource:subtitleSource, + fileName:fileSub.fileNameAsString, + language:language,// TODO previously: language(""). This was not correct? + quality:ReleaseParser.getQualityKeyword(fileSub.fileNameAsString), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:ReleaseParser.extractReleaseGroup(fileSub.fileNameAsString, true), + uploader:fileSub.toAbsolutePath().toString(), + hearingImpaired:false)); } } + default -> { + } } } catch (ReleaseParseException | ReleaseControlException e) { if (LOGGER.isDebugEnabled() || LOGGER.isTraceEnabled()) { @@ -157,9 +150,12 @@ public Set searchSubtitles(MovieRelease movieRelease, Language languag private List getAllSubtitlesFiles(Path dir, String filter) { try { return dir.list().filter(Files::isRegularFile) - .filter(file -> file.hasExtension("srt")) - .filter(file -> file.getFileNameAsString().replaceAll("[^A-Za-z]", "").toLowerCase().contains(filter.toLowerCase())) - .toList(); + .filter(file -> file.hasExtension("srt")) + .filter(file -> file.getFileNameAsString() + .replaceAll("[^A-Za-z]", "") + .toLowerCase() + .contains(filter.toLowerCase())) + .toList(); } catch (IOException e) { LOGGER.error(e.getMessage(), e); return List.of(); @@ -168,7 +164,7 @@ private List getAllSubtitlesFiles(Path dir, String filter) { @Override public String getProviderName() { - return getSubtitleSource().name(); + return subtitleSource.name(); } @Override diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleApi.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleApi.java index 78a2f4e1..d6b75efa 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleApi.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleApi.java @@ -1,9 +1,10 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.sublibrary.model.SubtitleSource; public interface SubtitleApi { - SubtitleSource getSubtitleSource(); + @val SubtitleSource subtitleSource; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleProvider.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleProvider.java index 7c8b776f..7aacd1f5 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleProvider.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/SubtitleProvider.java @@ -3,6 +3,7 @@ import java.util.Optional; import java.util.Set; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.cache.CacheType; @@ -16,19 +17,15 @@ public interface SubtitleProvider { + @val Manager manager; + @val SubtitleSource subtitleSource; + @val String providerName; + @val String name = subtitleSource.name; + Set searchSubtitles(TvRelease tvRelease, Language language); Set searchSubtitles(MovieRelease movieRelease, Language language); - SubtitleSource getSubtitleSource(); - - /** - * @return The name of the SubtitleProvider - */ - default String getName() { - return getSubtitleSource().getName(); - } - /** * Starts a search for subtitles * @@ -38,28 +35,20 @@ default String getName() { */ default Set search(Release release, Language language) { try { - if (release instanceof MovieRelease movieRelease) { - return this.searchSubtitles(movieRelease, language); - } else if (release instanceof TvRelease tvRelease) { - return this.searchSubtitles(tvRelease, language); - } + return switch (release) { + case MovieRelease movieRelease -> this.searchSubtitles(movieRelease, language); + case TvRelease tvRelease -> this.searchSubtitles(tvRelease, language); + }; } catch (Exception e) { - LoggerFactory.getLogger(SubtitleProvider.class).error("Error in %s API: %s".formatted(getName(), e.getMessage()), e); + LoggerFactory.getLogger(SubtitleProvider.class) + .error("Error in %s API: %s".formatted(name, e.getMessage()), e); } return Set.of(); } default void clearCache() { - getManager().clearExpiredCacheBuilder() - .cacheType(CacheType.DISK) - .keyFilter((String k) -> k.startsWith(getProviderName() + "-")) - .clear(); + manager.getCache(CacheType.DISK, k -> k.startsWith(providerName + "-")).clearExpiredCache(); } - String getProviderName(); - - Manager getManager(); - Optional getProviderSerieId(TvRelease tvRelease) throws X; - } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/AbstractAdapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/AbstractAdapter.java index 58365df7..459c5192 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/AbstractAdapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/AbstractAdapter.java @@ -1,79 +1,82 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.adapters; +import static manifold.science.measures.TimeUnit.*; +import static org.lodder.subtools.sublibrary.util.Sleep.*; + import java.util.ArrayList; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import com.pivovarit.function.ThrowingSupplier; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import manifold.ext.rt.api.Self; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.data.ProviderSerieId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.pivovarit.function.ThrowingSupplier; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; /** - * @param - * type of the subtitle objects returned by the api - * @param - * type of the exception thrown by the api + * @param type of the subtitle objects returned by the api + * @param type of the exception thrown by the api */ -@Getter -@RequiredArgsConstructor -abstract class AbstractAdapter implements Adapter, SubtitleProvider { - Logger LOGGER = LoggerFactory.getLogger(AbstractAdapter.class); - private final Manager manager; - private final UserInteractionHandler userInteractionHandler; +@AllArgsConstructor +abstract sealed class AbstractAdapter + implements Adapter, SubtitleProvider + permits JAddic7edAdapter, JAddic7edViaProxyAdapter, JOpenSubAdapter, JPodnapisiAdapter, JSubsceneAdapter, + JTVsubtitlesAdapter { + + @val @override Manager manager; + @val @override UserInteractionHandler userInteractionHandler; @RequiredArgsConstructor - public static class ExecuteCall> { + public static class ExecuteCall { private final ThrowingSupplier supplier; private String message; private int retries = 3; private final List> retryPredicates = new ArrayList<>(); private final List> exceptionHandlers = new ArrayList<>(); - private record HandleException(Predicate predicate, Function exceptionFunction) {} + private record HandleException(Predicate predicate, + Function exceptionFunction) {} - public E retryWhenException(Predicate predicate) { + public @Self ExecuteCall retryWhenException(Predicate predicate) { retryPredicates.add(predicate); - return getThis(); + return this; } - public E handleException(Predicate predicate, Function exceptionFunction) { + public @Self ExecuteCall handleException(Predicate predicate, Function exceptionFunction) { exceptionHandlers.add(new HandleException<>(predicate, exceptionFunction)); - return getThis(); + return this; } - public E handleException(Predicate predicate, Supplier supplier) { - return handleException(predicate, e -> supplier.get()); + public @Self ExecuteCall handleException(Predicate predicate, Supplier supplier) { + return handleException(predicate, _ -> supplier.get()); } - public E handleException(Function exceptionFunction) { - return handleException(e -> true, exceptionFunction); + public @Self ExecuteCall handleException(Function exceptionFunction) { + return handleException(_ -> true, exceptionFunction); } - public E handleException(Supplier supplier) { - return handleException(e -> true, e -> supplier.get()); + public @Self ExecuteCall handleException(Supplier supplier) { + return handleException(_ -> true, _ -> supplier.get()); } - public E retries(int retries) { + public @Self ExecuteCall retries(int retries) { if (retries <= 0) { throw new IllegalStateException("Retries should be greater than 0"); } this.retries = retries; - return getThis(); + return this; } - public E message(String message) { + public @Self ExecuteCall message(String message) { this.message = message; - return getThis(); + return this; } @SuppressWarnings("unchecked") @@ -89,26 +92,20 @@ public T execute() throws X { if (retries-- == 0) { throw new RuntimeException("Max retries reached when calling %s".formatted(message)); } - try { - Thread.sleep(5000); - } catch (InterruptedException e1) { - // continue - } + sleep(5 Second); return execute(); } else { try { - return exceptionHandlers.stream().filter(handleException -> handleException.predicate().test(exception)).findAny() - .map(handleException -> handleException.exceptionFunction().apply(exception)).orElseThrow(() -> e); + return exceptionHandlers.stream() + .filter(handleException -> handleException.predicate().test(exception)) + .findAny() + .map(handleException -> handleException.exceptionFunction().apply(exception)) + .orElseThrow(() -> e); } catch (Exception e1) { throw (X) e1; } } } } - - @SuppressWarnings("unchecked") - private E getThis() { - return (E) this; - } } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/Adapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/Adapter.java index 9fb96b45..72076759 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/Adapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/Adapter.java @@ -1,28 +1,28 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.adapters; -import static org.lodder.subtools.sublibrary.util.OptionalExtension.*; +import static manifold.science.util.UnitConstants.*; +import static org.lodder.subtools.multisubdownloader.Messages.*; import java.io.IOException; -import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.OptionalInt; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.function.Supplier; +import lombok.experimental.ExtensionMethod; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.StringUtils; -import org.lodder.subtools.multisubdownloader.Messages; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.OpenSubtitlesHasher; import org.lodder.subtools.sublibrary.Language; -import org.lodder.subtools.sublibrary.Manager.ValueBuilderIsPresentIntf; +import org.lodder.subtools.sublibrary.Manager.CacheKey; +import org.lodder.subtools.sublibrary.Manager.Value; import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.data.ProviderSerieId; import org.lodder.subtools.sublibrary.data.UserInteractionSettingsIntf; @@ -30,24 +30,21 @@ import org.lodder.subtools.sublibrary.model.Subtitle; import org.lodder.subtools.sublibrary.model.TvRelease; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import org.lodder.subtools.sublibrary.util.OptionalExtension; +import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.experimental.ExtensionMethod; - /** - * * @param type of the subtitle objects returned by the api * @param type of the ProviderSerieId * @param type of the exception thrown by the api */ -@ExtensionMethod({ Files.class }) +@ExtensionMethod({Files.class}) public interface Adapter extends SubtitleProvider { Logger LOGGER = LoggerFactory.getLogger(Adapter.class); default UserInteractionSettingsIntf getUserInteractionSettings() { - return getUserInteractionHandler().getSettings(); + return getUserInteractionHandler().settings; } UserInteractionHandler getUserInteractionHandler(); @@ -55,33 +52,33 @@ default UserInteractionSettingsIntf getUserInteractionSettings() { @Override default Set searchSubtitles(MovieRelease movieRelease, Language language) { Set subtitles = new HashSet<>(); - if (StringUtils.isNotBlank(movieRelease.getFileName())) { - Path file = movieRelease.getPath().resolve(movieRelease.getFileName()); + if (StringUtils.isNotBlank(movieRelease.fileName)) { + Path file = movieRelease.getPath().resolve(movieRelease.fileName); if (file.exists()) { try { subtitles.addAll(searchMovieSubtitlesWithHash(OpenSubtitlesHasher.computeHash(file), language)); } catch (IOException e) { LOGGER.error("Error calculating file hash", e); } catch (Exception e) { - LOGGER.error("API %s searchSubtitles using file hash for movie [%s] (%s)".formatted(getSubtitleSource().getName(), - movieRelease.getName(), e.getMessage()), e); + LOGGER.error("API %s searchSubtitles using file hash for movie [%s] (%s)".formatted( + subtitleSource.name, movieRelease.name, e.getMessage()), e); } } } - movieRelease.getImdbId().ifPresent(imdbId -> { + if (movieRelease.imdbId != null) { try { - subtitles.addAll(searchMovieSubtitlesWithId(imdbId, language)); + subtitles.addAll(searchMovieSubtitlesWithId(movieRelease.imdbId, language)); } catch (Exception e) { - LOGGER.error("API %s searchSubtitles using imdbid [%s] for movie [%s] (%s)".formatted(getSubtitleSource().getName(), - imdbId, movieRelease.getName(), e.getMessage()), e); + LOGGER.error("API %s searchSubtitles using imdbid [%s] for movie [%s] (%s)".formatted( + subtitleSource.name, movieRelease.imdbId, movieRelease.name, e.getMessage()), e); } - }); + } if (subtitles.isEmpty()) { try { - subtitles.addAll(searchMovieSubtitlesWithName(movieRelease.getName(), movieRelease.getYear(), language)); + subtitles.addAll(searchMovieSubtitlesWithName(movieRelease.name, movieRelease.year, language)); } catch (Exception e) { - LOGGER.error("API %s searchSubtitles using title for movie [%s] (%s)".formatted(getSubtitleSource().getName(), - movieRelease.getName(), e.getMessage()), e); + LOGGER.error("API %s searchSubtitles using title for movie [%s] (%s)".formatted(subtitleSource.name, + movieRelease.name, e.getMessage()), e); } } return convertToSubtitles(movieRelease, subtitles, language); @@ -93,16 +90,16 @@ default Set searchSubtitles(MovieRelease movieRelease, Language langua Collection searchMovieSubtitlesWithId(int tvdbId, Language language) throws X; - Collection searchMovieSubtitlesWithName(String name, int year, Language language) throws X; + Collection searchMovieSubtitlesWithName(String name, @Nullable Integer year, Language language) throws X; @Override default Set searchSubtitles(TvRelease tvRelease, Language language) { try { return convertToSubtitles(tvRelease, searchSerieSubtitles(tvRelease, language), language); } catch (Exception e) { - String displayName = StringUtils.defaultIfBlank(tvRelease.getOriginalName(), tvRelease.getName()); - LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(getSubtitleSource().getName(), - TvRelease.formatName(displayName, tvRelease.getSeason(), tvRelease.getFirstEpisodeNumber()), e.getMessage()), e); + String displayName = StringUtils.defaultIfBlank(tvRelease.originalName, tvRelease.name); + LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(subtitleSource.name, + TvRelease.formatName(displayName, tvRelease.season, tvRelease.firstEpisode), e.getMessage()), e); return Set.of(); } } @@ -111,11 +108,11 @@ default Set searchSubtitles(TvRelease tvRelease, Language language) { Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, Language language); - List getSortedProviderSerieIds(OptionalInt tvdbIdOptional, String serieName, int season) throws X; + List getSortedProviderSerieIds(@Nullable Integer tvdbId, String serieName, int season) throws X; @Override default Optional getProviderSerieId(TvRelease tvRelease) throws X { - if (StringUtils.isNotBlank(tvRelease.getCustomName())) { + if (StringUtils.isNotBlank(tvRelease.customName)) { return getProviderSerieId(tvRelease, TvRelease::getOriginalName, TvRelease::getCustomName); } else { Optional providerSerieId = getProviderSerieId(tvRelease, TvRelease::getOriginalName); @@ -123,122 +120,111 @@ default Optional getProviderSerieId(TvRelease tvRelease) throws X } } - default Optional getProviderSerieId(TvRelease tvRelease, Function nameFunction) throws X { + default Optional getProviderSerieId(TvRelease tvRelease, Function nameFunction) + throws X { return getProviderSerieId(tvRelease, nameFunction, nameFunction); } default Optional getProviderSerieId(TvRelease tvRelease, Function nameFunction, - Function customNameFunction) throws X { - return getProviderSerieId(nameFunction.apply(tvRelease), customNameFunction.apply(tvRelease), tvRelease.getDisplayName(), - tvRelease.getSeason(), tvRelease.getTvdbId()); + Function customNameFunction) throws X { + return getProviderSerieId(nameFunction.apply(tvRelease), customNameFunction.apply(tvRelease), + tvRelease.displayName, tvRelease.season, tvRelease.tvdbId); } - default Optional getProviderSerieId(String serieName, String displayName, int season, - OptionalInt tvdbIdOptional) throws X { - return getProviderSerieId(serieName, serieName, displayName, season, tvdbIdOptional); - } + default Optional getProviderSerieId(String serieName, String serieNameToSearchFor, String displayName, + int season, Integer tvdbId) throws X { - default Optional getProviderSerieId(String serieName, String serieNameToSearchFor, String displayName, int season, - OptionalInt tvdbIdOptional) throws X { - Supplier> tvdbIdValueBuilder = - () -> mapToObj(tvdbIdOptional, tvdbId -> getManager().valueBuilder().cacheType(CacheType.DISK) - .key("%s-serieName-tvdbId:%s-%s".formatted(getProviderName(), tvdbId, - useSeasonForSerieId() ? season : -1))).orElseThrow(); - if (tvdbIdOptional.isPresent() && tvdbIdValueBuilder.get().isPresent()) { - // if value using the tvdbId is present, return it - return tvdbIdValueBuilder.get().returnType(SerieMapping.class).getOptional(); + LazySupplier tvdbIdCacheFunction = new LazySupplier<>(() -> manager.getCache(CacheType.DISK, + "%s-serieName-tvdbId:%s-%s".formatted(providerName, tvdbId, useSeasonForSerieId ? season : -1))); + if (tvdbId != null) { + CacheKey tvdbIdCache = tvdbIdCacheFunction.get(); + if (tvdbIdCache.isPresent()) { + // if value using the tvdbId is present, return it + return tvdbIdCache.getOptional(); + } } if (StringUtils.isBlank(serieNameToSearchFor)) { return Optional.empty(); } - int seasonToUse = useSeasonForSerieId() ? season : 0; - ValueBuilderIsPresentIntf serieNameValueBuilder = getManager().valueBuilder() - .cacheType(CacheType.DISK) - .key("%s-serieName-name:%s-%s".formatted(getProviderName(), serieName.toLowerCase(), seasonToUse)); - - if (StringUtils.equals(serieNameToSearchFor, serieName) && serieNameValueBuilder.isPresent()) { - boolean returnValue; - Optional value; - if (serieNameValueBuilder.isTemporaryObject()) { - returnValue = !serieNameValueBuilder.isExpiredTemporary(); - value = Optional.empty(); + + int seasonToUse = useSeasonForSerieId ? season : 0; + CacheKey serieNameCache = manager.getCache(CacheType.DISK, + "%s-serieName-name:%s-%s".formatted(providerName, serieName.toLowerCase(), seasonToUse)); + if (StringUtils.equals(serieNameToSearchFor, serieName) && serieNameCache.isPresent()) { + if (serieNameCache.isTemporaryObject()) { + if (!serieNameCache.isExpiredTemporary()) { + return Optional.empty(); + } } else { - value = serieNameValueBuilder.returnType(SerieMapping.class).getOptional(); - returnValue = true; - } - if (returnValue) { - // if value using the name is present, return it - // if tvdbId is known, also persist the value using the tvdbId - return ifPresentDo(value, - providerSerieName -> tvdbIdOptional.ifPresent(tvdbId -> tvdbIdValueBuilder.get().value(providerSerieName).store())); + Optional serieMapping = serieNameCache.getOptional(); + if (tvdbId != null) { + serieMapping.map(Value::of).ifPresent(tvdbIdCacheFunction.get()::store); + } + return serieMapping; } } - List providerSerieIds = getSortedProviderSerieIds(tvdbIdOptional, serieNameToSearchFor, seasonToUse); + List providerSerieIds = getSortedProviderSerieIds(tvdbId, serieNameToSearchFor, seasonToUse); if (providerSerieIds.isEmpty()) { - // if no provider serie id's could be found, store a temporary null value with expiration time of 1 day - // (so the provider isn't contacted every time this method is being called) - // If a temporary expired value was already found, persist the null value with a doubled expiration time - serieNameValueBuilder - .value(new SerieMapping(serieName, null, null, seasonToUse)) - .storeTempNullValue() - .timeToLive(OptionalExtension - .map(serieNameValueBuilder.getTemporaryTimeToLive(), v -> v * 2) - .orElseGet(() -> TimeUnit.SECONDS.convert(1, TimeUnit.DAYS))) - .storeAsTempValue(); + // If no provider serie id's could be found, store a temporary null value with expiration time of 1 day + // (so the provider isn't contacted every time this method is being called). + // If a temporary expired value was already found, persist the null value with a doubled expiration time. + serieNameCache.store( + value:Value.of(new SerieMapping(serieName, null, null, seasonToUse)), + timeToLive:serieNameCache.getTemporaryTimeToLive().map(v -> v * 2).orElse(1 day), + storeAsTempValue:true, + storeTempNullValue:true); return Optional.empty(); } SerieMapping serieMapping; - if (!getUserInteractionSettings().isOptionsConfirmProviderMapping() && providerSerieIds.size() == 1) { - serieMapping = new SerieMapping(serieName, providerSerieIds.get(0).getId(), providerSerieIds.get(0).getName(), seasonToUse); + if (!getUserInteractionSettings().optionsConfirmProviderMapping && providerSerieIds.size() == 1) { + serieMapping = + new SerieMapping(serieName, providerSerieIds.first.id, providerSerieIds.first.name, seasonToUse); } else { - ValueBuilderIsPresentIntf previousResultsValueBuilder = getManager().valueBuilder() - .cacheType(CacheType.MEMORY) - .key("%s-serieName-prev-results:%s-%s".formatted(getProviderName(), displayName.toLowerCase(), seasonToUse)); + CacheKey previousResultsCache = manager.getCache(CacheType.MEMORY, + "%s-serieName-prev-results:%s-%s".formatted(providerName, displayName.toLowerCase(), seasonToUse)); - boolean previousResultsPresent = previousResultsValueBuilder.isPresent(); + boolean previousResultsPresent = previousResultsCache.isPresent(); Optional uriForSerie; // Check if the previous results were the same for the service. If so, don't ask the user to select again - if (previousResultsPresent - && previousResultsValueBuilder.returnType((Class>) null, null).getCollection().equals(providerSerieIds)) { + if (previousResultsPresent && providerSerieIds.equals(previousResultsCache.getCollection(null))) { uriForSerie = Optional.empty(); } else { // let the user select the correct provider serie id - uriForSerie = getUserInteractionHandler().selectFromList(providerSerieIds, - useSeasonForSerieId() - ? Messages.getString("SelectDialog.SelectSerieNameForNameWithSeason").formatted(displayName, seasonToUse) - : Messages.getString("SelectDialog.SelectSerieNameForName").formatted(displayName), - getProviderName(), - this::providerSerieIdToDisplayString); + uriForSerie = getUserInteractionHandler().selectFromList( + providerSerieIds, + useSeasonForSerieId ? + getText("SelectDialog.SelectSerieNameForNameWithSeason", displayName, seasonToUse) : + getText("SelectDialog.SelectSerieNameForName", displayName), + providerName, + this::providerSerieIdToDisplayString); } if (uriForSerie.isEmpty()) { if (serieNameToSearchFor.equals(serieName)) { // if no provider serie id was selected, store a temporary null value with expiration time of 1 day, // or the doubled previously temporary value (if present) - serieNameValueBuilder - .value(new SerieMapping(serieNameToSearchFor, null, null, seasonToUse)) - .storeTempNullValue() - .timeToLive(OptionalExtension - .map(serieNameValueBuilder.getTemporaryTimeToLive(), v -> v * 2) - .orElseGet(() -> TimeUnit.SECONDS.convert(1, TimeUnit.DAYS))) - .storeAsTempValue(); - previousResultsValueBuilder.collectionValue(providerSerieIds).store(); + serieNameCache.store( + value:Value.of(new SerieMapping(serieNameToSearchFor, null, null, seasonToUse)), + timeToLive:serieNameCache.getTemporaryTimeToLive().map(v -> v * 2).orElse(1 day), + storeAsTempValue:true, + storeTempNullValue:true); + previousResultsCache.store(Value.ofCollection(providerSerieIds)); } return Optional.empty(); } // create a serieMapping for the selected value - serieMapping = new SerieMapping(serieName, uriForSerie.get().getId(), uriForSerie.get().getName(), seasonToUse); + serieMapping = new SerieMapping(serieName, uriForSerie.get().id, uriForSerie.get().name, seasonToUse); } - if (tvdbIdOptional.isPresent()) { - tvdbIdValueBuilder.get().value(serieMapping).store(); + if (tvdbId != null) { + tvdbIdCacheFunction.get().store(Value.of(serieMapping)); } else { - serieNameValueBuilder.value(serieMapping).store(); + serieNameCache.store(Value.of(serieMapping)); } return Optional.of(serieMapping); } - boolean useSeasonForSerieId(); + @val boolean useSeasonForSerieId; String providerSerieIdToDisplayString(S providerSerieId); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edAdapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edAdapter.java index 550347b7..fa0b96cd 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edAdapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edAdapter.java @@ -3,15 +3,20 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; -import java.util.OptionalInt; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import extensions.java.lang.String.StringExt; +import lombok.Getter; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.JAddic7edApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.exception.Addic7edException; import org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.model.Addic7edSubtitleDescriptor; +import org.lodder.subtools.sublibrary.Credentials; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.control.ReleaseParser; @@ -22,31 +27,30 @@ import org.lodder.subtools.sublibrary.model.SubtitleMatchType; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.model.TvRelease; -import org.lodder.subtools.sublibrary.util.OptionalExtension; -import org.lodder.subtools.sublibrary.util.StringUtil; import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - @Getter -@ExtensionMethod({ OptionalExtension.class }) -public class JAddic7edAdapter extends AbstractAdapter { +public final class JAddic7edAdapter extends AbstractAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(JAddic7edAdapter.class); + private static LazySupplier jaapi; + @val @override SubtitleSource subtitleSource = SubtitleSource.ADDIC7ED; + @val @override String providerName = subtitleSource.name(); + @val @override boolean useSeasonForSerieId = true; - public JAddic7edAdapter(boolean isLoginEnabled, String username, String password, boolean speedy, Manager manager, - UserInteractionHandler userInteractionHandler) { + public JAddic7edAdapter(Manager manager, boolean speedy, Credentials credentials=null, + UserInteractionHandler userInteractionHandler) { super(manager, userInteractionHandler); if (jaapi == null) { jaapi = new LazySupplier<>(() -> { try { - return isLoginEnabled ? new JAddic7edApi(username, password, speedy, manager) : new JAddic7edApi(speedy, manager); + return new JAddic7edApi(manager, speedy, credentials); } catch (Exception e) { - throw new SubtitlesProviderInitException(getProviderName(), e); + throw new SubtitlesProviderInitException(providerName, e); } }); } @@ -56,15 +60,6 @@ private JAddic7edApi getApi() { return jaapi.get(); } - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.ADDIC7ED; - } - - @Override - public String getProviderName() { - return getSubtitleSource().name(); - } @Override public List searchMovieSubtitlesWithHash(String hash, Language language) { @@ -79,66 +74,65 @@ public List searchMovieSubtitlesWithId(int tvdbId, L } @Override - public List searchMovieSubtitlesWithName(String name, int year, Language language) { + public Collection searchMovieSubtitlesWithName(String name, @Nullable Integer year, + Language language) { // TODO implement this return List.of(); } @Override - public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, Language language) { + public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, + Language language) { // TODO implement this return Set.of(); } @Override - public Set searchSerieSubtitles(TvRelease tvRelease, Language language) throws Addic7edException { - return getProviderSerieId(tvRelease) - .map(providerSerieId -> tvRelease.getEpisodeNumbers().stream() - .flatMap(episode -> { - try { - return getApi().getSubtitles(providerSerieId, tvRelease.getSeason(), episode, language).stream(); - } catch (Addic7edException e) { - LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(getSubtitleSource().getName(), - TvRelease.formatName(providerSerieId.getProviderName(), tvRelease.getSeason(), episode), - e.getMessage()), e); - return Stream.empty(); - } - }) - .collect(Collectors.toSet())) - .orElseGet(Set::of); + public Set searchSerieSubtitles(TvRelease tvRelease, Language language) + throws Addic7edException { + return getProviderSerieId(tvRelease).map( + providerSerieId -> tvRelease.episodes.stream().flatMap(episode -> { + try { + return getApi().getSubtitles(providerSerieId, tvRelease.season, episode, language).stream(); + } catch (Addic7edException e) { + LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(subtitleSource.name, + TvRelease.formatName(providerSerieId.providerName, tvRelease.season, episode), + e.getMessage()), e); + return Stream.empty(); + } + }).collect(Collectors.toSet())).orElseGet(Set::of); } @Override - public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, Language language) { + public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, + Language language) { return subtitles.stream() - .filter(sub -> language == sub.getLanguage()) - .map(sub -> Subtitle.downloadSource(sub.getUrl()) - .subtitleSource(getSubtitleSource()) - .fileName(StringUtil.removeIllegalFilenameChars(sub.getTitle() + " " + sub.getVersion())) - .language(sub.getLanguage()) - .quality(ReleaseParser.getQualityKeyword(sub.getTitle() + " " + sub.getVersion())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(ReleaseParser.extractReleasegroup(sub.getTitle() + " " + sub.getVersion(), - (sub.getTitle() + " " + sub.getVersion()).endsWith(".srt"))) - .uploader(sub.getUploader()) - .hearingImpaired(false)) - .collect(Collectors.toSet()); - } - - @Override - public List getSortedProviderSerieIds(OptionalInt tvdbIdOptional, String serieName, int season) throws Addic7edException { - return getApi().getProviderId(serieName).stream() - .sorted(Comparator.comparing(n -> !serieName.replaceAll("[^A-Za-z]", "").equalsIgnoreCase(n.getName().replaceAll("[^A-Za-z]", "")))) - .toList(); + .filter(sub -> language == sub.language) + .map(sub -> new Subtitle( + downloadSource:Subtitle.DownloadSource.of(sub.url), + subtitleSource:subtitleSource, + fileName:StringExt.removeIllegalFilenameChars(sub.title + " " + sub.version), + language:sub.language, + quality:ReleaseParser.getQualityKeyword(sub.title + " " + sub.version), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:ReleaseParser.extractReleaseGroup(sub.title, sub.title.endsWith(".srt")), + uploader:sub.uploader, + hearingImpaired:false)) + .collect(Collectors.toSet()); } @Override - public boolean useSeasonForSerieId() { - return true; + public List getSortedProviderSerieIds(@Nullable Integer tvdbId, String serieName, int season) + throws Addic7edException { + return getApi().getProviderId(serieName) + .stream() + .sorted(Comparator.comparing(n -> !serieName.replaceAll("[^A-Za-z]", "") + .equalsIgnoreCase(n.name.replaceAll("[^A-Za-z]", "")))) + .toList(); } @Override public String providerSerieIdToDisplayString(ProviderSerieId providerSerieId) { - return providerSerieId.getName(); + return providerSerieId.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edViaProxyAdapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edViaProxyAdapter.java index ca51c350..b017a44a 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edViaProxyAdapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JAddic7edViaProxyAdapter.java @@ -4,14 +4,19 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.OptionalInt; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.pivovarit.function.ThrowingSupplier; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.gestdown.invoker.ApiException; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.proxy.gestdown.JAddic7edProxyGestdownApi; import org.lodder.subtools.sublibrary.Language; @@ -21,38 +26,24 @@ import org.lodder.subtools.sublibrary.model.Subtitle; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.model.TvRelease; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.pivovarit.function.ThrowingSupplier; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.ExtensionMethod; - @Getter -@ExtensionMethod({ OptionalExtension.class }) -public class JAddic7edViaProxyAdapter extends AbstractAdapter { +public final class JAddic7edViaProxyAdapter extends AbstractAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(JAddic7edViaProxyAdapter.class); + private final JAddic7edProxyGestdownApi jaapi; + @val @override SubtitleSource subtitleSource = SubtitleSource.ADDIC7ED; + @val @override String providerName = subtitleSource.name() + "-GESTDOWN"; + @val @override boolean useSeasonForSerieId = false; public JAddic7edViaProxyAdapter(Manager manager, UserInteractionHandler userInteractionHandler) { super(manager, userInteractionHandler); this.jaapi = new JAddic7edProxyGestdownApi(manager); } - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.ADDIC7ED; - } - - @Override - public String getProviderName() { - return getSubtitleSource().name() + "-GESTDOWN"; - } - private JAddic7edProxyGestdownApi getApi() { return jaapi; } @@ -70,7 +61,7 @@ public Collection searchMovieSubtitlesWithId(int tvdbId, Language lang } @Override - public Collection searchMovieSubtitlesWithName(String name, int year, Language language) { + public Collection searchMovieSubtitlesWithName(String name, @Nullable Integer year, Language language) { // TODO implement this return List.of(); } @@ -82,52 +73,53 @@ public Set convertToSubtitles(MovieRelease movieRelease, Set @Override public Set searchSerieSubtitles(TvRelease tvRelease, Language language) throws ApiException { - return getProviderSerieId(tvRelease) - .map(providerSerieId -> tvRelease.getEpisodeNumbers().stream() - .flatMap(episode -> { - try { - return new ExecuteCall<>(() -> getApi().getSubtitles(providerSerieId, tvRelease.getSeason(), episode, language)) - .message("getSubtitles: [%s]".formatted( - TvRelease.formatName(providerSerieId.getProviderName(), tvRelease.getSeason(), episode))) - .retryWhenHttpCode(ReturnCode.REFRESHING) - .retryWhenHttpCode(ReturnCode.RATE_LIMIT_REACHED) - .execute().stream(); - } catch (ApiException e) { - LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(getSubtitleSource().getName(), - TvRelease.formatName(providerSerieId.getProviderName(), tvRelease.getSeason(), episode), - e.getMessage()), e); - return Stream.empty(); - } - }) - .collect(Collectors.toSet())) - .orElseGet(Set::of); + return getProviderSerieId(tvRelease).map( + providerSerieId -> tvRelease.episodes.stream().flatMap(episode -> { + try { + return new ExecuteCall<>( + () -> getApi().getSubtitles(providerSerieId, tvRelease.season, episode, language)) + .message("getSubtitles: [%s]".formatted( + TvRelease.formatName(providerSerieId.providerName, tvRelease.season, episode))) + .retryWhenHttpCode(ReturnCode.REFRESHING) + .retryWhenHttpCode(ReturnCode.RATE_LIMIT_REACHED) + .execute() + .stream(); + } catch (ApiException e) { + LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(subtitleSource.name, + TvRelease.formatName(providerSerieId.providerName, tvRelease.season, episode), + e.getMessage()), e); + return Stream.empty(); + } + }).collect(Collectors.toSet())).orElseGet(Set::of); } @Override - public List getSortedProviderSerieIds(OptionalInt tvdbIdOptional, String serieName, int season) throws ApiException { - List serieIds = tvdbIdOptional.mapToObj(tvdbId -> new ExecuteCall<>(() -> getApi().getProviderSerieName(tvdbId)) - .message("getProviderSerieName: [%s]".formatted(tvdbId)) + public List getSortedProviderSerieIds(@Nullable Integer tvdbId, String serieName, int season) + throws ApiException { + List serieIds = tvdbId == null ? List.of() : + new ExecuteCall<>(() -> getApi().getProviderSerieName(tvdbId)) + .message("getProviderSerieName: [$tvdbId]") .retryWhenHttpCode(ReturnCode.RATE_LIMIT_REACHED) .handleHttpCode(ReturnCode.NOT_FOUND, () -> { - LOGGER.info("API %s - Could not find tvdbId [%s]".formatted(getProviderName(), tvdbId)); + LOGGER.info("API %s - Could not find tvdbId [%s]".formatted(providerName, tvdbId)); return List.of(); }) - .execute()).orElseGet(List::of); + .execute(); if (serieIds.isEmpty()) { - serieIds = new ExecuteCall<>(() -> getApi().getProviderSerieName(serieName)) - .message("getProviderSerieName: [%s]".formatted(serieName)) - .retryWhenHttpCode(ReturnCode.RATE_LIMIT_REACHED) - .handleHttpCode(ReturnCode.NOT_FOUND, () -> { - LOGGER.info("API %s - Could not find serie name [%s]".formatted(getProviderName(), serieName)); - return List.of(); - }) - .execute(); + serieIds = new ExecuteCall<>(() -> getApi().getProviderSerieName(serieName)).message( + "getProviderSerieName: [$serieName]") + .retryWhenHttpCode(ReturnCode.RATE_LIMIT_REACHED) + .handleHttpCode(ReturnCode.NOT_FOUND, () -> { + LOGGER.info("API $providerName - Could not find serie name [$serieName]"); + return List.of(); + }) + .execute(); } return serieIds.stream() - .sorted(Comparator - .comparing(n -> !serieName.replaceAll("[^A-Za-z]", "").equalsIgnoreCase(n.getName().replaceAll("[^A-Za-z]", "")))) - .toList(); + .sorted(Comparator.comparing(n -> !serieName.replaceAll("[^A-Za-z]", "") + .equalsIgnoreCase(n.name.replaceAll("[^A-Za-z]", "")))) + .toList(); } @Override @@ -135,22 +127,15 @@ public Set convertToSubtitles(TvRelease tvRelease, Collection(subtitles); } - @Override - public boolean useSeasonForSerieId() { - return false; - } - @Override public String providerSerieIdToDisplayString(ProviderSerieId providerSerieId) { - return providerSerieId.getName(); + return providerSerieId.name; } @Getter @RequiredArgsConstructor private enum ReturnCode { - NOT_FOUND(404), - RATE_LIMIT_REACHED(429), - REFRESHING(423); + NOT_FOUND(404), RATE_LIMIT_REACHED(429), REFRESHING(423); final int code; @@ -159,27 +144,31 @@ public boolean isSameCode(int code) { } } - public static class ExecuteCall extends AbstractAdapter.ExecuteCall> { + private static class ExecuteCall extends AbstractAdapter.ExecuteCall { public ExecuteCall(ThrowingSupplier supplier) { super(supplier); } public ExecuteCall retryWhenHttpCode(ReturnCode returnCode) { - return super.retryWhenException(e -> returnCode.isSameCode(e.getCode())); + super.retryWhenException(e -> returnCode.isSameCode(e.getCode())); + return this; } public ExecuteCall handleHttpCode(ReturnCode returnCode, Function function) { - return super.handleException(e -> returnCode.isSameCode(e.getCode()), function); + super.handleException(e -> returnCode.isSameCode(e.getCode()), function); + return this; } public ExecuteCall handleHttpCode(ReturnCode returnCode, Supplier supplier) { - return super.handleException(e -> returnCode.isSameCode(e.getCode()), supplier); + super.handleException(e -> returnCode.isSameCode(e.getCode()), supplier); + return this; } @Override public ExecuteCall handleException(Supplier suppliers) { - return super.handleException(e -> true, suppliers); + super.handleException(_ -> true, suppliers); + return this; } } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JOpenSubAdapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JOpenSubAdapter.java index 99cbf7b8..c493a951 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JOpenSubAdapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JOpenSubAdapter.java @@ -3,17 +3,22 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; -import java.util.OptionalInt; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import lombok.Getter; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.OpenSubtitlesApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.exception.OpenSubtitlesException; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.model.OpensubtitleSerieId; +import org.lodder.subtools.sublibrary.Credentials; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.control.ReleaseParser; @@ -23,37 +28,31 @@ import org.lodder.subtools.sublibrary.model.SubtitleMatchType; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.model.TvRelease; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; -import org.opensubtitles.model.Latest200ResponseDataInnerAttributesFilesInner; import org.opensubtitles.model.SubtitleAttributes; +import org.opensubtitles.model.SubtitleAttributesFilesInner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - @Getter -@ExtensionMethod({ OptionalExtension.class }) -public class JOpenSubAdapter - extends AbstractAdapter { +public final class JOpenSubAdapter + extends AbstractAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(JOpenSubAdapter.class); + private static LazySupplier osApi; + @val @override SubtitleSource subtitleSource = SubtitleSource.OPENSUBTITLES; + @val @override String providerName = subtitleSource.name(); + @val @override boolean useSeasonForSerieId = false; - public JOpenSubAdapter(boolean isLoginEnabled, String username, String password, Manager manager, - UserInteractionHandler userInteractionHandler) { + public JOpenSubAdapter(Manager manager, Credentials credentials, UserInteractionHandler userInteractionHandler) { super(manager, userInteractionHandler); if (osApi == null) { osApi = new LazySupplier<>(() -> { try { - if (isLoginEnabled) { - return new OpenSubtitlesApi(manager, username, password); - } else { - return new OpenSubtitlesApi(manager); - } + return new OpenSubtitlesApi(manager, credentials); } catch (OpenSubtitlesException e) { - throw new SubtitlesProviderInitException(getProviderName(), e); + throw new SubtitlesProviderInitException(providerName, e); } }); } @@ -64,119 +63,103 @@ private OpenSubtitlesApi getApi() { } @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.OPENSUBTITLES; - } - - @Override - public String getProviderName() { - return getSubtitleSource().name(); - } - - @Override - public List searchMovieSubtitlesWithHash(String hash, Language language) throws OpenSubtitlesException { - return getApi().searchSubtitles() - .movieHash(hash) - .language(language) - .searchSubtitles() - .getData(); + public List searchMovieSubtitlesWithHash(String hash, Language language) + throws OpenSubtitlesException { + return getApi().searchSubtitles().movieHash(hash).language(language).searchSubtitles().getData(); } @Override - public List searchMovieSubtitlesWithId(int tvdbId, Language language) throws OpenSubtitlesException { - return getApi().searchSubtitles() - .imdbId(tvdbId) - .language(language) - .searchSubtitles() - .getData(); + public List searchMovieSubtitlesWithId(int tvdbId, Language language) + throws OpenSubtitlesException { + return getApi().searchSubtitles().imdbId(tvdbId).language(language).searchSubtitles().getData(); } @Override - public List searchMovieSubtitlesWithName(String name, int year, Language language) - throws OpenSubtitlesException { - return getApi().searchSubtitles() - .query(name) - .language(language) - .searchSubtitles() - .getData(); + public Collection searchMovieSubtitlesWithName(String name, + @Nullable Integer year, + Language language) throws OpenSubtitlesException { + return getApi().searchSubtitles().query(name).language(language).searchSubtitles().getData(); } @Override - public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, Language language) { - return subtitles.stream().map(org.opensubtitles.model.Subtitle::getAttributes) - .filter(attributes -> movieRelease.getYear() == attributes.getFeatureDetails().getYear().intValue()) - .flatMap(attributes -> attributes.getFiles().stream().map(file -> createSubtitle(file, attributes))) - .collect(Collectors.toSet()); + public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, + Language language) { + return subtitles.stream() + .map(org.opensubtitles.model.Subtitle::getAttributes) + .filter(attributes -> + attributes.getFeatureDetails().getYear() != null + ? Objects.equals(attributes.getFeatureDetails().getYear().intValue(), movieRelease.year) + : movieRelease.year == null) + .flatMap(attributes -> attributes.getFiles().stream().map(file -> createSubtitle(file, attributes))) + .collect(Collectors.toSet()); } @Override - public Set searchSerieSubtitles(TvRelease tvRelease, Language language) throws OpenSubtitlesException { - return getProviderSerieId(tvRelease) - .map(providerSerieId -> tvRelease.getEpisodeNumbers().stream() - .flatMap(episode -> { - try { - return getApi().searchSubtitles() - .query(providerSerieId.getName()) - .season(tvRelease.getSeason()) - .episode(episode) - .language(language) - .searchSubtitles() - .getData().stream(); - } catch (OpenSubtitlesException e) { - LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(getSubtitleSource().getName(), - TvRelease.formatName(providerSerieId.getProviderName(), tvRelease.getSeason(), episode), - e.getMessage()), e); - return Stream.empty(); - } - }) - .collect(Collectors.toSet())) - .orElseGet(Set::of); + public Set searchSerieSubtitles(TvRelease tvRelease, Language language) + throws OpenSubtitlesException { + return getProviderSerieId(tvRelease).map( + providerSerieId -> tvRelease.episodes.stream().flatMap(episode -> { + try { + return getApi().searchSubtitles() + .query(providerSerieId.name) + .season(tvRelease.season) + .episode(episode) + .language(language) + .searchSubtitles() + .getData() + .stream(); + } catch (OpenSubtitlesException e) { + LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(subtitleSource.name, + TvRelease.formatName(providerSerieId.providerName, tvRelease.season, episode), + e.getMessage()), e); + return Stream.empty(); + } + }).collect(Collectors.toSet())).orElseGet(Set::of); } @Override - public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, Language language) { - String name = StringUtils.lowerCase(RegExUtils.replaceAll(tvRelease.getName(), "[^A-Za-z]", "")); - String originalName = StringUtils.lowerCase(RegExUtils.replaceAll(tvRelease.getOriginalName(), "[^A-Za-z]", "")); - return subtitles.stream().map(org.opensubtitles.model.Subtitle::getAttributes) - .flatMap(attributes -> attributes.getFiles().stream() - .filter(file -> file.getFileName() != null) - .filter(file -> { - String subFileName = file.getFileName().replaceAll("[^A-Za-z]", "").toLowerCase(); - return subFileName.contains(name) || (StringUtils.isNotBlank(originalName) && subFileName.contains(originalName)); - }) - .map(file -> createSubtitle(file, attributes))) - .collect(Collectors.toSet()); - } - - private Subtitle createSubtitle(Latest200ResponseDataInnerAttributesFilesInner file, SubtitleAttributes attributes) { - return Subtitle.downloadSource(() -> getApi().downloadSubtitle().fileId(file.getFileId().intValue()).download().getLink()) - .subtitleSource(getSubtitleSource()) - .fileName(file.getFileName()) - .language(Language.fromIdOptional(attributes.getLanguage()).orElse(null)) - .quality(ReleaseParser.getQualityKeyword(file.getFileName())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(ReleaseParser.extractReleasegroup(file.getFileName(), file.getFileName().endsWith(".srt"))) - .uploader(attributes.getUploader().getName()) - .hearingImpaired(attributes.isHearingImpaired()); + public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, + Language language) { + String name = StringUtils.lowerCase(RegExUtils.replaceAll(tvRelease.name, "[^A-Za-z]", "")); + String originalName = StringUtils.lowerCase(RegExUtils.replaceAll(tvRelease.originalName, "[^A-Za-z]", "")); + return subtitles.stream() + .map(org.opensubtitles.model.Subtitle::getAttributes) + .flatMap(attributes -> attributes.getFiles().stream().filter(file -> { + String subFileName = file.getFileName().replaceAll("[^A-Za-z]", "").toLowerCase(); + return subFileName.contains(name) || + (StringUtils.isNotBlank(originalName) && subFileName.contains(originalName)); + }).map(file -> createSubtitle(file, attributes))) + .collect(Collectors.toSet()); } - @Override - public List getSortedProviderSerieIds(OptionalInt tvdbIdOptional, String serieName, int season) - throws OpenSubtitlesException { - return getApi().getProviderSerieIds(serieName).stream() - .sorted(Comparator.comparing( - (OpensubtitleSerieId n) -> !serieName.replaceAll("[^A-Za-z]", "").equalsIgnoreCase(n.getName().replaceAll("[^A-Za-z]", ""))) - .thenComparing(OpensubtitleSerieId::getYear, Comparator.reverseOrder())) - .toList(); + private Subtitle createSubtitle(SubtitleAttributesFilesInner file, SubtitleAttributes attributes) { + return new Subtitle( + downloadSource:Subtitle.DownloadSource.of( + () -> getApi().downloadSubtitle().fileId(file.getFileId().intValue()).download().getLink()), + subtitleSource:subtitleSource, + fileName:file.getFileName(), + language:Language.fromIdOptional(attributes.getLanguage()).orElse(null), + quality:ReleaseParser.getQualityKeyword(file.getFileName()), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:ReleaseParser.extractReleaseGroup(file.fileName, file.fileName.endsWith(".srt")), + uploader:attributes.getUploader() != null ? attributes.getUploader().getName() : null, + hearingImpaired:Boolean.TRUE == attributes.isHearingImpaired()); } @Override - public boolean useSeasonForSerieId() { - return false; + public List getSortedProviderSerieIds(@Nullable Integer tvdbId, String serieName, int season) + throws OpenSubtitlesException { + return getApi().getProviderSerieIds(serieName) + .stream() + .sorted( + Comparator.comparing((OpensubtitleSerieId n) -> !serieName.replaceAll("[^A-Za-z]", "") + .equalsIgnoreCase(n.name.replaceAll("[^A-Za-z]", ""))) + .thenComparing(OpensubtitleSerieId::getYear, Comparator.nullsLast(Comparator.reverseOrder()))) + .toList(); } @Override public String providerSerieIdToDisplayString(OpensubtitleSerieId providerSerieId) { - return "%s (%s)".formatted(providerSerieId.getName(), providerSerieId.getYear()); + return "${providerSerieId.name} (${providerSerieId.year})"; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JPodnapisiAdapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JPodnapisiAdapter.java index 4869aaff..32e74746 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JPodnapisiAdapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JPodnapisiAdapter.java @@ -2,12 +2,14 @@ import java.util.Collection; import java.util.List; -import java.util.OptionalInt; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.podnapisi.JPodnapisiApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.podnapisi.exception.PodnapisiException; @@ -22,20 +24,19 @@ import org.lodder.subtools.sublibrary.model.SubtitleMatchType; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.model.TvRelease; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - -@Getter -@ExtensionMethod({ OptionalExtension.class }) -public class JPodnapisiAdapter extends AbstractAdapter { +public final class JPodnapisiAdapter + extends AbstractAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(JPodnapisiAdapter.class); + private static LazySupplier jpapi; + @val @override SubtitleSource subtitleSource = SubtitleSource.PODNAPISI; + @val @override String providerName = subtitleSource.name(); + @val @override boolean useSeasonForSerieId = false; public JPodnapisiAdapter(Manager manager, UserInteractionHandler userInteractionHandler) { super(manager, userInteractionHandler); @@ -44,7 +45,7 @@ public JPodnapisiAdapter(Manager manager, UserInteractionHandler userInteraction try { return new JPodnapisiApi(manager, "JBierSubDownloader"); } catch (Exception e) { - throw new SubtitlesProviderInitException(getProviderName(), e); + throw new SubtitlesProviderInitException(providerName, e); } }); } @@ -54,16 +55,6 @@ private JPodnapisiApi getApi() { return jpapi.get(); } - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.PODNAPISI; - } - - @Override - public String getProviderName() { - return getSubtitleSource().name(); - } - @Override public List searchMovieSubtitlesWithHash(String hash, Language language) { return List.of(); @@ -75,66 +66,64 @@ public List searchMovieSubtitlesWithId(int tvdbId, } @Override - public List searchMovieSubtitlesWithName(String name, int year, Language language) throws PodnapisiException { + public Collection searchMovieSubtitlesWithName(String name, @Nullable Integer year, + Language language) throws PodnapisiException { return getApi().getMovieSubtitles(name, year, 0, 0, language); } @Override - public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, Language language) { + public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, + Language language) { return buildListSubtitles(language, subtitles); } @Override - public Set searchSerieSubtitles(TvRelease tvRelease, Language language) throws PodnapisiException { - return getProviderSerieId(tvRelease) - .map(providerSerieId -> tvRelease.getEpisodeNumbers().stream() - .flatMap(episode -> { - try { - return getApi().getSerieSubtitles(providerSerieId, tvRelease.getSeason(), episode, language).stream(); - } catch (PodnapisiException e) { - LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(getSubtitleSource().getName(), - TvRelease.formatName(providerSerieId.getProviderName(), tvRelease.getSeason(), episode), - e.getMessage()), e); - return Stream.empty(); - } - }) - .collect(Collectors.toSet())) - .orElseGet(Set::of); + public Set searchSerieSubtitles(TvRelease tvRelease, Language language) + throws PodnapisiException { + return getProviderSerieId(tvRelease).map( + providerSerieId -> tvRelease.episodes.stream().flatMap(episode -> { + try { + return api.getSerieSubtitles(providerSerieId, tvRelease.season, episode, language).stream(); + } catch (PodnapisiException e) { + LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(subtitleSource.name, + TvRelease.formatName(providerSerieId.providerName, tvRelease.season, episode), + e.getMessage()), e); + return Stream.empty(); + } + }).collect(Collectors.toSet())).orElseGet(Set::of); } @Override - public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, Language language) { + public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, + Language language) { return buildListSubtitles(language, subtitles); } private Set buildListSubtitles(Language language, Collection lSubtitles) { return lSubtitles.stream() - .filter(ossd -> StringUtils.isNotBlank(ossd.getReleaseString())) - .map(ossd -> Subtitle.downloadSource(ossd.getUrl()) - .subtitleSource(getSubtitleSource()) - .fileName(ossd.getReleaseString()) - .language(language) - .quality(ReleaseParser.getQualityKeyword(ossd.getReleaseString())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(ReleaseParser.extractReleasegroup(ossd.getReleaseString(), - StringUtils.endsWith(ossd.getReleaseString(), ".srt"))) - .uploader(ossd.getUploaderName()) - .hearingImpaired(ossd.isHearingImpaired())) - .collect(Collectors.toSet()); + .filter(ossd -> StringUtils.isNotBlank(ossd.releaseString)) + .map(ossd -> new Subtitle( + downloadSource:Subtitle.DownloadSource.of(ossd.url), + subtitleSource:subtitleSource, + fileName:ossd.releaseString, + language:language, + quality:ReleaseParser.getQualityKeyword(ossd.releaseString), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:ReleaseParser.extractReleaseGroup(ossd.releaseString, + StringUtils.endsWith(ossd.releaseString, ".srt")), + uploader:ossd.uploaderName, + hearingImpaired:ossd.hearingImpaired)) + .collect(Collectors.toSet()); } @Override - public List getSortedProviderSerieIds(OptionalInt tvdbIdOptional, String serieName, int season) throws PodnapisiException { + public List getSortedProviderSerieIds(@Nullable Integer tvdbId, String serieName, int season) + throws PodnapisiException { return getApi().getPodnapisiShowName(serieName).stream().toList(); } - @Override - public boolean useSeasonForSerieId() { - return false; - } - @Override public String providerSerieIdToDisplayString(ProviderSerieId providerSerieId) { - return providerSerieId.getName(); + return providerSerieId.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JSubsceneAdapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JSubsceneAdapter.java index d7286a62..0e896877 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JSubsceneAdapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JSubsceneAdapter.java @@ -1,5 +1,7 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.adapters; +import static org.lodder.subtools.sublibrary.model.Subtitle.*; + import java.util.Collection; import java.util.Comparator; import java.util.List; @@ -13,6 +15,9 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.Messages; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.subscene.SubsceneApi; @@ -28,21 +33,19 @@ import org.lodder.subtools.sublibrary.model.SubtitleMatchType; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.model.TvRelease; -import org.lodder.subtools.sublibrary.util.OptionalExtension; -import org.lodder.subtools.sublibrary.util.StringUtil; import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - -@Getter -@ExtensionMethod({ OptionalExtension.class }) -public class JSubsceneAdapter extends AbstractAdapter { +public final class JSubsceneAdapter + extends AbstractAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(JSubsceneAdapter.class); + private static LazySupplier api; + @val @override SubtitleSource subtitleSource = SubtitleSource.SUBSCENE; + @val @override String providerName = subtitleSource.name(); + @val @override boolean useSeasonForSerieId = true; public JSubsceneAdapter(Manager manager, UserInteractionHandler userInteractionHandler) { super(manager, userInteractionHandler); @@ -51,7 +54,7 @@ public JSubsceneAdapter(Manager manager, UserInteractionHandler userInteractionH try { return new SubsceneApi(manager); } catch (Exception e) { - throw new SubtitlesProviderInitException(getProviderName(), e); + throw new SubtitlesProviderInitException(providerName, e); } }); } @@ -61,16 +64,6 @@ private SubsceneApi getApi() { return api.get(); } - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.SUBSCENE; - } - - @Override - public String getProviderName() { - return getSubtitleSource().name(); - } - @Override public List searchMovieSubtitlesWithHash(String hash, Language language) { // TODO implement this @@ -84,37 +77,38 @@ public List searchMovieSubtitlesWithId(int tvdbId, L } @Override - public List searchMovieSubtitlesWithName(String name, int year, Language language) { + public Collection searchMovieSubtitlesWithName(String name, @Nullable Integer year, + Language language) { // TODO implement this return List.of(); } @Override - public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, Language language) { + public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, + Language language) { // TODO implement this return Set.of(); } @Override - public Set searchSerieSubtitles(TvRelease tvRelease, Language language) throws SubsceneException { - return getProviderSerieId(tvRelease) - .map(providerSerieId -> tvRelease.getEpisodeNumbers().stream() - .flatMap(episode -> { - try { - return getApi().getSubtitles(providerSerieId, tvRelease.getSeason(), episode, language).stream(); - } catch (SubsceneException e) { - LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(getSubtitleSource().getName(), - TvRelease.formatName(providerSerieId.getProviderName(), tvRelease.getSeason(), episode), - e.getMessage()), e); - return Stream.empty(); - } - }) - .collect(Collectors.toSet())) - .orElseGet(Set::of); + public Set searchSerieSubtitles(TvRelease tvRelease, Language language) + throws SubsceneException { + return getProviderSerieId(tvRelease).map( + providerSerieId -> tvRelease.episodes.stream().flatMap(episode -> { + try { + return getApi().getSubtitles(providerSerieId, tvRelease.season, episode, language).stream(); + } catch (SubsceneException e) { + LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(subtitleSource.name, + TvRelease.formatName(providerSerieId.providerName, tvRelease.season, episode), + e.getMessage()), e); + return Stream.empty(); + } + }).collect(Collectors.toSet())).orElseGet(Set::of); } @Override - public List getSortedProviderSerieIds(OptionalInt tvdbIdOptional, String serieName, int season) throws SubsceneException { + public List getSortedProviderSerieIds(@Nullable Integer tvdbId, String serieName, int season) + throws SubsceneException { ToIntFunction providerTypeFunction = value -> switch (value) { case "TV-Serie" -> 1; case "Exact" -> 2; @@ -122,56 +116,56 @@ public List getSortedProviderSerieIds(OptionalInt tvdbIdOptiona default -> 4; }; Pattern yearPattern = Pattern.compile("(\\d\\d\\d\\d)"); - return getApi().getSubSceneSerieNames(serieName).entrySet().stream() - .sorted(Comparator.comparingInt(entry -> providerTypeFunction.applyAsInt(entry.getKey()))) - .map(Entry::getValue).flatMap(List::stream) - .sorted(Comparator - .comparing((SubSceneSerieId serieId) -> serieId.getSeason() == 0) - .thenComparing(serieId -> { - Matcher matcher = yearPattern.matcher(serieId.getName()); - return matcher.find() ? Integer.parseInt(matcher.group()) : 0; - }, Comparator.reverseOrder()) - .thenComparing(SubSceneSerieId::getSeason, Comparator.reverseOrder())) - .distinct() - .toList(); + return getApi().getSubSceneSerieNames(serieName) + .entrySet() + .stream() + .sorted(Comparator.comparingInt(entry -> providerTypeFunction.applyAsInt(entry.getKey()))) + .map(Entry::getValue) + .flatMap(List::stream) + .sorted(Comparator.comparing((SubSceneSerieId serieId) -> serieId.season == 0) + .thenComparing(serieId -> { + Matcher matcher = yearPattern.matcher(serieId.name); + return matcher.find() ? Integer.parseInt(matcher.group()) : 0; + }, Comparator.reverseOrder()) + .thenComparing(SubSceneSerieId::getSeason, Comparator.reverseOrder())) + .distinct() + .toList(); } @Override - public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, Language language) { + public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, + Language language) { return subtitles.stream() - .filter(sub -> language == sub.getLanguage()) - .filter(sub -> sub.getName().contains(getSeasonEpisodeString(tvRelease.getSeason(), tvRelease.getEpisodeNumbers().get(0)))) - .map(sub -> Subtitle.downloadSource(sub.getUrlSupplier()) - .subtitleSource(getSubtitleSource()) - .fileName(StringUtil.removeIllegalFilenameChars(sub.getName())) - .language(sub.getLanguage()) - .quality(ReleaseParser.getQualityKeyword(sub.getName())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(ReleaseParser.extractReleasegroup(sub.getName(), false)) - .uploader(sub.getUploader()) - .hearingImpaired(sub.isHearingImpaired())) - .collect(Collectors.toSet()); + .filter(sub -> language == sub.language) + .filter(sub -> sub.name + .contains(getSeasonEpisodeString(tvRelease.season, tvRelease.firstEpisode))) + .map(sub -> new Subtitle( + downloadSource:DownloadSource.of(sub.urlSupplier), + subtitleSource:subtitleSource, + fileName:sub.name.removeIllegalFilenameChars(), + language:sub.language, + quality:ReleaseParser.getQualityKeyword(sub.name), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:ReleaseParser.extractReleaseGroup(sub.name, false), + uploader:sub.uploader, + hearingImpaired:sub.hearingImpaired)) + .collect(Collectors.toSet()); } private String getSeasonEpisodeString(int season, int episode) { - return "S" + org.apache.commons.lang3.StringUtils.leftPad(String.valueOf(season), 2, "0") + "E" - + org.apache.commons.lang3.StringUtils.leftPad(String.valueOf(episode), 2, "0"); - } - - @Override - public boolean useSeasonForSerieId() { - return true; + return "S%02dE%02d".formatted(season, episode); } @Override public String providerSerieIdToDisplayString(SubSceneSerieId providerSerieId) { - if (providerSerieId.getId().endsWith("-season")) { + if (providerSerieId.id.endsWith("-season")) { OptionalInt season = IntStream.rangeClosed(1, 100) - .filter(i -> providerSerieId.getId().endsWith("-%s-season".formatted(SubsceneApi.getOrdinalName(i).toLowerCase()))).findAny(); + .filter(i -> providerSerieId.id.endsWith("-${SubsceneApi.getOrdinalName(i).toLowerCase()}-season")) + .findAny(); if (season.isPresent()) { - return "%s (%s %s)".formatted(providerSerieId.getName(), Messages.getString("App.Season"), season.getAsInt()); + return "%s %s %s".formatted(providerSerieId.name, Messages.getText("App.Season"), season.getAsInt()); } } - return providerSerieId.getName(); + return providerSerieId.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JTVsubtitlesAdapter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JTVsubtitlesAdapter.java index 97baa08f..bb962157 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JTVsubtitlesAdapter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/adapters/JTVsubtitlesAdapter.java @@ -3,18 +3,19 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; -import java.util.OptionalInt; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.JTVSubtitlesApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.exception.TvSubtitlesException; -import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.model.TVsubtitlesSubtitleDescriptor; +import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.model.TVSubtitlesSubtitleDescriptor; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.control.ReleaseParser; @@ -25,20 +26,19 @@ import org.lodder.subtools.sublibrary.model.SubtitleMatchType; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.model.TvRelease; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - -@Getter -@ExtensionMethod({ OptionalExtension.class }) -public class JTVsubtitlesAdapter extends AbstractAdapter { +public final class JTVsubtitlesAdapter + extends AbstractAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(JTVsubtitlesAdapter.class); + private static LazySupplier jtvapi; + @val @override SubtitleSource subtitleSource = SubtitleSource.TVSUBTITLES; + @val @override String providerName = subtitleSource.name(); + @val @override boolean useSeasonForSerieId = false; public JTVsubtitlesAdapter(Manager manager, UserInteractionHandler userInteractionHandler) { super(manager, userInteractionHandler); @@ -47,7 +47,7 @@ public JTVsubtitlesAdapter(Manager manager, UserInteractionHandler userInteracti try { return new JTVSubtitlesApi(manager); } catch (Exception e) { - throw new SubtitlesProviderInitException(getProviderName(), e); + throw new SubtitlesProviderInitException(providerName, e); } }); } @@ -58,95 +58,82 @@ private JTVSubtitlesApi getApi() { } @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.TVSUBTITLES; - } - - @Override - public String getProviderName() { - return getSubtitleSource().name(); - } - - @Override - public List searchMovieSubtitlesWithHash(String hash, Language language) { + public List searchMovieSubtitlesWithHash(String hash, Language language) { // TODO implement this return List.of(); } @Override - public List searchMovieSubtitlesWithId(int tvdbId, Language language) { + public List searchMovieSubtitlesWithId(int tvdbId, Language language) { // TODO implement this return List.of(); } @Override - public List searchMovieSubtitlesWithName(String name, int year, Language language) { + public Collection searchMovieSubtitlesWithName(String name, @Nullable Integer year, + Language language) { // TODO implement this return List.of(); } @Override - public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, Language language) { + public Set convertToSubtitles(MovieRelease movieRelease, Set subtitles, + Language language) { // TODO implement this return Set.of(); } @Override - public Set searchSerieSubtitles(TvRelease tvRelease, Language language) throws TvSubtitlesException { - return getProviderSerieId(tvRelease) - .map(providerSerieId -> tvRelease.getEpisodeNumbers().stream() - .flatMap(episode -> { - try { - return getApi().getSubtitles(providerSerieId, tvRelease.getSeason(), episode, language).stream(); - } catch (TvSubtitlesException e) { - LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(getSubtitleSource().getName(), - TvRelease.formatName(providerSerieId.getProviderName(), tvRelease.getSeason(), episode), - e.getMessage()), e); - return Stream.empty(); - } - }) - .collect(Collectors.toSet())) - .orElseGet(Set::of); + public Set searchSerieSubtitles(TvRelease tvRelease, Language language) + throws TvSubtitlesException { + return getProviderSerieId(tvRelease).map( + providerSerieId -> tvRelease.episodes.stream().flatMap(episode -> { + try { + return getApi().getSubtitles(providerSerieId, tvRelease.season, episode, language).stream(); + } catch (TvSubtitlesException e) { + LOGGER.error("API %s searchSubtitles for serie [%s] (%s)".formatted(subtitleSource.name, + TvRelease.formatName(providerSerieId.providerName, tvRelease.season, episode), + e.getMessage()), e); + return Stream.empty(); + } + }).collect(Collectors.toSet())).orElseGet(Set::of); } @Override - public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, Language language) { + public Set convertToSubtitles(TvRelease tvRelease, Collection subtitles, + Language language) { return subtitles.stream() - .map(sub -> Subtitle.downloadSource(sub.getUrl()) - .subtitleSource(getSubtitleSource()) - .fileName(sub.getFilename()) - .language(language) - .quality(ReleaseParser.getQualityKeyword(sub.getFilename() + " " + sub.getRip())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(ReleaseParser.extractReleasegroup(sub.getFilename(), StringUtils.endsWith(sub.getFilename(), ".srt"))) - .uploader(sub.getAuthor()) - .hearingImpaired(false)) - .collect(Collectors.toSet()); + .map(sub -> new Subtitle( + downloadSource:Subtitle.DownloadSource.of(sub.url), + subtitleSource:subtitleSource, + fileName:sub.filename, + language:language, + quality:ReleaseParser.getQualityKeyword(sub.filename + " " + sub.source), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:sub.releaseGroup)) + .collect(Collectors.toSet()); } @Override - public List getSortedProviderSerieIds(OptionalInt tvdbIdOptional, String serieName, int season) throws TvSubtitlesException { + public List getSortedProviderSerieIds(@Nullable Integer tvdbId, String serieName, int season) + throws TvSubtitlesException { Pattern yearPatter = Pattern.compile("\\((\\d\\d\\d\\d)-(\\d\\d\\d\\d)\\)"); - return getApi().getUrisForSerieName(serieName).stream() - .sorted(Comparator.comparing( - (ProviderSerieId n) -> !serieName.replaceAll("[^A-Za-z]", "").equalsIgnoreCase(n.getName().replaceAll("[^A-Za-z]", ""))) - .thenComparing((ProviderSerieId providerSerieId) -> { - Matcher matcher = yearPatter.matcher(providerSerieId.getName()); - if (matcher.find()) { - return Integer.parseInt(matcher.group(2)); - } - return 0; - }, Comparator.reverseOrder())) - .toList(); - } - - @Override - public boolean useSeasonForSerieId() { - return false; + return getApi().getUrisForSerieName(serieName) + .stream() + .sorted(Comparator.comparing((ProviderSerieId n) -> !serieName.replaceAll("[^A-Za-z]", "") + .equalsIgnoreCase(n.name.replaceAll("[^A-Za-z]", ""))) + .thenComparing((ProviderSerieId providerSerieId) -> { + Matcher matcher = yearPatter.matcher(providerSerieId.name); + if (matcher.find()) { + return Integer.parseInt(matcher.group(2)); + } + return 0; + }, Comparator.reverseOrder())) + .toList(); } @Override public String providerSerieIdToDisplayString(ProviderSerieId providerSerieId) { - return providerSerieId.getName(); + return providerSerieId.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/JAddic7edApi.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/JAddic7edApi.java index a70cb8c9..5d657ee5 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/JAddic7edApi.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/JAddic7edApi.java @@ -1,18 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed; import static java.nio.charset.StandardCharsets.*; +import static manifold.science.util.UnitConstants.*; +import static org.lodder.subtools.sublibrary.util.Sleep.*; import java.net.URLEncoder; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import manifold.science.measures.Time; import org.apache.commons.lang3.StringUtils; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -20,47 +20,44 @@ import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.exception.Addic7edException; import org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.model.Addic7edSubtitleDescriptor; +import org.lodder.subtools.sublibrary.Credentials; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; import org.lodder.subtools.sublibrary.ManagerException; +import org.lodder.subtools.sublibrary.PageContentParams; import org.lodder.subtools.sublibrary.cache.CacheType; -import org.lodder.subtools.sublibrary.data.Html; import org.lodder.subtools.sublibrary.data.ProviderSerieId; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import org.lodder.subtools.sublibrary.util.OptionalExtension; -import lombok.experimental.ExtensionMethod; +public class JAddic7edApi implements SubtitleApi { -@ExtensionMethod({ OptionalExtension.class }) -public class JAddic7edApi extends Html implements SubtitleApi { + private static final Time RATE_DURATION = 1 s; // seconds - private static final long RATEDURATION = 1; // seconds private static final String DOMAIN = "https://www.addic7ed.com"; - private final static Pattern TITLE_PATTERN = Pattern.compile(".*? - [0-9]+x[0-9]+ - (.*)"); - private final static Pattern VERSION_PATTERN = Pattern.compile("Version (.+), Duration: ([0-9]+).([0-9])+"); + private static final Pattern TITLE_PATTERN = Pattern.compile(".*? - \\d+x\\d+ - (.*)"); + private static final Pattern VERSION_PATTERN = Pattern.compile("Version (.+), Duration: (\\d+).(\\d)+"); + private final Manager manager; private final boolean speedy; - private LocalDateTime lastRequest = LocalDateTime.now(); + private Time lastRequest = Time.now(); + @val @override SubtitleSource subtitleSource = SubtitleSource.ADDIC7ED; - public JAddic7edApi(boolean speedy, Manager manager) { - super(manager, "Mozilla/5.25 Netscape/5.0 (Windows; I; Win95)"); + public JAddic7edApi(Manager manager, boolean speedy, Credentials credentials=null) throws Addic7edException { +// super(manager, "Mozilla/5.25 Netscape/5.0 (Windows; I; Win95)"); + this.manager = manager; this.speedy = speedy; + if (credentials != null) { + login(credentials); + } } - public JAddic7edApi(String username, String password, boolean speedy, Manager manager) throws Addic7edException { - super(manager, "Mozilla/5.25 Netscape/5.0 (Windows; I; Win95)"); - this.speedy = speedy; - login(username, password); - } - - public void login(String username, String password) throws Addic7edException { + public void login(Credentials credentials) throws Addic7edException { try { - getManager().postBuilder() - .url(DOMAIN + "/dologin.php") - .addData("username", username) - .addData("password", password) - .addData("remember", "false") - .post(); + manager.postBuilder("$DOMAIN/dologin.php") + .addData("username", credentials.username) + .addData("password", credentials.password) + .addData("remember", "false") + .post(); } catch (ManagerException e) { throw new Addic7edException(e); } @@ -71,16 +68,17 @@ public List getProviderId(String serieName) throws Addic7edExce return List.of(); } try { - List providerSerieIds = getContent(DOMAIN + "/allshows/" + serieName.split(" ")[0]) - .map(doc -> doc.select("table.tabel90 td a").stream() - .map(element -> new ProviderSerieId(element.text(), element.attr("href").split("/")[2])).toList()) - .orElseGet(List::of); + List providerSerieIds = + getContent("$DOMAIN/allshows/" + serieName.split(" ")[0]).selectAllByCss("table.tabel90 td a") + .stream() + .map(elem -> new ProviderSerieId(elem.text(), elem.attr("href").split("/")[2])) + .toList(); String serieNameFormatted = serieName.replaceAll("[^A-Za-z]", ""); List providerSerieIdsFormatted = providerSerieIds.stream().filter(providerId -> { - String formattedSerieName = providerId.getName().replaceAll("[^A-Za-z]", ""); + String formattedSerieName = providerId.name.replaceAll("[^A-Za-z]", ""); return StringUtils.containsIgnoreCase(serieNameFormatted, formattedSerieName) || - StringUtils.containsIgnoreCase(formattedSerieName, serieNameFormatted); + StringUtils.containsIgnoreCase(formattedSerieName, serieNameFormatted); }).toList(); return !providerSerieIdsFormatted.isEmpty() ? providerSerieIdsFormatted : providerSerieIds; } catch (Exception e) { @@ -88,153 +86,111 @@ public List getProviderId(String serieName) throws Addic7edExce } } - // private List getAllMappings() throws Addic7edException { - // return ALL_MAPPINGS.get(); - // } - // - // private final LazyThrowingSupplier, Addic7edException> ALL_MAPPINGS = - // new LazyThrowingSupplier<>(() -> getContent(DOMAIN) - // .map(doc -> doc.select("#qsShow option").stream() - // .map(e -> new ProviderSerieId(e.text(), e.attr("value"))) - // .toList()) - // .orElseGet(List::of)); - - public List getSubtitles(SerieMapping addic7edSerieMapping, int season, int episode, Language language) - throws Addic7edException { - return getManager().valueBuilder() - .memoryCache() - .key("%s-subtitles-%s-%s-%s-%s".formatted(getSubtitleSource().name(), addic7edSerieMapping.getProviderId(), season, episode, - language)) - .collectionSupplier(Addic7edSubtitleDescriptor.class, () -> { - List languageIds = LanguageId.forLanguage(language); - String url = "%s/serie/%s/%s/%s/%s".formatted( - DOMAIN, - URLEncoder.encode(addic7edSerieMapping.getProviderName().replace(" ", "_"), UTF_8), - season, - episode, - languageIds.size() == 1 ? languageIds.get(0).getId() : LanguageId.ALL.getId()); - - Optional doc = getContent(url); - if (doc.isEmpty()) { - return List.of(); + public List getSubtitles(SerieMapping addic7edSerieMapping, int season, int episode, + Language language) throws Addic7edException { + + return manager.getCache(CacheType.MEMORY, + "%s-subtitles-%s-%s-%s-%s".formatted(subtitleSource.name(), addic7edSerieMapping.providerId, + season, episode, language)) + .getCollection(() -> { + List languageIds = LanguageId.forLanguage(language); + String url = "%s/serie/%s/%s/%s/%s".formatted(DOMAIN, + URLEncoder.encode(addic7edSerieMapping.providerName.replace(" ", "_"), UTF_8), season, + episode, languageIds.size() == 1 ? languageIds.first.id : LanguageId.ALL.id); + + Document doc = getContent(url); + String title = null; + + Elements elTitle = doc.getElementsByClass("titulo"); + if (elTitle.size() == 1) { + Matcher matcher = TITLE_PATTERN.matcher(elTitle.first.html()); + if (matcher.matches()) { + title = matcher.group(1); } + } - String title = null; - - Elements elTitel = doc.get().getElementsByClass("titulo"); - if (elTitel.size() == 1) { - Matcher matcher = TITLE_PATTERN.matcher(elTitel.get(0).html()); - if (matcher.matches()) { - title = matcher.group(1); + Elements blocks = doc.select(".tabel95[width='100%']"); + + List lSubtitles = new ArrayList<>(); + for (Element block : blocks) { + String uploader = ""; + String version = null; + String lang = null; + String download = null; + boolean hearingImpaired = false; + + Elements classesNewsTitle = block.getElementsByClass("NewsTitle"); + Elements classesNewsDate = block.getElementsByClass("newsDate").select("td[colspan=3]"); + if (classesNewsTitle.size() == 1 && classesNewsDate.size() == 1) { + Matcher m = VERSION_PATTERN.matcher(classesNewsTitle.first.text().trim()); + if (!m.matches()) { + break; + } else { + version = m.group(1).trim(); + uploader = block.selectFirst("a[href*=user/]").text(); + hearingImpaired = !block.select("img[title~=Hearing]").isEmpty(); } } - String uploader, version, lang, download; - boolean hearingImpaired; - Elements blocks = doc.get().select(".tabel95[width='100%']"); - - List lSubtitles = new ArrayList<>(); - for (Element block : blocks) { - uploader = ""; - version = null; - lang = null; - download = null; - hearingImpaired = false; - - Elements classesNewsTitle = block.getElementsByClass("NewsTitle"); - Elements classesNewsDate = block.getElementsByClass("newsDate").select("td[colspan=3]"); - if (classesNewsTitle.size() == 1 && classesNewsDate.size() == 1) { - Matcher m = VERSION_PATTERN.matcher(classesNewsTitle.get(0).text().trim()); - if (!m.matches()) { - break; - } else { - version = m.group(1).trim(); - uploader = block.selectFirst("a[href*=user/]").text(); - hearingImpaired = !block.select("img[title~=Hearing]").isEmpty(); + if (version != null) { + Elements tds = block.select("tr:contains(Completed)"); + Elements reqTds = tds.select("td").not("td[rowspan=2]"); + for (Element td : reqTds) { + if (td.hasClass("language")) { + lang = td.html().substring(0, td.html().indexOf("<")); } - } - - if (version != null) { - Elements tds = block.select("tr:contains(Completed)"); - Elements reqTds = tds.select("td").not("td[rowspan=2]"); - for (Element td : reqTds) { - if (td.hasClass("language")) { - lang = td.html().substring(0, td.html().indexOf("<")); - } - // incomplete not wanted - if ((lang != null && td.toString().toLowerCase().contains("completed")) - && td.html().toLowerCase().contains("% completed")) { - lang = null; - } + // incomplete not wanted + if ((lang != null && td.toString().toLowerCase().contains("completed")) && + td.html().toLowerCase().contains("% completed")) { + lang = null; + } - Elements downloadElements = td.getElementsByClass("buttonDownload"); - if (lang != null && downloadElements.size() > 0) { - if (downloadElements.size() == 1) { - download = DOMAIN + downloadElements.get(0).attr("href"); - } - if (downloadElements.size() == 2) { - download = DOMAIN + downloadElements.get(1).attr("href"); - } + Elements downloadElements = td.getElementsByClass("buttonDownload"); + if (lang != null && !downloadElements.isEmpty()) { + if (downloadElements.size() == 1) { + download = DOMAIN + downloadElements.first.attr("href"); + } else if (downloadElements.size() == 2) { + download = DOMAIN + downloadElements.get(1).attr("href"); } - if (lang != null && download != null && title != null) { - Addic7edSubtitleDescriptor sub = - new Addic7edSubtitleDescriptor() - .setUploader(uploader) - .setTitle(title.trim()) - .setVersion(version.trim()) - .setUrl(download) - .setLanguage(Language.fromValueOptional(lang.trim()).orElse(null)) - .setHearingImpaired(hearingImpaired); - if (!isDuplicate(lSubtitles, sub)) { - lSubtitles.add(sub); - } - lang = null; - download = null; + } + if (lang != null && download != null && title != null) { + Addic7edSubtitleDescriptor sub = new Addic7edSubtitleDescriptor(version.trim(), + Language.fromValueOptional(lang.trim()).orElse(null), download, title.trim(), + uploader, hearingImpaired); + if (!isDuplicate(lSubtitles, sub)) { + lSubtitles.add(sub); } + lang = null; + download = null; } } } - return lSubtitles; - }).getCollection(); + } + return lSubtitles; + }); } public boolean isDuplicate(List lSubtitles, Addic7edSubtitleDescriptor sub) { - return lSubtitles.stream() - .anyMatch(s -> s.getLanguage() == sub.getLanguage() - && StringUtils.equals(s.getUrl(), sub.getUrl()) - && StringUtils.equals(s.getVersion(), sub.getVersion())); + return lSubtitles.stream().anyMatch(s -> s.language == sub.language && StringUtils.equals(s.url, sub.url) && + StringUtils.equals(s.version, sub.version)); } - private Optional getContent(String url) throws Addic7edException { - return getContent(url, null); - } - - private Optional getContent(String url, Predicate emptyResultPredicate) throws Addic7edException { + private Document getContent(String url) throws Addic7edException { try { - if (!speedy && !getManager().valueBuilder().cacheType(CacheType.MEMORY).key(url).isPresent()) { + if (!speedy && !manager.getCache(CacheType.MEMORY, url).isPresent()) { // if (ChronoUnit.SECONDS.between(lastRequest, LocalDateTime.now()) < RATEDURATION) { // LOGGER.info("RateLimit is reached for ADDIC7ed, please wait {} seconds", RATEDURATION); // } - while (ChronoUnit.SECONDS.between(lastRequest, LocalDateTime.now()) < RATEDURATION) { - try { - // Pause for 1 seconds - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - // restore interrupted status - Thread.currentThread().interrupt(); - } + Time timeToSleep = RATE_DURATION - Time.now() + lastRequest; + if (timeToSleep.isPositive) { + sleep(timeToSleep); } - lastRequest = LocalDateTime.now(); + lastRequest = Time.now(); } - return this.getHtml(url).cacheType(CacheType.NONE).getAsJsoupDocument(emptyResultPredicate); + return manager.getAsJsoupDocument(PageContentParams.params(url:url, userAgent:"")); } catch (Exception e) { throw new Addic7edException(e); } } - - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.ADDIC7ED; - } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/LanguageId.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/LanguageId.java index 51abb175..2440789e 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/LanguageId.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/LanguageId.java @@ -1,16 +1,12 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.sublibrary.Language; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor +@AllArgsConstructor public enum LanguageId { ALL(null, 0), @@ -80,10 +76,10 @@ public enum LanguageId { VIETNAMESE(Language.VIETNAMESE, 45), WELSH(Language.WELSH, 65); - private final Language language; - private final int id; + @val Language language; + @val int id; public static List forLanguage(Language language) { - return Arrays.stream(LanguageId.values()).filter(langId -> langId.getLanguage() == language).collect(Collectors.toList()); + return LanguageId.values().stream().filter(langId -> langId.language == language).toList(); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/exception/Addic7edException.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/exception/Addic7edException.java index eefd94cb..2f1de388 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/exception/Addic7edException.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/exception/Addic7edException.java @@ -2,11 +2,10 @@ import java.io.Serial; +import lombok.experimental.StandardException; import org.lodder.subtools.sublibrary.exception.SubtitlesProviderException; import org.lodder.subtools.sublibrary.model.SubtitleSource; -import lombok.experimental.StandardException; - @StandardException public class Addic7edException extends SubtitlesProviderException { @@ -15,6 +14,6 @@ public class Addic7edException extends SubtitlesProviderException { @Override public String getSubtitleProvider() { - return SubtitleSource.ADDIC7ED.getName(); + return SubtitleSource.ADDIC7ED.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/model/Addic7edSubtitleDescriptor.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/model/Addic7edSubtitleDescriptor.java index 080f51b1..f1e5f9f5 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/model/Addic7edSubtitleDescriptor.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/model/Addic7edSubtitleDescriptor.java @@ -1,22 +1,10 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.model; -import org.lodder.subtools.sublibrary.Language; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; +import java.io.Serializable; -@EqualsAndHashCode -@Accessors(chain = true) -@Getter -@Setter -public class Addic7edSubtitleDescriptor { +import org.jspecify.annotations.Nullable; +import org.lodder.subtools.sublibrary.Language; - private String version; - private Language language; - private String url; - private String title; - private String uploader; - private boolean hearingImpaired; +public record Addic7edSubtitleDescriptor(String version, @Nullable Language language, String url, String title, + String uploader, boolean hearingImpaired) implements Serializable { } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/proxy/gestdown/JAddic7edProxyGestdownApi.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/proxy/gestdown/JAddic7edProxyGestdownApi.java index 163ca312..f9648fee 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/proxy/gestdown/JAddic7edProxyGestdownApi.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/addic7ed/proxy/gestdown/JAddic7edProxyGestdownApi.java @@ -1,10 +1,13 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.addic7ed.proxy.gestdown; -import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; +import extensions.java.lang.String.StringExt; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.gestdown.api.SubtitlesApi; import org.gestdown.api.TvShowsApi; import org.gestdown.invoker.ApiException; @@ -14,76 +17,67 @@ import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleApi; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; +import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.control.ReleaseParser; -import org.lodder.subtools.sublibrary.data.Html; import org.lodder.subtools.sublibrary.data.ProviderSerieId; import org.lodder.subtools.sublibrary.model.Subtitle; import org.lodder.subtools.sublibrary.model.SubtitleMatchType; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import org.lodder.subtools.sublibrary.util.OptionalExtension; -import org.lodder.subtools.sublibrary.util.StringUtil; - -import lombok.experimental.ExtensionMethod; // see https://www.gestdown.info/Api -@ExtensionMethod({ OptionalExtension.class }) -public class JAddic7edProxyGestdownApi extends Html implements SubtitleApi { +public class JAddic7edProxyGestdownApi implements SubtitleApi { private static final String DOMAIN = "https://api.gestdown.info"; - private final TvShowsApi tvShowsApi; - private final SubtitlesApi subtitlesApi; + + private final Manager manager; + private final TvShowsApi tvShowsApi = new TvShowsApi(); + private final SubtitlesApi subtitlesApi = new SubtitlesApi(); + @val @override SubtitleSource subtitleSource = SubtitleSource.ADDIC7ED; public JAddic7edProxyGestdownApi(Manager manager) { - super(manager); - tvShowsApi = new TvShowsApi(); - subtitlesApi = new SubtitlesApi(); + this.manager = manager; } public List getProviderSerieName(String serieName) throws ApiException { return tvShowsApi.showsSearchSearchGet(serieName).getShows().stream() - .map(showDto -> new ProviderSerieId(showDto.getName(), showDto.getId().toString())).toList(); + .map(showDto -> new ProviderSerieId(showDto.getName(), showDto.getId().toString())).toList(); } public List getProviderSerieName(int tvdbId) throws ApiException { return tvShowsApi.showsExternalTvdbTvdbIdGet(tvdbId).getShows().stream() - .map(showDto -> new ProviderSerieId(showDto.getName(), showDto.getId().toString())).toList(); + .map(showDto -> new ProviderSerieId(showDto.getName(), showDto.getId().toString())).toList(); } - public Set getSubtitles(SerieMapping providerSerieId, int season, int episode, Language language) throws ApiException { - return getManager().valueBuilder() - .memoryCache() - .key("%s-subtitles-%s-%s-%s-%s".formatted(getSubtitleSource().name(), providerSerieId.getProviderId(), season, episode, language)) - .collectionSupplier(Subtitle.class, () -> { - Set results = new HashSet<>(); - SubtitleSearchResponse response = subtitlesApi.subtitlesGetShowUniqueIdSeasonEpisodeLanguageGet(language.getName(), - UUID.fromString(providerSerieId.getProviderId()), season, episode); - response.getMatchingSubtitles().stream() - .filter(SubtitleDto::isCompleted).map(sub -> mapToSubtitle(sub, response.getEpisode(), language)) - .forEach(results::add); - return results; - }).getCollection(); + public Set getSubtitles(SerieMapping providerSerieId, int season, int episode, Language language) + throws ApiException { + return manager.getCache(CacheType.MEMORY, "%s-subtitles-%s-%s-%s-%s".formatted(subtitleSource.name(), + providerSerieId.providerId, season, episode, language)) + .getCollection(() -> { + SubtitleSearchResponse response = subtitlesApi.subtitlesGetShowUniqueIdSeasonEpisodeLanguageGet( + language.getName(), UUID.fromString(providerSerieId.providerId), season, episode); + return response.getMatchingSubtitles() + .stream() + .filter(SubtitleDto::isCompleted) + .map(sub -> mapToSubtitle(sub, response.episode, language)) + .collect(Collectors.toSet()); + }); } private Subtitle mapToSubtitle(SubtitleDto sub, EpisodeDto episodedto, Language language) { - return Subtitle.downloadSource(getDownloadUrl(sub.getDownloadUri())) - .subtitleSource(getSubtitleSource()) - .fileName(StringUtil - .removeIllegalFilenameChars("%s - %s - %s".formatted(episodedto.getShow(), episodedto.getTitle(), sub.getVersion()))) - .language(language) - .quality(ReleaseParser.getQualityKeyword(episodedto.getTitle() + " " + sub.getVersion())) - .subtitleMatchType(SubtitleMatchType.EVERYTHING) - .releaseGroup(sub.getVersion()) - .uploader("") - .hearingImpaired(false); + return new Subtitle( + downloadSource:Subtitle.DownloadSource.of(getDownloadUrl(sub.getDownloadUri())), + subtitleSource:subtitleSource, + fileName:StringExt.removeIllegalFilenameChars("${episodedto.show} - ${episodedto.title} - ${sub.version}"), + language:language, + quality:ReleaseParser.getQualityKeyword(episodedto.getTitle() + " " + sub.getVersion()), + subtitleMatchType:SubtitleMatchType.EVERYTHING, + releaseGroup:sub.getVersion(), + uploader:"", + hearingImpaired:false); } public String getDownloadUrl(String subtitleId) { return DOMAIN + subtitleId; } - - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.ADDIC7ED; - } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/DownloadSubtitle.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/DownloadSubtitle.java index a093fc4d..f6bef9f4 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/DownloadSubtitle.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/DownloadSubtitle.java @@ -1,28 +1,29 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles; +import lombok.RequiredArgsConstructor; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.exception.OpenSubtitlesException; import org.opensubtitles.api.DownloadApi; import org.opensubtitles.invoker.ApiClient; import org.opensubtitles.model.Download200Response; import org.opensubtitles.model.DownloadRequest; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; - -@Accessors(fluent = true, chain = true) -@Setter @RequiredArgsConstructor -public class DownloadSubtitle extends OpenSubtitlesExecuter { +public final class DownloadSubtitle extends OpenSubtitlesExecuter { private final ApiClient apiClient; private int fileId; public Download200Response download() throws OpenSubtitlesException { try { - return execute(() -> new DownloadApi(apiClient).download(new DownloadRequest().fileId(fileId))); + return execute(() -> new DownloadApi(apiClient) + .download("SubTools", new DownloadRequest().fileId(fileId))); } catch (Exception e) { throw new OpenSubtitlesException(e); } } + + public DownloadSubtitle fileId(int fileId) { + this.fileId = fileId; + return this; + } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesApi.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesApi.java index 61f1c084..0a72e6a5 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesApi.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesApi.java @@ -1,18 +1,22 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles; +import static manifold.science.measures.TimeUnit.*; + import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; -import org.json.JSONArray; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.exception.OpenSubtitlesException; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.model.OpensubtitleSerieId; +import org.lodder.subtools.sublibrary.Credentials; import org.lodder.subtools.sublibrary.Manager; +import org.lodder.subtools.sublibrary.Manager.Retry; +import org.lodder.subtools.sublibrary.PageContentParams; import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.model.SubtitleSource; -import org.lodder.subtools.sublibrary.util.JSONUtils; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.lodder.subtools.sublibrary.util.http.HttpClientException; import org.opensubtitles.api.AuthenticationApi; import org.opensubtitles.invoker.ApiClient; @@ -20,35 +24,31 @@ import org.opensubtitles.model.Login200Response; import org.opensubtitles.model.LoginRequest; -import lombok.Getter; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ OptionalExtension.class, JSONUtils.class }) public class OpenSubtitlesApi implements SubtitleApi { - private static final String APIKEY = "lNNp0yv0ah8gytkmYPbHwuaATJqr4rS9"; + private static final String APIKEY = "3IlyaP0KNv6QmJ1gOBX8IXwzD1P9b8c0";//"lNNp0yv0ah8gytkmYPbHwuaATJqr4rS9"; private static final ApiClient API_CLIENT; - @Getter - private final Manager manager; + private static final String USER_AGENT = "SubTools"; + @val Manager manager; + @val @override SubtitleSource subtitleSource = SubtitleSource.OPENSUBTITLES; static { API_CLIENT = new ApiClient(); API_CLIENT.setApiKey(APIKEY); } - public OpenSubtitlesApi(Manager manager) { + public OpenSubtitlesApi(Manager manager, Credentials credentials=null) throws OpenSubtitlesException { this.manager = manager; + if (credentials != null) { + login(credentials); + } } - public OpenSubtitlesApi(Manager manager, String userName, String password) throws OpenSubtitlesException { - this(manager); - login(userName, password); - } - - public void login(String userName, String password) throws OpenSubtitlesException { + public void login(Credentials credentials) throws OpenSubtitlesException { try { Login200Response loginResponse = - new AuthenticationApi(API_CLIENT).login("application/json", new LoginRequest().username(userName).password(password)); + new AuthenticationApi(API_CLIENT).login("application/json", USER_AGENT, + new LoginRequest().username(credentials.username).password(credentials.password)); API_CLIENT.setBearerToken(loginResponse.getToken()); } catch (ApiException e) { throw new OpenSubtitlesException(e); @@ -57,7 +57,8 @@ public void login(String userName, String password) throws OpenSubtitlesExceptio public static boolean isValidCredentials(String userName, String password) { try { - new AuthenticationApi(API_CLIENT).login("application/json", new LoginRequest().username(userName).password(password)); + new AuthenticationApi(API_CLIENT).login("application/json", USER_AGENT, + new LoginRequest().username(userName).password(password)); return true; } catch (ApiException e) { return false; @@ -74,26 +75,23 @@ public DownloadSubtitle downloadSubtitle() { public List getProviderSerieIds(String serieName) throws OpenSubtitlesException { try { - JSONArray shows = manager.getPageContentBuilder() - .url("https://www.opensubtitles.org/libs/suggest.php?format=json3&MovieName=" - + URLEncoder.encode(serieName.toLowerCase(), StandardCharsets.UTF_8)) - .userAgent("") - .cacheType(CacheType.MEMORY) - .retries(1) - .retryPredicate(exception -> exception instanceof HttpClientException e && e.getResponseCode() == 429) - .retryWait(5) - .getAsJsonArray(); - return shows.stream() - .filter(show -> "tv".equals(show.getString("kind"))) - .map(show -> new OpensubtitleSerieId(show.getString("name"), show.getInt("id"), show.getString("year"))) - .toList(); + return manager.getAsJsonArray(PageContentParams.params( + url:"https://www.opensubtitles.org/libs/suggest.php?format=json3&MovieName=" + + URLEncoder.encode(serieName.toLowerCase(), StandardCharsets.UTF_8), + cacheType:CacheType.MEMORY, + userAgent:"", + retry:new Retry( + 1, + exc -> exc instanceof HttpClientException e && e.responseCode == 429, + 5 Second) + )) + .streamJsonObjects() + .filter(show -> "tv".equals(show.getString("kind"))) + .map(show -> new OpensubtitleSerieId(show.getString("name"), show.getInt("id"), + show.getString("year"))) + .toList(); } catch (Exception e) { throw new OpenSubtitlesException(e); } } - - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.OPENSUBTITLES; - } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesExecuter.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesExecuter.java index 1889c005..5ba76ec1 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesExecuter.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesExecuter.java @@ -1,10 +1,12 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles; -import org.opensubtitles.invoker.ApiException; +import static manifold.science.measures.TimeUnit.*; +import static org.lodder.subtools.sublibrary.util.Sleep.*; import com.pivovarit.function.ThrowingSupplier; +import org.opensubtitles.invoker.ApiException; -public abstract class OpenSubtitlesExecuter { +public abstract sealed class OpenSubtitlesExecuter permits DownloadSubtitle, SearchSubtitles { protected T execute(ThrowingSupplier callable) throws ApiException { try { @@ -12,11 +14,7 @@ protected T execute(ThrowingSupplier callable) throws ApiEx } catch (ApiException e) { if (e.getCode() == 429 || e.getMessage().contains("ratelimit")) { // Too Many Requests - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - throw new RuntimeException(e1); - } + sleep(1 Second); // retry return callable.get(); } else { diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesHasher.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesHasher.java index 125b247a..f26d8b62 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesHasher.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/OpenSubtitlesHasher.java @@ -13,9 +13,8 @@ import java.nio.file.StandardOpenOption; /** - * Hash code is based on Media Player Classic. In natural language it calculates: size + 64bit - * checksum of the first and last 64k (even if they overlap because the file is smaller than - * 128k). + * Hash code is based on Media Player Classic. In natural language it calculates: size + 64bit checksum of the first and + * last 64k (even if they overlap because the file is smaller than 128k). */ public class OpenSubtitlesHasher { @@ -30,8 +29,9 @@ public static String computeHash(Path path) throws IOException { try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) { long head = computeHashForChunk(fileChannel.map(MapMode.READ_ONLY, 0, chunkSizeForFile)); - long tail = computeHashForChunk(fileChannel.map(MapMode.READ_ONLY, Math.max(size - HASH_CHUNK_SIZE, 0), chunkSizeForFile)); - return String.format("%016x", size + head + tail); + long tail = computeHashForChunk( + fileChannel.map(MapMode.READ_ONLY, Math.max(size - HASH_CHUNK_SIZE, 0), chunkSizeForFile)); + return "%016x".formatted(size + head + tail); } } @@ -39,7 +39,8 @@ public static String computeHash(InputStream stream, long length) throws IOExcep int chunkSizeForFile = (int) Math.min(HASH_CHUNK_SIZE, length); - // buffer that will contain the head and the tail chunk, chunks will overlap if length is smaller than two chunks + // buffer that will contain the head and the tail chunk, chunks will overlap if length is smaller than two + // chunks byte[] chunkBytes = new byte[(int) Math.min(2 * HASH_CHUNK_SIZE, length)]; try (DataInputStream in = new DataInputStream(stream)) { @@ -59,9 +60,10 @@ public static String computeHash(InputStream stream, long length) throws IOExcep in.readFully(chunkBytes, chunkSizeForFile, chunkBytes.length - chunkSizeForFile); long head = computeHashForChunk(ByteBuffer.wrap(chunkBytes, 0, chunkSizeForFile)); - long tail = computeHashForChunk(ByteBuffer.wrap(chunkBytes, chunkBytes.length - chunkSizeForFile, chunkSizeForFile)); + long tail = computeHashForChunk( + ByteBuffer.wrap(chunkBytes, chunkBytes.length - chunkSizeForFile, chunkSizeForFile)); - return String.format("%016x", length + head + tail); + return "%016x".formatted(length + head + tail); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/SearchSubtitles.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/SearchSubtitles.java index 50592fed..c0cea172 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/SearchSubtitles.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/SearchSubtitles.java @@ -1,5 +1,10 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.exception.OpenSubtitlesException; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param.AiTranslatedEnum; import org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param.ForeignPartsOnlyEnum; @@ -18,87 +23,85 @@ import org.opensubtitles.invoker.ApiClient; import org.opensubtitles.model.Subtitles200Response; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; - @Accessors(fluent = true, chain = true) @Getter @Setter @RequiredArgsConstructor -public class SearchSubtitles extends OpenSubtitlesExecuter { +public final class SearchSubtitles extends OpenSubtitlesExecuter { private final Manager manager; private final ApiClient apiClient; - private AiTranslatedEnum aiTranslated; + private @Nullable AiTranslatedEnum aiTranslated; + + private @Nullable Integer episode; - private Integer episode; + private @Nullable ForeignPartsOnlyEnum foreignPartsOnly; - private ForeignPartsOnlyEnum foreignPartsOnly; + private @Nullable HearingImpairedEnum hearingImpaired; - private HearingImpairedEnum hearingImpaired; + private @Nullable Integer id; - private Integer id; + private @Nullable Integer imdbId; - private Integer imdbId; + private @Nullable Language language; - private Language language; + private @Nullable MachineTranslatedEnum machineTranslated; - private MachineTranslatedEnum machineTranslated; + private @Nullable String movieHash; - private String movieHash; + private @Nullable MoviehashMatchEnum movieHashMatch; - private MoviehashMatchEnum movieHashMatch; + private @Nullable SearchSubtitlesEnum orderBy; - private SearchSubtitlesEnum orderBy; + private @Nullable OrderDirectionEnum orderDirection; - private OrderDirectionEnum orderDirection; + private @Nullable Integer page; - private Integer page; + private @Nullable Integer parentFeatureId; - private Integer parentFeatureId; + private @Nullable Integer parentImdbId; - private Integer parentImdbId; + private @Nullable Integer parentTmdbId; - private Integer parentTmdbId; + private @Nullable String query; - private String query; + private @Nullable Integer season; - private Integer season; + private @Nullable Integer tmdbId; - private Integer tmdbId; + private @Nullable TrustedSourcesEnum trustedSources; - private TrustedSourcesEnum trustedSources; + private @Nullable TypeEnum type; - private TypeEnum type; + private @Nullable Integer userId; - private Integer userId; + private @Nullable Integer year; - private Integer year; + private String userAgent = "SubTools"; // should be set public Subtitles200Response searchSubtitles() throws OpenSubtitlesException { - return manager.valueBuilder() - .cacheType(CacheType.MEMORY) - .key("OpenSubtitles-subtitles-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s" - .formatted(id, imdbId, tmdbId, type, query, language, movieHash, userId, hearingImpaired, - foreignPartsOnly, trustedSources, machineTranslated, aiTranslated, orderBy, orderDirection, - parentFeatureId, parentImdbId, parentTmdbId, season, episode, year, movieHashMatch, page)) - .valueSupplier(() -> { - try { - return execute(() -> new SubtitlesApi(apiClient).subtitles(id, imdbId, tmdbId, getValue(type), query, - language != null ? language.getLangCode() : null, movieHash, - userId, getValue(hearingImpaired), getValue(foreignPartsOnly), getValue(trustedSources), getValue(machineTranslated), - getValue(aiTranslated), orderBy == null ? null : orderBy.getParamName(), getValue(orderDirection), parentFeatureId, - parentImdbId, parentTmdbId, season, episode, year, getValue(movieHashMatch), page)); - } catch (Exception e) { - throw new OpenSubtitlesException(e); - } - }) - .get(); + return manager.getCache(CacheType.MEMORY, + "OpenSubtitles-subtitles-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s".formatted( + id, imdbId, tmdbId, type, query, language, movieHash, userId, hearingImpaired, foreignPartsOnly, + trustedSources, machineTranslated, aiTranslated, orderBy, orderDirection, parentFeatureId, + parentImdbId, parentTmdbId, season, episode, year, movieHashMatch, page)) + .get(() -> { + try { + return execute( + () -> new SubtitlesApi(apiClient).subtitles(id, imdbId, tmdbId, getValue(type), query, + language != null ? language.langCode : null, movieHash, userId, + getValue(hearingImpaired), getValue(foreignPartsOnly), getValue(trustedSources), + getValue(machineTranslated), getValue(aiTranslated), + orderBy == null ? null : orderBy.paramName, getValue(orderDirection), + parentFeatureId, parentImdbId, parentTmdbId, season, episode, year, + getValue(movieHashMatch), page, userAgent)); + } catch (Exception e) { + throw new OpenSubtitlesException(e); + } + }); } private String getValue(ParamIntf param) { - return param == null ? null : param.getValue(); + return param == null ? null : param.value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/exception/OpenSubtitlesException.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/exception/OpenSubtitlesException.java index e863b27a..2044554b 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/exception/OpenSubtitlesException.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/exception/OpenSubtitlesException.java @@ -2,11 +2,10 @@ import java.io.Serial; +import lombok.experimental.StandardException; import org.lodder.subtools.sublibrary.exception.SubtitlesProviderException; import org.lodder.subtools.sublibrary.model.SubtitleSource; -import lombok.experimental.StandardException; - @StandardException public class OpenSubtitlesException extends SubtitlesProviderException { @@ -15,6 +14,6 @@ public class OpenSubtitlesException extends SubtitlesProviderException { @Override public String getSubtitleProvider() { - return SubtitleSource.OPENSUBTITLES.getName(); + return SubtitleSource.OPENSUBTITLES.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesMovieDescriptor.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesMovieDescriptor.java index 74602779..06cd0c00 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesMovieDescriptor.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesMovieDescriptor.java @@ -2,27 +2,21 @@ import java.util.Objects; -import lombok.Getter; -import lombok.Setter; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; +import manifold.ext.props.rt.api.var; -@Getter -@Setter +@AllArgsConstructor public class OpenSubtitlesMovieDescriptor { - private final int year; - private final int imdbId; - private String name; + @var String name; + @val int year; + @val int imdbId; public OpenSubtitlesMovieDescriptor(String name, int imdbId) { this(name, -1, imdbId); } - public OpenSubtitlesMovieDescriptor(String name, int year, int imdbId) { - this.name = name; - this.year = year; - this.imdbId = imdbId; - } - @Override public boolean equals(Object object) { return object instanceof OpenSubtitlesMovieDescriptor other @@ -39,7 +33,6 @@ public String toString() { if (year < 0) { return name; } - - return String.format("%s (%d)", name, year); + return "$name ($year)"; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesSubtitleDescriptor.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesSubtitleDescriptor.java index 8f083d5c..92597a50 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesSubtitleDescriptor.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpenSubtitlesSubtitleDescriptor.java @@ -1,45 +1,42 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.model; -import lombok.Getter; -import lombok.Setter; +import manifold.ext.props.rt.api.var; -@Getter -@Setter public class OpenSubtitlesSubtitleDescriptor { - private String userNickName; - private String subFormat; - private int idSubtitle; - private int idMovie; - private String subBad; - private int userId; - private String zipDownloadLink; - private long subSize; - private String subFileName; - private String subDownloadLink; - private String userRank; - private String subActualCD; - private String movieImdbRating; - private String subAuthorComment; - private String subRating; - private String subtitlesLink; - private String subHearingImpaired; - private String subHash; - private int idSubMovieFile; - private String ISO639; - private int subDownloadsCnt; - private String movieHash; - private int subSumCD; - private String subComments; - private long movieByteSize; - private String languageName; - private int movieYear; - private String subLanguageID; - private String movieReleaseName; - private String movieTimeMS; - private String matchedBy; - private String movieName; - private String subAddDate; - private int idMovieImdb; - private String MovieNameEng; - private int idSubtitleFile; + @var String userNickName; + @var String subFormat; + @var int idSubtitle; + @var int idMovie; + @var String subBad; + @var int userId; + @var String zipDownloadLink; + @var long subSize; + @var String subFileName; + @var String subDownloadLink; + @var String userRank; + @var String subActualCD; + @var String movieImdbRating; + @var String subAuthorComment; + @var String subRating; + @var String subtitlesLink; + @var String subHearingImpaired; + @var String subHash; + @var int idSubMovieFile; + @var String ISO639; + @var int subDownloadsCnt; + @var String movieHash; + @var int subSumCD; + @var String subComments; + @var long movieByteSize; + @var String languageName; + @var int movieYear; + @var String subLanguageID; + @var String movieReleaseName; + @var String movieTimeMS; + @var String matchedBy; + @var String movieName; + @var String subAddDate; + @var int idMovieImdb; + @var String MovieNameEng; + @var int idSubtitleFile; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpensubtitleSerieId.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpensubtitleSerieId.java index 123dcfac..02132a33 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpensubtitleSerieId.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/model/OpensubtitleSerieId.java @@ -2,16 +2,13 @@ import java.io.Serial; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.sublibrary.data.ProviderSerieId; -import lombok.Getter; - -@Getter public class OpensubtitleSerieId extends ProviderSerieId { - @Serial - private static final long serialVersionUID = 5858875211782260667L; - private final String year; + @Serial private static final long serialVersionUID = 5858875211782260667L; + @val String year; public OpensubtitleSerieId(String name, int id, String year) { super(name, String.valueOf(id)); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/AiTranslatedEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/AiTranslatedEnum.java index 3c1f18c9..079e729d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/AiTranslatedEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/AiTranslatedEnum.java @@ -1,18 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum AiTranslatedEnum implements ParamIntf { EXCLUDE("exclude"), INCLUDE("include"); - private final String value; + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ForeignPartsOnlyEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ForeignPartsOnlyEnum.java index 7a6ba5d0..545b26da 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ForeignPartsOnlyEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ForeignPartsOnlyEnum.java @@ -1,18 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum ForeignPartsOnlyEnum implements ParamIntf { EXCLUDE("exclude"), INCLUDE("include"), ONLY("only"); - private final String value; + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/HearingImpairedEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/HearingImpairedEnum.java index 8586a61e..44d74b55 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/HearingImpairedEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/HearingImpairedEnum.java @@ -1,18 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum HearingImpairedEnum implements ParamIntf { EXCLUDE("exclude"), INCLUDE("include"), ONLY("only"); - private final String value; + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MachineTranslatedEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MachineTranslatedEnum.java index 4795d938..ef98bf0d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MachineTranslatedEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MachineTranslatedEnum.java @@ -1,19 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum MachineTranslatedEnum implements ParamIntf { EXCLUDE("exclude"), INCLUDE("include"); - private final String value; - + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MoviehashMatchEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MoviehashMatchEnum.java index 40ad9842..0731a418 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MoviehashMatchEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/MoviehashMatchEnum.java @@ -1,18 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum MoviehashMatchEnum implements ParamIntf { INCLUDE("include"), ONLY("only"); - private final String value; + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/OrderDirectionEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/OrderDirectionEnum.java index 3076c02c..c248164c 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/OrderDirectionEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/OrderDirectionEnum.java @@ -1,18 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum OrderDirectionEnum implements ParamIntf { ASCENDING("asc"), DESCENDING("desc"); - private final String value; + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ParamIntf.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ParamIntf.java index e0ef2281..0282aaa9 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ParamIntf.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/ParamIntf.java @@ -1,5 +1,7 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; +import manifold.ext.props.rt.api.val; + public interface ParamIntf { - String getValue(); + @val String value; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/SearchSubtitlesEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/SearchSubtitlesEnum.java index 36cd0d61..510e6819 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/SearchSubtitlesEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/SearchSubtitlesEnum.java @@ -1,11 +1,10 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum SearchSubtitlesEnum { // exclude, include (default: exclude) @@ -80,5 +79,5 @@ public enum SearchSubtitlesEnum { // Filter by movie/episode year YEAR("year"); - private final String paramName; + @val String paramName; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TrustedSourcesEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TrustedSourcesEnum.java index a2e7bcbb..ed2292d5 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TrustedSourcesEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TrustedSourcesEnum.java @@ -1,18 +1,19 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum TrustedSourcesEnum implements ParamIntf { - INCLUDE("include"), ONLY("only"); + INCLUDE("include"), + ONLY("only"); - private final String value; + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TypeEnum.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TypeEnum.java index 2cdbfb27..38de4c7d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TypeEnum.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/opensubtitles/param/TypeEnum.java @@ -1,18 +1,18 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.opensubtitles.param; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum TypeEnum implements ParamIntf { MOVIE("movie"), EPISODE("episode"), ALL("all"); - private final String value; + @val @override String value; @Override public String toString() { - return getValue(); + return value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/JPodnapisiApi.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/JPodnapisiApi.java index 1b36f7eb..26c5ecbd 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/JPodnapisiApi.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/JPodnapisiApi.java @@ -1,9 +1,11 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.podnapisi; +import static manifold.science.measures.TimeUnit.*; +import static org.lodder.subtools.sublibrary.PageContentParams.*; + import java.io.Serial; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; import java.util.Collections; import java.util.EnumMap; import java.util.List; @@ -12,96 +14,94 @@ import java.util.Optional; import java.util.function.Function; +import lombok.RequiredArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.podnapisi.exception.PodnapisiException; import org.lodder.subtools.multisubdownloader.subtitleproviders.podnapisi.model.PodnapisiSubtitleDescriptor; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; +import org.lodder.subtools.sublibrary.Manager.Retry; import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.data.ProviderSerieId; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import org.lodder.subtools.sublibrary.util.OptionalExtension; -import org.lodder.subtools.sublibrary.util.StringUtil; import org.lodder.subtools.sublibrary.util.http.HttpClientException; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.ExtensionMethod; - -@Getter(value = AccessLevel.PRIVATE) @RequiredArgsConstructor -@ExtensionMethod({ OptionalExtension.class, StringUtil.class }) public class JPodnapisiApi implements SubtitleApi { - public static final int maxAge = 90; private static final String DOMAIN = "https://www.podnapisi.net"; private final Manager manager; private final String userAgent; - private LocalDateTime nextCheck; + @val @override SubtitleSource subtitleSource = SubtitleSource.PODNAPISI; public Optional getPodnapisiShowName(String showName) throws PodnapisiException { String url = DOMAIN + "/sl/ppodnapisi/search?sK=" + showName.trim().toLowerCase().urlEncode(); - return getXml(url).selectFirst(".subtitle-entry") != null - ? Optional.of(new ProviderSerieId(showName, showName)) - : Optional.empty(); + return getXml(url).selectFirstByClass("subtitle-entry") != null ? + Optional.of(new ProviderSerieId(showName, showName)) : Optional.empty(); } - public List getMovieSubtitles(String movieName, int year, int season, int episode, Language language) - throws PodnapisiException { + public List getMovieSubtitles(String movieName, @Nullable Integer year, int season, + int episode, Language language) throws PodnapisiException { return getSubtitles(new SerieMapping(movieName, movieName, movieName, season), year, season, episode, language); } - public List getSerieSubtitles(SerieMapping providerSerieId, int season, int episode, Language language) - throws PodnapisiException { + public List getSerieSubtitles(SerieMapping providerSerieId, int season, int episode, + Language language) throws PodnapisiException { return getSubtitles(providerSerieId, null, season, episode, language); } - private List getSubtitles(SerieMapping providerSerieId, Integer year, int season, int episode, Language language) - throws PodnapisiException { - return getManager().valueBuilder() - .memoryCache() - .key("%s-subtitles-%s-%s-%s-%s".formatted(getSubtitleSource().name(), providerSerieId.getProviderId(), season, episode, language)) - .collectionSupplier(PodnapisiSubtitleDescriptor.class, () -> { - try { - StringBuilder url = new StringBuilder(DOMAIN + "/sl/ppodnapisi/search?sK=") - .append(URLEncoder.encode(providerSerieId.getProviderId().trim().toLowerCase(), StandardCharsets.UTF_8)); - if (PODNAPISI_LANGS.containsKey(language)) { - url.append("&sJ=").append(PODNAPISI_LANGS.get(language)); - } - if (year != null) { - url.append("&sY=").append(year); - } - if (season > 0) { - url.append("&sTS=").append(season).append("&sT=1"); // series - } else { - url.append("&sT=0"); // movies - } - if (episode > 0) { - url.append("&sTE=").append(episode); - } - url.append("&sXML=1"); - - return getXml(url.toString()).select("subtitle").stream().map(this::parsePodnapisiSubtitle).toList(); - } catch (Exception e) { - throw new PodnapisiException(e); + private List getSubtitles(SerieMapping providerSerieId, @Nullable Integer year, + int season, int episode, Language language) throws PodnapisiException { + return manager.getCache(CacheType.MEMORY, + "%s-subtitles-%s-%s-%s-%s".formatted(subtitleSource.name(), providerSerieId.providerId, season, episode, + language)) + .getCollection(() -> { + try { + StringBuilder url = new StringBuilder("$DOMAIN/sl/ppodnapisi/search?sK=").append( + URLEncoder.encode(providerSerieId.providerId.trim().toLowerCase(), StandardCharsets.UTF_8)); + if (PODNAPISI_LANGS.containsKey(language)) { + url.append("&sJ=").append(PODNAPISI_LANGS.get(language)); + } + if (year != null) { + url.append("&sY=").append(year); + } + if (season > 0) { + url.append("&sTS=").append(season).append("&sT=1"); // series + } else { + url.append("&sT=0"); // movies } - }) - .getCollection(); + if (episode > 0) { + url.append("&sTE=").append(episode); + } + url.append("&sXML=1"); + + return getXml(url.toString()).selectAllByTag("subtitle") + .stream() + .map(this::parsePodnapisiSubtitle) + .toList(); + } catch (Exception e) { + throw new PodnapisiException(e); + } + }, new Retry(1, ex -> ex instanceof HttpClientException e && e.responseCode >= 500, 1 Second)); } - protected Document getXml(String url) throws PodnapisiException { + protected @Nullable Document getXml(String url) throws PodnapisiException { try { - return manager.getPageContentBuilder().url(url).userAgent(getUserAgent()).cacheType(CacheType.MEMORY).retries(1) - .retryPredicate(e -> e instanceof HttpClientException httpClientException && httpClientException.getResponseCode() >= 500 - && httpClientException.getResponseCode() < 600) - .retryWait(5).getAsJsoupDocument(); + return manager.getAsJsoupDocument(params(url, CacheType.MEMORY, userAgent, + new Retry( + 1, + ex -> ex instanceof HttpClientException e && e.responseCode >= 500 && + e.responseCode < 600, + 5 Second))); } catch (Exception e) { throw new PodnapisiException(e); } @@ -110,83 +110,84 @@ protected Document getXml(String url) throws PodnapisiException { private PodnapisiSubtitleDescriptor parsePodnapisiSubtitle(Element elem) { Function getText = e -> e == null ? null : e.text(); return PodnapisiSubtitleDescriptor.builder() - .hearingImpaired(elem.select("new_flags flags").stream().anyMatch(flagElem -> "hearing_impaired".equals(flagElem.text()))) - .language(languageIdToLanguage(elem.selectFirst("languageId").text())) - .releaseString(elem.selectFirst("release").text().length() > 10 ? elem.selectFirst("release").text() - : elem.selectFirst("title").text().replace(":", "") + " " + elem.selectFirst("release").text()) - .uploaderName(elem.selectFirst("uploaderName").text()) - .url(elem.selectFirst("url").text() + "/download?") - .subtitleId(elem.selectFirst("id").text()) - .year(getText.apply(elem.selectFirst("year"))) - .imdb(getText.apply(elem.selectFirst("imdb"))) - .omdb(getText.apply(elem.selectFirst("omdb"))) - .build(); - } - - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.PODNAPISI; + .hearingImpaired(elem.select("new_flags flags") + .stream() + .anyMatch(flagElem -> "hearing_impaired".equals(flagElem.text()))) + .language(languageIdToLanguage(elem.selectFirst("languageId").text())) + .releaseString(elem.selectFirst("release").text().length() > 10 ? elem.selectFirst("release").text() : + elem.selectFirst("title").text().replace(":", "") + " " + elem.selectFirst("release").text()) + .uploaderName(elem.selectFirst("uploaderName").text()) + .url(elem.selectFirst("url").text() + "/download?") + .subtitleId(elem.selectFirst("id").text()) + .year(getText.apply(elem.selectFirst("year"))) + .imdb(getText.apply(elem.selectFirst("imdb"))) + .omdb(getText.apply(elem.selectFirst("omdb"))) + .build(); } private Language languageIdToLanguage(String languageId) { - return PODNAPISI_LANGS.entrySet().stream().filter(entry -> entry.getValue().equals(languageId)).map(Entry::getKey).findFirst().orElse(null); + return PODNAPISI_LANGS.entrySet() + .stream() + .filter(entry -> entry.getValue().equals(languageId)) + .map(Entry::getKey) + .findFirst() + .orElse(null); } - private static final Map PODNAPISI_LANGS = Collections - .unmodifiableMap(new EnumMap<>(Language.class) { - @Serial - private static final long serialVersionUID = 2950169212654074275L; - - { - put(Language.SLOVENIAN, "1"); - put(Language.ENGLISH, "2"); - put(Language.NORWEGIAN, "3"); - put(Language.KOREAN, "4"); - put(Language.GERMAN, "5"); - put(Language.ICELANDIC, "6"); - put(Language.CZECH, "7"); - put(Language.FRENCH, "8"); - put(Language.ITALIAN, "9"); - put(Language.BOSNIAN, "10"); - put(Language.JAPANESE, "11"); - put(Language.ARABIC, "12"); - put(Language.ROMANIAN, "13"); - put(Language.SPANISH, "14"); // es-ar Spanish (Argentina) - put(Language.HUNGARIAN, "15"); - put(Language.GREEK, "16"); - put(Language.CHINESE_SIMPLIFIED, "17"); - put(Language.LITHUANIAN, "19"); - put(Language.ESTONIAN, "20"); - put(Language.LATVIAN, "21"); - put(Language.HEBREW, "22"); - put(Language.DUTCH, "23"); - put(Language.DANISH, "24"); - put(Language.SWEDISH, "25"); - put(Language.POLISH, "26"); - put(Language.RUSSIAN, "27"); - put(Language.SPANISH, "28"); - put(Language.ALBANIAN, "29"); - put(Language.TURKISH, "30"); - put(Language.FINNISH, "31"); - put(Language.PORTUGUESE, "32"); - put(Language.BULGARIAN, "33"); - put(Language.MACEDONIAN, "35"); - put(Language.SLOVAK, "37"); - put(Language.CROATIAN, "38"); - put(Language.CHINESE_SIMPLIFIED, "40"); - put(Language.HINDI, "42"); - put(Language.THAI, "44"); - put(Language.UKRAINIAN, "46"); - put(Language.SERBIAN, "47"); - put(Language.PORTUGUESE, "48"); // Portuguese (Brazil) - put(Language.IRISH, "49"); - put(Language.BELARUSIAN, "50"); - put(Language.VIETNAMESE, "51"); - put(Language.PERSIAN, "52"); - put(Language.CATALAN, "53"); - put(Language.INDONESIAN, "54"); - - } - }); + private static final Map PODNAPISI_LANGS = + Collections.unmodifiableMap(new EnumMap<>(Language.class) { + @Serial private static final long serialVersionUID = 2950169212654074275L; + + { + put(Language.SLOVENIAN, "1"); + put(Language.ENGLISH, "2"); + put(Language.NORWEGIAN, "3"); + put(Language.KOREAN, "4"); + put(Language.GERMAN, "5"); + put(Language.ICELANDIC, "6"); + put(Language.CZECH, "7"); + put(Language.FRENCH, "8"); + put(Language.ITALIAN, "9"); + put(Language.BOSNIAN, "10"); + put(Language.JAPANESE, "11"); + put(Language.ARABIC, "12"); + put(Language.ROMANIAN, "13"); + put(Language.SPANISH, "14"); // es-ar Spanish (Argentina) + put(Language.HUNGARIAN, "15"); + put(Language.GREEK, "16"); + put(Language.CHINESE_SIMPLIFIED, "17"); + put(Language.LITHUANIAN, "19"); + put(Language.ESTONIAN, "20"); + put(Language.LATVIAN, "21"); + put(Language.HEBREW, "22"); + put(Language.DUTCH, "23"); + put(Language.DANISH, "24"); + put(Language.SWEDISH, "25"); + put(Language.POLISH, "26"); + put(Language.RUSSIAN, "27"); + put(Language.SPANISH, "28"); + put(Language.ALBANIAN, "29"); + put(Language.TURKISH, "30"); + put(Language.FINNISH, "31"); + put(Language.PORTUGUESE, "32"); + put(Language.BULGARIAN, "33"); + put(Language.MACEDONIAN, "35"); + put(Language.SLOVAK, "37"); + put(Language.CROATIAN, "38"); + put(Language.CHINESE_SIMPLIFIED, "40"); + put(Language.HINDI, "42"); + put(Language.THAI, "44"); + put(Language.UKRAINIAN, "46"); + put(Language.SERBIAN, "47"); + put(Language.PORTUGUESE, "48"); // Portuguese (Brazil) + put(Language.IRISH, "49"); + put(Language.BELARUSIAN, "50"); + put(Language.VIETNAMESE, "51"); + put(Language.PERSIAN, "52"); + put(Language.CATALAN, "53"); + put(Language.INDONESIAN, "54"); + + } + }); } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/exception/PodnapisiException.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/exception/PodnapisiException.java index 97d96dd9..578d08bc 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/exception/PodnapisiException.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/exception/PodnapisiException.java @@ -2,11 +2,10 @@ import java.io.Serial; +import lombok.experimental.StandardException; import org.lodder.subtools.sublibrary.exception.SubtitlesProviderException; import org.lodder.subtools.sublibrary.model.SubtitleSource; -import lombok.experimental.StandardException; - @StandardException public class PodnapisiException extends SubtitlesProviderException { @@ -15,6 +14,6 @@ public class PodnapisiException extends SubtitlesProviderException { @Override public String getSubtitleProvider() { - return SubtitleSource.PODNAPISI.getName(); + return SubtitleSource.PODNAPISI.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/model/PodnapisiSubtitleDescriptor.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/model/PodnapisiSubtitleDescriptor.java index ada3e7c0..80f3519f 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/model/PodnapisiSubtitleDescriptor.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/podnapisi/model/PodnapisiSubtitleDescriptor.java @@ -1,34 +1,29 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.podnapisi.model; -import org.lodder.subtools.sublibrary.Language; +import java.io.Serializable; import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import manifold.ext.props.rt.api.var; +import org.lodder.subtools.sublibrary.Language; /** - * Created by IntelliJ IDEA. - * User: lodder - * Date: 20/08/11 - * Time: 13:44 - * To change this template use Path | Settings | Path Templates. + * Created by IntelliJ IDEA. User: lodder Date: 20/08/11 Time: 13:44 To change this template use Path | Settings | Path + * Templates. */ -@Getter -@Setter @Builder -public class PodnapisiSubtitleDescriptor { +public class PodnapisiSubtitleDescriptor implements Serializable { - private String subtitleId; - private Language language; - private String uploaderName; - // private String uploaderUid; - // private String matchRanking; - private String releaseString; - // private String subtitleRating; - private String url; - private boolean hearingImpaired; - private boolean isInexact; - private String year; - private String omdb; - private String imdb; + @var String subtitleId; + @var Language language; + @var String uploaderName; + // @var String uploaderUid; + // @var String matchRanking; + @var String releaseString; + // @var String subtitleRating; + @var String url; + @var boolean hearingImpaired; + @var boolean isInexact; + @var String year; + @var String omdb; + @var String imdb; } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/SubsceneApi.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/SubsceneApi.java index 400b85c3..4a53cd49 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/SubsceneApi.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/SubsceneApi.java @@ -1,10 +1,9 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.subscene; +import static manifold.science.measures.TimeUnit.*; +import static org.lodder.subtools.sublibrary.util.Sleep.*; + import java.io.Serial; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.EnumMap; import java.util.List; @@ -13,11 +12,15 @@ import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; +import com.pivovarit.function.ThrowingSupplier; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; +import manifold.science.measures.Time; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleApi; @@ -26,40 +29,40 @@ import org.lodder.subtools.multisubdownloader.subtitleproviders.subscene.model.SubsceneSubtitleDescriptor; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; -import org.lodder.subtools.sublibrary.Manager.PageContentBuilderCacheTypeIntf; +import org.lodder.subtools.sublibrary.Manager.Retry; import org.lodder.subtools.sublibrary.ManagerException; -import org.lodder.subtools.sublibrary.data.Html; +import org.lodder.subtools.sublibrary.PageContentParams; +import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.data.ProviderSerieId; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import org.lodder.subtools.sublibrary.util.OptionalExtension; import org.lodder.subtools.sublibrary.util.http.HttpClientException; +import util.Utils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ OptionalExtension.class }) -public class SubsceneApi extends Html implements SubtitleApi { +public class SubsceneApi implements SubtitleApi { - private static final int RATEDURATION_SHORT = 1; // seconds - private static final int RATEDURATION_LONG = 5; // seconds + private static final Time RATE_DURATION_SHORT = 1 Second; + private static final Time RATE_DURATION_LONG = 5 Second; private static final String DOMAIN = "https://subscene.com"; - // private static final String SERIE_URL_PREFIX = DOMAIN + "/subtitles/"; private static final Pattern SERIE_NAME_PATTERN = Pattern.compile(".*? - ([A-Z][a-z]*) Season.*"); - private static final Predicate RETRY_PREDICATE = - exception -> (exception instanceof HttpClientException httpClientException - && (httpClientException.getResponseCode() == 409 || httpClientException.getResponseCode() == 429)) - || (exception instanceof ManagerException managerException && managerException.getMessage().contains("409 Conflict")); + private static final Predicate RETRY_PREDICATE = exception -> switch (exception) { + case HttpClientException httpClientException -> + httpClientException.responseCode == 409 || httpClientException.responseCode == 429; + case ManagerException managerException -> managerException.getMessage().contains("409 Conflict"); + default -> false; + }; + private final Manager manager; private int selectedLanguage; private boolean selectedIncludeHearingImpaired; - private LocalDateTime lastRequest = LocalDateTime.now(); + private Time lastRequest = Time.now(); + @val @override SubtitleSource subtitleSource = SubtitleSource.SUBSCENE; public SubsceneApi(Manager manager) { - super(manager, "Mozilla/5.25 Netscape/5.0 (Windows; I; Win95)"); +// super(manager, "Mozilla/5.25 Netscape/5.0 (Windows; I; Win95)"); + this.manager = manager; addCookie("ForeignOnly", "False"); } @@ -73,70 +76,81 @@ public Map> getSubSceneSerieNames(String serieName if (StringUtils.isBlank(serieName)) { return Map.of(); } - String url = DOMAIN + "/subtitles/searchbytitle?query=" + URLEncoder.encode(serieName, StandardCharsets.UTF_8); - Element searchResultElement = getJsoupDocument(url).selectFirst(".search-result"); - - return searchResultElement.select("h2").stream() - .map(titleElement -> Pair.of(titleElement.text(), titleElement.nextElementSibling().select("a").stream() - .map(aElem -> { - Matcher matcher = SERIE_NAME_PATTERN.matcher(aElem.text()); - int season = 0; - if (matcher.matches()) { - season = OrdinalNumber.optionalFromValue(matcher.group(1)).mapToInt(OrdinalNumber::getNumber).orElse(-1); - } - return new SubSceneSerieId(aElem.text(), aElem.attr("href"), season); - }).toList())) - .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); + String url = "$DOMAIN/subtitles/searchbytitle?query=" + serieName.urlEncode(); + return getJsoupDocument(url).selectFirstByClass("search-result").selectAllByTag("h2") + .stream() + .collect(Utils.mapCollector((map, titleElement) -> map.put(titleElement.text(), + titleElement.nextElementSibling().selectAllByTag("a").stream().map(elem -> { + Matcher matcher = SERIE_NAME_PATTERN.matcher(elem.text()); + int season = 0; + if (matcher.matches()) { + season = OrdinalNumber.optionalFromValue(matcher.group(1)) + .mapToInt(OrdinalNumber::getNumber).orElse(-1); + } + return new SubSceneSerieId(elem.text(), elem.attr("href"), season); + }).toList()))); } catch (Exception e) { throw new SubsceneException(e); } } - public List getSubtitles(SerieMapping providerSerieId, int season, int episode, Language language) - throws SubsceneException { - return getManager().valueBuilder().memoryCache() - .key("%s-subtitles-%s-%s-%s-%s".formatted(getSubtitleSource().getName(), providerSerieId.getProviderId(), season, episode, language)) - .collectionSupplier(SubsceneSubtitleDescriptor.class, () -> { - setLanguageWithCookie(language); - try { - return getJsoupDocument(DOMAIN + providerSerieId.getProviderId()) - .select("td.a1").stream().map(Element::parent) - .map(row -> new SubsceneSubtitleDescriptor() - .setLanguage(Language.fromValueOptional(row.select(".a1 span.l").text().trim()).orElse(null)) - .setUrlSupplier(() -> getDownloadUrl(DOMAIN + row.select(".a1 > a").attr("href").trim())) - .setName(row.select(".a1 span:not(.l)").text().trim()) - .setHearingImpaired(!row.select(".a41").isEmpty()) - .setUploader(row.select(".a5 > a").text().trim()) - .setComment(row.select(".a6 > div").text().trim())) - .filter(subDescriptor -> subDescriptor.getSeasonEpisode() != null - && subDescriptor.getSeasonEpisode().getEpisodes().stream().anyMatch(ep -> ep == episode)) - .toList(); - } catch (Exception e) { - throw new SubsceneException(e); - } - }).getCollection(); + public List getSubtitles(SerieMapping providerSerieId, int season, int episode, + Language language) throws SubsceneException { + return manager.getCache(CacheType.MEMORY, "%s-subtitles-%s-%s-%s-%s".formatted(subtitleSource.name, + providerSerieId.providerId, season, episode, language)) + .getCollection(() -> { + setLanguageWithCookie(language); + try { + return getJsoupDocument(DOMAIN + providerSerieId.providerId) + .selectAllByCss("td.a1") + .stream() + .map(el -> (Element) el.parent()) + .map(row -> { + Language lang = Language.fromValueOptional(row.selectAllByCss(".a1 span.l").text().trim()) + .orElse(null); + String name = row.selectAllByCss(".a1 span:not(.l)").text().trim(); + boolean hearingImpaired = row.selectFirstByCss(".a41") != null; + String uploader = row.selectFirstByCss(".a5 > a").text().trim(); + String comment = row.selectFirstByCss(".a6 > div").text().trim(); + ThrowingSupplier urlSupplier = () -> getDownloadUrl( + DOMAIN + row.selectAllByCss(".a1 > a").attr("href").trim()); + return new SubsceneSubtitleDescriptor(lang, name, hearingImpaired, uploader, comment, + urlSupplier); + }) + .filter(subDescriptor -> subDescriptor.seasonEpisode != null && + subDescriptor.seasonEpisode.containsEpisode(episode)) + .toList(); + } catch (Exception e) { + throw new SubsceneException(e); + } + }); } private String getDownloadUrl(String seriePageUrl) throws SubsceneException { try { - return DOMAIN + getJsoupDocument(seriePageUrl).selectFirst("#downloadButton").attr("href"); + String href = getJsoupDocument(seriePageUrl).selectFirstById("downloadButton").attr("href"); + if (StringUtils.isBlank(href)) { + throw new SubsceneException("href for $seriePageUrl is blank"); + } + return DOMAIN + href; } catch (ManagerException e) { throw new SubsceneException(e); } } private Document getJsoupDocument(String url) throws ManagerException { - while (ChronoUnit.SECONDS.between(lastRequest, LocalDateTime.now()) < RATEDURATION_SHORT) { - sleepSeconds(1); + Time timeToSleep = RATE_DURATION_SHORT - Time.now() + lastRequest; + if (timeToSleep.isPositive) { + sleep(timeToSleep); } - Document document = super.getHtml(url).retries(1).retryPredicate(RETRY_PREDICATE).retryWait(RATEDURATION_LONG).getAsJsoupDocument(); - lastRequest = LocalDateTime.now(); - return document; - } - @Override - public PageContentBuilderCacheTypeIntf getHtml(String url) { - throw new IllegalStateException("Should not be used, use getJsoupDocument"); + Document document = + manager.getAsJsoupDocument(PageContentParams.params( + url:url, + userAgent:"", + retry:new Retry(1, RETRY_PREDICATE, RATE_DURATION_LONG))); + lastRequest = Time.now(); + return document; } private void setLanguageWithCookie(Language language) { @@ -156,99 +170,94 @@ private void setIncludeHearingImpairedWithCookie(boolean includeHearingImpaired) } private void addCookie(String cookieName, String cookieValue) { - getManager().storeCookies("subscene.com", Map.of(cookieName, cookieValue)); + manager.storeCookies("subscene.com", Map.of(cookieName, cookieValue)); } - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.SUBSCENE; - } + private static final Map SUBSCENE_LANGS = + Collections.unmodifiableMap(new EnumMap<>(Language.class) { + @Serial private static final long serialVersionUID = 2950169212654074275L; - private static final Map SUBSCENE_LANGS = Collections.unmodifiableMap(new EnumMap<>(Language.class) { - @Serial - private static final long serialVersionUID = 2950169212654074275L; + { + put(Language.ARABIC, 2); + put(Language.BENGALI, 54); + put(Language.PORTUGUESE, 4); // BRAZILLIAN PORTUGUESE + put(Language.CHINESE_SIMPLIFIED, 7); + put(Language.CZECH, 9); + put(Language.DANISH, 10); + put(Language.DUTCH, 11); + put(Language.ENGLISH, 13); + // put(Language.FARSI / PERSIAN, 46); + put(Language.FINNISH, 17); + put(Language.FRENCH, 18); + put(Language.GERMAN, 19); + put(Language.GREEK, 21); + put(Language.HEBREW, 22); + put(Language.INDONESIAN, 44); + put(Language.ITALIAN, 26); + put(Language.KOREAN, 28); + put(Language.MALAY, 50); + put(Language.NORWEGIAN, 30); + put(Language.POLISH, 31); + put(Language.PORTUGUESE, 32); + put(Language.ROMANIAN, 33); + put(Language.SPANISH, 38); + put(Language.SWEDISH, 39); + put(Language.THAI, 40); + put(Language.TURKISH, 41); + put(Language.VIETNAMESE, 45); + put(Language.ALBANIAN, 1); + put(Language.ARMENIAN, 73); + put(Language.AZERBAIJANI, 55); + // put(Language.BASQUE, 74); + put(Language.BELARUSIAN, 68); + put(Language.CHINESE_SIMPLIFIED, 3); // BIG 5 CODE + put(Language.BOSNIAN, 60); + put(Language.BULGARIAN, 5); + // put(Language.BULGARIAN / ENGLISH, 6); + // put(Language.BURMESE, 61); + // put(Language.CAMBODIAN / KHMER, 79); + put(Language.CATALAN, 49); + put(Language.CROATIAN, 8); + // put(Language.DUTCH / ENGLISH, 12); + // put(Language.ENGLISH / GERMAN, 15); + // put(Language.ESPERANTO, 47); + put(Language.ESTONIAN, 16); + // put(Language.GEORGIAN, 62); + // put(Language.GREENLANDIC, 57); + put(Language.HINDI, 51); + put(Language.HUNGARIAN, 23); + // put(Language.HUNGARIAN / ENGLISH, 24); + put(Language.ICELANDIC, 25); + put(Language.JAPANESE, 27); + put(Language.KANNADA, 78); + // put(Language.KINYARWANDA, 81); + // put(Language.KURDISH, 52); + put(Language.LATVIAN, 29); + put(Language.LITHUANIAN, 43); + put(Language.MACEDONIAN, 48); + put(Language.MALAYALAM, 64); + // put(Language.MANIPURI, 65); + // put(Language.MONGOLIAN, 72); + // put(Language.NEPALI, 80); + // put(Language.PASHTO, 67); + // put(Language.PUNJABI, 66); + put(Language.RUSSIAN, 34); + put(Language.SERBIAN, 35); + put(Language.SINHALA, 58); + put(Language.SLOVAK, 36); + put(Language.SLOVENIAN, 37); + // put(Language.SOMALI, 70); + // put(Language.SUNDANESE, 76); + // put(Language.SWAHILI, 75); + put(Language.TAGALOG, 53); + put(Language.TAMIL, 59); + put(Language.TELUGU, 63); + put(Language.UKRAINIAN, 56); + // put(Language.URDU, 42); + // put(Language.YORUBA, 71); - { - put(Language.ARABIC, 2); - put(Language.BENGALI, 54); - put(Language.PORTUGUESE, 4); // BRAZILLIAN PORTUGUESE - put(Language.CHINESE_SIMPLIFIED, 7); - put(Language.CZECH, 9); - put(Language.DANISH, 10); - put(Language.DUTCH, 11); - put(Language.ENGLISH, 13); - // put(Language.FARSI / PERSIAN, 46); - put(Language.FINNISH, 17); - put(Language.FRENCH, 18); - put(Language.GERMAN, 19); - put(Language.GREEK, 21); - put(Language.HEBREW, 22); - put(Language.INDONESIAN, 44); - put(Language.ITALIAN, 26); - put(Language.KOREAN, 28); - put(Language.MALAY, 50); - put(Language.NORWEGIAN, 30); - put(Language.POLISH, 31); - put(Language.PORTUGUESE, 32); - put(Language.ROMANIAN, 33); - put(Language.SPANISH, 38); - put(Language.SWEDISH, 39); - put(Language.THAI, 40); - put(Language.TURKISH, 41); - put(Language.VIETNAMESE, 45); - put(Language.ALBANIAN, 1); - put(Language.ARMENIAN, 73); - put(Language.AZERBAIJANI, 55); - // put(Language.BASQUE, 74); - put(Language.BELARUSIAN, 68); - put(Language.CHINESE_SIMPLIFIED, 3); // BIG 5 CODE - put(Language.BOSNIAN, 60); - put(Language.BULGARIAN, 5); - // put(Language.BULGARIAN / ENGLISH, 6); - // put(Language.BURMESE, 61); - // put(Language.CAMBODIAN / KHMER, 79); - put(Language.CATALAN, 49); - put(Language.CROATIAN, 8); - // put(Language.DUTCH / ENGLISH, 12); - // put(Language.ENGLISH / GERMAN, 15); - // put(Language.ESPERANTO, 47); - put(Language.ESTONIAN, 16); - // put(Language.GEORGIAN, 62); - // put(Language.GREENLANDIC, 57); - put(Language.HINDI, 51); - put(Language.HUNGARIAN, 23); - // put(Language.HUNGARIAN / ENGLISH, 24); - put(Language.ICELANDIC, 25); - put(Language.JAPANESE, 27); - put(Language.KANNADA, 78); - // put(Language.KINYARWANDA, 81); - // put(Language.KURDISH, 52); - put(Language.LATVIAN, 29); - put(Language.LITHUANIAN, 43); - put(Language.MACEDONIAN, 48); - put(Language.MALAYALAM, 64); - // put(Language.MANIPURI, 65); - // put(Language.MONGOLIAN, 72); - // put(Language.NEPALI, 80); - // put(Language.PASHTO, 67); - // put(Language.PUNJABI, 66); - put(Language.RUSSIAN, 34); - put(Language.SERBIAN, 35); - put(Language.SINHALA, 58); - put(Language.SLOVAK, 36); - put(Language.SLOVENIAN, 37); - // put(Language.SOMALI, 70); - // put(Language.SUNDANESE, 76); - // put(Language.SWAHILI, 75); - put(Language.TAGALOG, 53); - put(Language.TAMIL, 59); - put(Language.TELUGU, 63); - put(Language.UKRAINIAN, 56); - // put(Language.URDU, 42); - // put(Language.YORUBA, 71); - - } - }); + } + }); @Getter @RequiredArgsConstructor @@ -284,15 +293,15 @@ private enum OrdinalNumber { TWENTY_EIGHTH(28, "Twenty-Eighth"), TWENTY_NINTH(29, "Twenty-Ninth"), THIRTIETH(30, "Thirtieth"), - THIRTHY_FIRST(31, "Thirty-First"), - THIRTHY_SECOND(32, "Thirty-Second"), - THIRTHY_THIRD(33, "Thirty-Third"), - THIRTHY_FOURTH(34, "Thirty-Fourth"), - THIRTHY_FIFTH(35, "Thirty-Fifth"), - THIRTHY_SIXTH(36, "Thirty-Sixth"), - THIRTHY_SEVENTH(37, "Thirty-Seventh"), - THIRTHY_EIGHTH(38, "Thirty-Eighth"), - THIRTHY_NINTH(39, "Thirty-Ninth"), + THIRTY_FIRST(31, "Thirty-First"), + THIRTY_SECOND(32, "Thirty-Second"), + THIRTY_THIRD(33, "Thirty-Third"), + THIRTY_FOURTH(34, "Thirty-Fourth"), + THIRTY_FIFTH(35, "Thirty-Fifth"), + THIRTY_SIXTH(36, "Thirty-Sixth"), + THIRTY_SEVENTH(37, "Thirty-Seventh"), + THIRTY_EIGHTH(38, "Thirty-Eighth"), + THIRTY_NINTH(39, "Thirty-Ninth"), FORTIETH(40, "Fortieth"), FORTY_FIRST(41, "Forty-First"), FORTY_SECOND(42, "Forty-Second"), @@ -359,8 +368,9 @@ private enum OrdinalNumber { private final String value; public static Optional optionalFromValue(String value) { - return Stream.of(OrdinalNumber.values()).filter(ordinalNumber -> StringUtils.equalsIgnoreCase(value, ordinalNumber.getValue())) - .findAny(); + return Stream.of(OrdinalNumber.values()) + .filter(ordinalNumber -> StringUtils.equalsIgnoreCase(value, ordinalNumber.getValue())) + .findAny(); } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/exception/SubsceneException.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/exception/SubsceneException.java index 4f3d97fd..ddb91b2c 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/exception/SubsceneException.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/exception/SubsceneException.java @@ -2,11 +2,10 @@ import java.io.Serial; +import lombok.experimental.StandardException; import org.lodder.subtools.sublibrary.exception.SubtitlesProviderException; import org.lodder.subtools.sublibrary.model.SubtitleSource; -import lombok.experimental.StandardException; - @StandardException public class SubsceneException extends SubtitlesProviderException { @@ -15,6 +14,6 @@ public class SubsceneException extends SubtitlesProviderException { @Override public String getSubtitleProvider() { - return SubtitleSource.SUBSCENE.getName(); + return SubtitleSource.SUBSCENE.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubSceneSerieId.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubSceneSerieId.java index 3ee49710..a452dc4d 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubSceneSerieId.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubSceneSerieId.java @@ -2,16 +2,15 @@ import java.io.Serial; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.sublibrary.data.ProviderSerieId; -import lombok.Getter; - -@Getter public class SubSceneSerieId extends ProviderSerieId { @Serial private static final long serialVersionUID = 5858875211782260667L; - private final int season; + + @val int season; public SubSceneSerieId(String name, String id, int season) { super(name, id); diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubsceneSubtitleDescriptor.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubsceneSubtitleDescriptor.java index a0026048..b8eba1ea 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubsceneSubtitleDescriptor.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/subscene/model/SubsceneSubtitleDescriptor.java @@ -1,36 +1,35 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.subscene.model; -import org.lodder.subtools.multisubdownloader.subtitleproviders.subscene.exception.SubsceneException; -import org.lodder.subtools.sublibrary.Language; -import org.lodder.subtools.sublibrary.model.SeasonEpisode; +import java.io.Serializable; import com.pivovarit.function.ThrowingSupplier; - import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; +import manifold.ext.props.rt.api.val; +import org.jspecify.annotations.Nullable; +import org.lodder.subtools.multisubdownloader.subtitleproviders.subscene.exception.SubsceneException; +import org.lodder.subtools.sublibrary.Language; +import org.lodder.subtools.sublibrary.model.SeasonEpisode; @EqualsAndHashCode -@Accessors(chain = true) -@NoArgsConstructor -@Getter -@Setter -public class SubsceneSubtitleDescriptor { +public class SubsceneSubtitleDescriptor implements Serializable { - private Language language; - private String name; - private boolean hearingImpaired; - private String uploader; - private String comment; - private SeasonEpisode seasonEpisode; - @EqualsAndHashCode.Exclude - private ThrowingSupplier urlSupplier; + @val @Nullable Language language; + @val @Nullable String name; + @val boolean hearingImpaired; + @val @Nullable String uploader; + @val @Nullable String comment; + @val @Nullable SeasonEpisode seasonEpisode; + @EqualsAndHashCode.Exclude @val ThrowingSupplier urlSupplier; - public SubsceneSubtitleDescriptor setName(String name) { + public SubsceneSubtitleDescriptor(@Nullable Language language, + @Nullable String name, boolean hearingImpaired,@Nullable String uploader, @Nullable String comment, + ThrowingSupplier urlSupplier) { + this.language = language; this.name = name; + this.hearingImpaired = hearingImpaired; + this.uploader = uploader; + this.comment = comment; this.seasonEpisode = SeasonEpisode.fromText(name).orElse(null); - return this; + this.urlSupplier = urlSupplier; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/JTVSubtitlesApi.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/JTVSubtitlesApi.java index 5e4ff030..4ddd2cca 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/JTVSubtitlesApi.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/JTVSubtitlesApi.java @@ -1,148 +1,168 @@ package org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; +import java.io.Serializable; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.BiPredicate; -import java.util.function.Function; +import manifold.ext.props.rt.api.override; +import manifold.ext.props.rt.api.val; import org.apache.commons.lang3.StringUtils; -import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleApi; import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.exception.TvSubtitlesException; -import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.model.TVsubtitlesSubtitleDescriptor; +import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.model.TVSubtitlesSubtitleDescriptor; +import org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.model.TVSubtitlesSubtitleDescriptor.TVSubtitlesSubtitleDescriptorBuilder; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.Manager; +import org.lodder.subtools.sublibrary.ManagerException; +import org.lodder.subtools.sublibrary.PageContentParams; import org.lodder.subtools.sublibrary.cache.CacheType; -import org.lodder.subtools.sublibrary.data.Html; +import org.lodder.subtools.sublibrary.control.VideoPatterns.Source; import org.lodder.subtools.sublibrary.data.ProviderSerieId; import org.lodder.subtools.sublibrary.model.SubtitleSource; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; -import org.lodder.subtools.sublibrary.util.OptionalExtension; -import org.lodder.subtools.sublibrary.util.StreamExtension; +import org.lodder.subtools.sublibrary.util.http.CookieManager; -import lombok.experimental.ExtensionMethod; - -@ExtensionMethod({ OptionalExtension.class, StreamExtension.class }) -public class JTVSubtitlesApi extends Html implements SubtitleApi { +public class JTVSubtitlesApi implements SubtitleApi { private static final String DOMAIN = "https://www.tvsubtitles.net"; private static final String SERIE_URL_PREFIX = DOMAIN + "/"; + private final Manager manager; + @val @override SubtitleSource subtitleSource = SubtitleSource.TVSUBTITLES; public JTVSubtitlesApi(Manager manager) { - super(manager); + this.manager = manager; } public List getUrisForSerieName(String serieName) throws TvSubtitlesException { try { - return getManager().postBuilder() - .url(DOMAIN + "/search.php") - .addData("qs", serieName) - .postAsJsoupDocument() - .select(".left_articles > ul > li a").stream() - .map(element -> new ProviderSerieId(element.text(), StringUtils.substringAfterLast(element.attr("href"), "/"))) - .toList(); + return manager.postBuilder("$DOMAIN/search.php") + .addData("qs", serieName) + .postAsJsoupDocument() + .select(".left_articles > ul > li a") + .stream() + .map(element -> new ProviderSerieId(element.text(), + StringUtils.substringAfterLast(element.attr("href"), "/"))) + .toList(); } catch (Exception e) { throw new TvSubtitlesException(e); } } - public Set getSubtitles(SerieMapping providerSerieId, int season, int episode, Language language) - throws TvSubtitlesException { - return getEpisodeUrl(SERIE_URL_PREFIX + providerSerieId.getProviderId(), season, episode) - .mapToObj(episodeUrl -> getSubtitles(episodeUrl, language)) - .orElseGet(Set::of); + public Set getSubtitles(SerieMapping providerSerieId, int season, int episode, + Language language) throws TvSubtitlesException { + // https://www.tvsubtitles.net/setlang.php?page=/tvshow-3219-2.html&setlang1=es + Set results = new HashSet<>(); + Optional episodeRow = getSeasonSubtitleInfo(providerSerieId.providerId, season, language).filter( + row -> row.isSameEpisode(season, episode)).findAny(); + if (episodeRow.isPresent()) { + for (String url : episodeRow.get().urls) { + results.addAll(getSubtitles(url)); + } + return results; + } + return results; } - private Set getSubtitles(String episodeUrl, Language language) throws TvSubtitlesException { - return getManager().valueBuilder() - .memoryCache() - .key("%s-subtitles-%s-%s".formatted(getSubtitleSource().name(), episodeUrl, language)) - .collectionSupplier(TVsubtitlesSubtitleDescriptor.class, () -> { - Set lSubtitles = new HashSet<>(); - try { - Document searchEpisodeDoc = - this.getHtml(episodeUrl.replace(".html", "-" + language.getLangCode() + ".html")).cacheType(CacheType.NONE) - .getAsJsoupDocument(); - Elements searchEpisodes = searchEpisodeDoc.select(".left_articles > a"); + private List getSeasonSubtitleInfo(String providerSerieId, int season, Language language) + throws TvSubtitlesException { + return manager.getCache(CacheType.MEMORY, + subtitleSource.name() + "subtitleInfo-$providerSerieId-$season-$language") + .getCollection(() -> { + try { + String languageCode = getLanguageCode(language); + CookieManager cookieManager = null; + if (languageCode != null) { + cookieManager = new CookieManager().storeCookie("tvsubtitles.net", "setlang", languageCode); + } +// DOMAIN + "/setlang.php?page=/$providerSerieId-$season.html&setlang1=$languageCode", + return manager.getAsJsoupDocument(PageContentParams.params( + DOMAIN + "/" + providerSerieId.replace(".html", "-$season.html"), + cookieManager:cookieManager)) + .select("#table4 table tr[bgcolor]") + .stream() + .filter(episodeRow -> StringUtils.isNotBlank(episodeRow.selectFirstByTag("td").text())) + .map(episodeRow -> { + Elements tds = episodeRow.selectAllByTag("td"); + String[] seasonEpisode = tds.get(0).text().split("x"); + List urls = + tds.get(3).select("a").stream().map(elem -> DOMAIN + "/" + elem.attr("href")).toList(); + return new EpisodeRow(Integer.parseInt(seasonEpisode[0]), + Integer.parseInt(seasonEpisode[1]), urls); + }).filter(episodeRow -> !episodeRow.urls.isEmpty()).toList(); + } catch (Exception e) { + throw new TvSubtitlesException(e); + } + }); + } - BiPredicate isRowWithText = (row, text) -> row.get(1).text().contains(text); - Function getRowValue = row -> row.get(2).text(); - for (Element ep : searchEpisodes) { - String url = ep.attr("href"); - if (url.contains("subtitle-")) { - Document subtitlePageDoc = this.getHtml(DOMAIN + url).cacheType(CacheType.NONE).getAsJsoupDocument(); - String filename = null, rip = null, title = null, author = null; - Elements subtitlePageTableDoc = subtitlePageDoc.getElementsByClass("subtitle1"); - if (subtitlePageTableDoc.size() == 1) { - for (Element item : subtitlePageTableDoc.get(0).getElementsByTag("tr")) { - Elements row = item.getElementsByTag("td"); - if (row.size() != 3) { - continue; - } - if (isRowWithText.test(row, "episode title:")) { - title = getRowValue.apply(row); - } else if (isRowWithText.test(row, "filename:")) { - filename = getRowValue.apply(row); - } else if (isRowWithText.test(row, "rip:")) { - rip = getRowValue.apply(row); - } else if (isRowWithText.test(row, "author:")) { - author = getRowValue.apply(row); - } - if (filename != null && rip != null) { - TVsubtitlesSubtitleDescriptor sub = TVsubtitlesSubtitleDescriptor.builder() - .filename(filename) - .url(DOMAIN + "/files/" + URLEncoder.encode( - filename.replace(title + ".", "").replace(".srt", ".zip").replace(" - ", "_"), - StandardCharsets.UTF_8)) - .rip(rip) - .author(author) - .build(); - lSubtitles.add(sub); - rip = null; - filename = null; - title = null; - author = null; - } + private List getSubtitles(String episodeUrl) + throws TvSubtitlesException { + return manager.getCache(CacheType.MEMORY, subtitleSource.name() + "subtitles-$episodeUrl") + .getCollection(() -> { + try { + return manager.getAsJsoupDocument(PageContentParams.url(episodeUrl)) + .select(".left_articles > div[class^='subtitle']") + .stream().map(subtitleElement -> { + TVSubtitlesSubtitleDescriptorBuilder subtitleBuilder = + TVSubtitlesSubtitleDescriptor.builder(); + for (Element titleElement : subtitleElement.select(".subtitle_grid > div > img[title]")) { + String value = + ((Element) titleElement.parent()).nextElementSibling().nextElementSibling().text(); + switch (titleElement.attr("title")) { + case "episode title" -> subtitleBuilder.title(value); + case "rip" -> subtitleBuilder.source(Source.fromValue(value)); + case "release" -> subtitleBuilder.releaseGroup(value); + case "filename" -> subtitleBuilder.filename(value); + default -> { } } } - } - return lSubtitles; - } catch (Exception e) { - throw new TvSubtitlesException(e); - } - }) - .getCollection(); + subtitleBuilder.url(DOMAIN + "/" + subtitleElement.select("a[href^='download-']") + .attr("href")); + return subtitleBuilder.build(); + }).toList(); + } catch (ManagerException e) { + throw new TvSubtitlesException(e); + } + }); } - private Optional getEpisodeUrl(String showUrl, int season, int episode) throws TvSubtitlesException { - return getManager().valueBuilder() - .memoryCache() - .key("%s-episodeUrl-%s-%s-%s".formatted(getSubtitleSource().name(), showUrl, season, episode)) - .optionalSupplier(() -> { - try { - String formattedSeasonEpisode = season + "x" + (episode < 10 ? "0" + episode : String.valueOf(episode)); - return getHtml(showUrl.replace(".html", "-" + season + ".html")) - .getAsJsoupDocument() - .getElementById("table5").getElementsByTag("tr").stream().skip(1) - .filter(row -> Optional.ofNullable(row.selectFirst("td")) - .map(element -> formattedSeasonEpisode.equals(element.text())) - .orElse(false)) - .map(element -> DOMAIN + "/" + element.select("td").get(1).selectFirst("a").attr("href")).findAny(); - } catch (Exception e) { - throw new TvSubtitlesException(e); - } - }).getOptional(); + private record EpisodeRow(int season, int episode, List urls) implements Serializable { + public boolean isSameEpisode(int season, int episode) { + return this.season == season && this.episode == episode; + } } - @Override - public SubtitleSource getSubtitleSource() { - return SubtitleSource.TVSUBTITLES; + private String getLanguageCode(Language language) { + return switch (language) { + case ENGLISH -> "en"; + case SPANISH -> "es"; + case FRENCH -> "fr"; + case GERMAN -> "de"; + case RUSSIAN -> "ru"; + case UKRAINIAN -> "ua"; + case ITALIAN -> "it"; + case GREEK -> "gr"; + case ARABIC -> "ar"; + case HUNGARIAN -> "hu"; + case POLISH -> "pl"; + case TURKISH -> "tr"; + case DUTCH -> "nl"; + case PORTUGUESE -> "pt"; + case SWEDISH -> "sv"; + case DANISH -> "da"; + case FINNISH -> "fi"; + case KOREAN -> "ko"; + case CHINESE_SIMPLIFIED, CHINESE_TRADITIONAL -> "cn"; + case JAPANESE -> "jp"; + case BULGARIAN -> "bg"; + case CZECH -> "cz"; + case ROMANIAN -> "ro"; + default -> null; + }; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/exception/TvSubtitlesException.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/exception/TvSubtitlesException.java index 4cf4df61..4d0e5cfc 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/exception/TvSubtitlesException.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/exception/TvSubtitlesException.java @@ -2,11 +2,10 @@ import java.io.Serial; +import lombok.experimental.StandardException; import org.lodder.subtools.sublibrary.exception.SubtitlesProviderException; import org.lodder.subtools.sublibrary.model.SubtitleSource; -import lombok.experimental.StandardException; - @StandardException public class TvSubtitlesException extends SubtitlesProviderException { @@ -15,6 +14,6 @@ public class TvSubtitlesException extends SubtitlesProviderException { @Override public String getSubtitleProvider() { - return SubtitleSource.TVSUBTITLES.getName(); + return SubtitleSource.TVSUBTITLES.name; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/model/TVSubtitlesSubtitleDescriptor.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/model/TVSubtitlesSubtitleDescriptor.java new file mode 100755 index 00000000..6d94b18d --- /dev/null +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/model/TVSubtitlesSubtitleDescriptor.java @@ -0,0 +1,21 @@ +package org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.model; + +import java.io.Serial; +import java.io.Serializable; + +import lombok.Builder; +import manifold.ext.props.rt.api.val; +import org.lodder.subtools.sublibrary.control.VideoPatterns.Source; + +@Builder +public class TVSubtitlesSubtitleDescriptor implements Serializable { + + @Serial + private static final long serialVersionUID = 6423513286301479905L; + @val String title; + @val String filename; + @val String url; + @val Source source; + @val String releaseGroup; + +} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/model/TVsubtitlesSubtitleDescriptor.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/model/TVsubtitlesSubtitleDescriptor.java deleted file mode 100755 index 679728e8..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/subtitleproviders/tvsubtitles/model/TVsubtitlesSubtitleDescriptor.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.lodder.subtools.multisubdownloader.subtitleproviders.tvsubtitles.model; - -import java.io.Serial; -import java.io.Serializable; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class TVsubtitlesSubtitleDescriptor implements Serializable { - - @Serial - private static final long serialVersionUID = 6423513286301479905L; - private final String filename; - private final String url; - private final String rip; - private final String author; - -} diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/CLIExtension.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/CLIExtension.java deleted file mode 100644 index bacc0eab..00000000 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/CLIExtension.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.lodder.subtools.multisubdownloader.util; - -import org.apache.commons.cli.CommandLine; -import org.lodder.subtools.multisubdownloader.cli.CliOption; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class CLIExtension { - public static boolean hasCliOption(CommandLine line, CliOption cliOption) { - return line.hasOption(cliOption.getValue()); - } - - public static String getCliOptionValue(CommandLine line, CliOption cliOption) { - return line.getOptionValue(cliOption.getValue()); - } -} \ No newline at end of file diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/ExportImport.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/ExportImport.java index a3cbcaeb..0e007890 100755 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/ExportImport.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/ExportImport.java @@ -1,6 +1,11 @@ package org.lodder.subtools.multisubdownloader.util; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import javax.swing.*; +import java.awt.*; import java.io.IOException; +import java.io.Serial; import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Path; @@ -10,34 +15,26 @@ import java.util.prefs.BackingStoreException; import java.util.prefs.InvalidPreferencesFormatException; -import javax.swing.JFileChooser; - -import org.lodder.subtools.multisubdownloader.Messages; +import com.google.gson.GsonBuilder; +import io.gsonfire.GsonFireBuilder; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.experimental.StandardException; +import lombok.experimental.UtilityClass; +import manifold.ext.props.rt.api.val; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.gui.dialog.MappingEpisodeNameDialog.MappingType; import org.lodder.subtools.multisubdownloader.settings.SettingsControl; import org.lodder.subtools.sublibrary.Manager; +import org.lodder.subtools.sublibrary.Manager.Value; import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.settings.model.SerieMapping; import org.lodder.subtools.sublibrary.userinteraction.UserInteractionHandler.MessageSeverity; -import org.lodder.subtools.sublibrary.util.StreamExtension; import org.lodder.subtools.sublibrary.util.filefilter.ExtensionFileFilter; import org.lodder.subtools.sublibrary.util.filefilter.JsonFileFilter; import org.lodder.subtools.sublibrary.util.filefilter.XmlFileFilter; -import com.google.gson.GsonBuilder; - -import java.awt.Component; - -import io.gsonfire.GsonFireBuilder; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.ExtensionMethod; -import lombok.experimental.StandardException; -import lombok.experimental.UtilityClass; - @RequiredArgsConstructor public class ExportImport { @@ -46,69 +43,63 @@ public class ExportImport { private final UserInteractionHandler userInteractionHandler; private final Component parent; - @RequiredArgsConstructor - @Getter + @AllArgsConstructor public enum SettingsType { - PREFERENCES(FileType.XML), - SERIE_MAPPING(FileType.JSON); + PREFERENCES(FileType.XML), SERIE_MAPPING(FileType.JSON); - private final FileType fileType; + @val FileType fileType; } - @RequiredArgsConstructor - @Getter + @AllArgsConstructor private enum FileType { - XML(".xml", new XmlFileFilter()), - JSON(".json", new JsonFileFilter()); + XML(".xml", new XmlFileFilter()), JSON(".json", new JsonFileFilter()); - private final String extension; - private final ExtensionFileFilter fileFilter; + @val String extension; + @val ExtensionFileFilter fileFilter; } public void importSettings(SettingsType listType) { - chooseFile(listType.getFileType()).ifPresent(path -> { + chooseFile(listType.fileType).ifPresent(path -> { if (Files.notExists(path)) { - userInteractionHandler.showMessage(Messages.getString("ImportExport.FileDoesNotExist"), - Messages.getString("ImportExport.ErrorWhileImporting"), MessageSeverity.WARNING); + userInteractionHandler.showMessage(getText("ImportExport.FileDoesNotExist"), + getText("ImportExport.ErrorWhileImporting"), MessageSeverity.WARNING); return; } try { switch (listType) { - case PREFERENCES -> ExportImportPreferences.importSettings(path, userInteractionHandler, settingsControl); - case SERIE_MAPPING -> ExportImportSerieMapping.importSettings(path, userInteractionHandler, manager); + case PREFERENCES -> + ExportImportPreferences.importSettings(path, userInteractionHandler, settingsControl); + case SERIE_MAPPING -> + ExportImportSerieMapping.importSettings(path, userInteractionHandler, manager); default -> throw new IllegalArgumentException("Unexpected value: " + listType); } } catch (CorruptSettingsFileException e) { - userInteractionHandler.showMessage( - Messages.getString("ImportExport.ImportCorruptFile"), - Messages.getString("ImportExport.ErrorWhileImporting"), - MessageSeverity.ERROR); + userInteractionHandler.showMessage(getText("ImportExport.ImportCorruptFile"), + getText("ImportExport.ErrorWhileImporting"), MessageSeverity.ERROR); } catch (Exception e) { - userInteractionHandler.showMessage(Messages.getString("ImportExport.ErrorWhileImporting"), - Messages.getString("ImportExport.ErrorWhileImporting"), MessageSeverity.ERROR); + userInteractionHandler.showMessage(getText("ImportExport.ErrorWhileImporting"), + getText("ImportExport.ErrorWhileImporting"), MessageSeverity.ERROR); } }); } public void exportSettings(SettingsType listType) { - chooseFile(listType.getFileType()) - .map(path -> path.toString().endsWith(listType.getFileType().getExtension()) ? path - : path.getParent().resolve(path.getFileName().toString() + listType.getFileType().getExtension())) - .ifPresent(path -> { - try { - switch (listType) { - case PREFERENCES -> ExportImportPreferences.exportSettings(path, settingsControl); - case SERIE_MAPPING -> ExportImportSerieMapping.exportSettings(path, manager); - default -> throw new IllegalArgumentException("Unexpected value: " + listType); - } - } catch (Exception e) { - userInteractionHandler.showMessage(Messages.getString("ImportExport.ErrorWhileExporting"), - Messages.getString("ImportExport.ErrorWhileExporting"), MessageSeverity.ERROR); + chooseFile(listType.fileType).map(path -> path.toString().endsWith(listType.fileType.extension) ? path : + path.getParent().resolve(path.getFileName().toString() + listType.fileType.extension)) + .ifPresent(path -> { + try { + switch (listType) { + case PREFERENCES -> ExportImportPreferences.exportSettings(path, settingsControl); + case SERIE_MAPPING -> ExportImportSerieMapping.exportSettings(path, manager); + default -> throw new IllegalArgumentException("Unexpected value: " + listType); } - }); + } catch (Exception e) { + userInteractionHandler.showMessage(getText("ImportExport.ErrorWhileExporting"), + getText("ImportExport.ErrorWhileExporting"), MessageSeverity.ERROR); + } + }); } - @ExtensionMethod({ StreamExtension.class }) @UtilityClass public static class ExportImportPreferences { @@ -116,8 +107,8 @@ public void exportSettings(Path path, SettingsControl settingsControl) throws Ex settingsControl.exportPreferences(path); } - public void importSettings(Path path, UserInteractionHandler userInteractionHandler, SettingsControl settingsControl) - throws CorruptSettingsFileException { + public void importSettings(Path path, UserInteractionHandler userInteractionHandler, + SettingsControl settingsControl) throws CorruptSettingsFileException { try { settingsControl.importPreferences(path); } catch (IOException | BackingStoreException | InvalidPreferencesFormatException e) { @@ -130,48 +121,55 @@ public void importSettings(Path path, UserInteractionHandler userInteractionHand public static class ExportImportSerieMapping { public void exportSettings(Path path, Manager manager) throws IOException { - List serieMappingsWithKey = Arrays.stream(MappingType.values()) - .map(MappingType::getSelectionForKeyPrefixList) - .flatMap(Arrays::stream) - .flatMap(selectionForKeyPrefix -> manager.valueBuilder() - .cacheType(CacheType.DISK) - .keyFilter(k -> k.startsWith(selectionForKeyPrefix.keyPrefix())) - .returnType(SerieMapping.class) - .getEntries().stream().map(pair -> new SeriemappingWithKey(pair.getKey(), pair.getValue()))) - .toList(); + List serieMappingsWithKey = MappingType.values().stream() + .map(MappingType::getSelectionForKeyPrefixList) + .flatMap(Arrays::stream) + .flatMap(selectionForKeyPrefix -> manager.getCache(CacheType.DISK, + k -> k.startsWith(selectionForKeyPrefix.keyPrefix())).getEntries(SerieMapping.class) + .stream() + .map(pair -> new SeriemappingWithKey(pair.getKey(), pair.getValue()))) + .toList(); Files.writeString(path, new GsonBuilder().setPrettyPrinting().create().toJson(serieMappingsWithKey)); } - public void importSettings(Path path, UserInteractionHandler userInteractionHandler, Manager manager) throws CorruptSettingsFileException { + public void importSettings(Path path, UserInteractionHandler userInteractionHandler, Manager manager) + throws CorruptSettingsFileException { SeriemappingWithKey[] serieMappings; try { - serieMappings = new GsonFireBuilder().enableHooks(SerieMapping.class).createGson().fromJson(Files.readString(path), - SeriemappingWithKey[].class); + serieMappings = new GsonFireBuilder().enableHooks(SerieMapping.class) + .createGson() + .fromJson(Files.readString(path), SeriemappingWithKey[].class); } catch (IOException e) { throw new CorruptSettingsFileException(e); } getImportStyle(userInteractionHandler).ifPresent(importStyle -> { if (importStyle == ImportStyle.OVERWRITE) { - Arrays.stream(MappingType.values()) - .map(MappingType::getSelectionForKeyPrefixList) - .flatMap(Arrays::stream) - .forEach(selectionForKeyPrefix -> manager.clearExpiredCacheBuilder() - .cacheType(CacheType.DISK) - .keyFilter((String k) -> k.startsWith(selectionForKeyPrefix.keyPrefix())) - .clear()); + MappingType.values().stream() + .map(MappingType::getSelectionForKeyPrefixList) + .flatMap(Arrays::stream) + .forEach(selectionForKeyPrefix -> + manager.getCache(CacheType.DISK, k -> k.startsWith(selectionForKeyPrefix.keyPrefix)) + .clearExpiredCache()); } - Arrays.stream(serieMappings).forEach(serieMapping -> manager.valueBuilder() - .cacheType(CacheType.DISK) - .key(serieMapping.key) - .value(serieMapping.serieMapping) - .store()); + serieMappings.forEach(serieMapping -> + manager.getCache(CacheType.DISK, serieMapping.key).store(Value.of(serieMapping.serieMapping))); }); } + private static Optional getImportStyle(UserInteractionHandler userInteractionHandler) { + return userInteractionHandler.selectFromList(Arrays.asList(ImportStyle.values()), + getText("ImportExport.OverwriteOrAdd"), + getText("ImportExport.OverwriteOrAddTitle"), + option -> switch (option) { + case OVERWRITE -> getText("ImportExport.Overwrite"); + case APPEND -> getText("ImportExport.Add"); + }); + } + @AllArgsConstructor @Data private static class SeriemappingWithKey implements Serializable { - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private String key; private SerieMapping serieMapping; } @@ -181,7 +179,7 @@ private Optional chooseFile(FileType fileType) { JFileChooser fc = new JFileChooser(); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setAcceptAllFileFilterUsed(false); - fc.setFileFilter(fileType.getFileFilter()); + fc.setFileFilter(fileType.fileFilter); int returnVal = fc.showOpenDialog(parent); if (returnVal == JFileChooser.APPROVE_OPTION) { return Optional.of(fc.getSelectedFile().toPath()); @@ -190,20 +188,11 @@ private Optional chooseFile(FileType fileType) { } } - private static Optional getImportStyle(UserInteractionHandler userInteractionHandler) { - return userInteractionHandler.choice(Arrays.asList(ImportStyle.values()), - Messages.getString("ImportExport.OverwriteOrAdd"), Messages.getString("ImportExport.OverwriteOrAddTitle"), - option -> switch (option) { - case OVERWRITE -> Messages.getString("ImportExport.Overwrite"); - case APPEND -> Messages.getString("ImportExport.Add"); - }); - } - private enum ImportStyle { OVERWRITE, APPEND } @StandardException - private static class CorruptSettingsFileException extends Exception { + public static class CorruptSettingsFileException extends Exception { } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/PropertiesReader.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/PropertiesReader.java index 95fa9971..1cea32d7 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/PropertiesReader.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/util/PropertiesReader.java @@ -4,10 +4,13 @@ import java.io.InputStream; import java.util.Properties; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; + public class PropertiesReader { - private final Properties properties; private static PropertiesReader propertiesReaderInstance; + private final Properties properties; public PropertiesReader() throws IOException { try (InputStream is = getClass().getClassLoader().getResourceAsStream("properties-from-pom.properties")) { @@ -27,7 +30,14 @@ private static PropertiesReader getPropertiesReader() { return propertiesReaderInstance; } - public static String getProperty(String propertyName) { - return PropertiesReader.getPropertiesReader().properties.getProperty(propertyName); + public static String getProperty(PomProperty property) { + return PropertiesReader.getPropertiesReader().properties.getProperty(property.value); + } + + @AllArgsConstructor + public enum PomProperty { + BUILD_TIMESTAMP("build.timestamp"); + + @val String value; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchManager.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchManager.java index d999a26a..71ee122a 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchManager.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchManager.java @@ -1,5 +1,7 @@ package org.lodder.subtools.multisubdownloader.workers; +import static manifold.ext.props.rt.api.PropOption.*; + import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; @@ -8,6 +10,9 @@ import java.util.Map.Entry; import java.util.Queue; +import manifold.ext.props.rt.api.set; +import manifold.ext.props.rt.api.val; +import manifold.ext.props.rt.api.var; import org.lodder.subtools.multisubdownloader.UserInteractionHandler; import org.lodder.subtools.multisubdownloader.gui.dialog.Cancelable; import org.lodder.subtools.multisubdownloader.lib.control.subtitles.sorting.ScoreCalculator; @@ -19,63 +24,33 @@ import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; - -@RequiredArgsConstructor public class SearchManager implements Cancelable { - public interface SearchManagerLanguage { - SearchManagerProgressListener language(@NonNull Language language); - } - - public interface SearchManagerProgressListener { - SearchManagerUserInteractionHandler progressListener(@NonNull SearchProgressListener progressListener); - } - - public interface SearchManagerUserInteractionHandler { - SearchManagerOnFound userInteractionHandler(@NonNull UserInteractionHandler userInteractionHandler); - } - - public interface SearchManagerOnFound { - SearchManager onFound(@NonNull SearchHandler onFound); - } - - @Setter - @Accessors(fluent = true) - public static class SearchManagerBuilder - implements SearchManagerOnFound, SearchManagerUserInteractionHandler, SearchManagerProgressListener, SearchManagerLanguage { - private Settings settings; - private Language language; - private SearchProgressListener progressListener; - private UserInteractionHandler userInteractionHandler; - - @Override - public SearchManager onFound(SearchHandler onFound) { - return new SearchManager(settings, onFound, language, progressListener, userInteractionHandler); - } - } - private final Map> queue = new HashMap<>(); private final Map workers = new HashMap<>(); private final Map scoreCalculators = new HashMap<>(); private final Settings settings; - @Getter - private int progress = 0; + @var @set(Private) int progress = 0; private int totalJobs; private final SearchHandler onFound; - @Getter - private final Language language; + @val Language language; private final SearchProgressListener progressListener; - @Getter - private final UserInteractionHandler userInteractionHandler; + @val UserInteractionHandler userInteractionHandler; + + public SearchManager(Settings settings, Language language, SearchProgressListener progressListener, + UserInteractionHandler userInteractionHandler, SearchHandler onFound) { + this.settings = settings; + this.language = language; + this.progressListener = progressListener; + this.userInteractionHandler = userInteractionHandler; + this.onFound = onFound; + } - public static SearchManagerLanguage createWithSettings(Settings settings) { - return new SearchManagerBuilder().settings(settings); + public void reset() { + queue.clear(); + workers.clear(); + scoreCalculators.clear(); } public void addProvider(SubtitleProvider provider) { @@ -87,10 +62,10 @@ public void addProvider(SubtitleProvider provider) { } public void addRelease(Release release) { - this.queue.forEach((key, value) -> queue.get(key).add(release)); + this.queue.forEach((key, _) -> queue.get(key).add(release)); /* Create a scoreCalculator so we can score subtitles for this release */ // TODO: extract to factory - SortWeight weights = new SortWeight(release, this.settings.getSortWeights()); + SortWeight weights = new SortWeight(release, this.settings.sortWeights); this.scoreCalculators.put(release, new ScoreCalculator(weights)); } @@ -106,17 +81,17 @@ public void start() { } public void onCompleted(SearchWorker worker) { - Release release = worker.getRelease(); - List subtitles = new ArrayList<>(worker.getSubtitles()); + Release release = worker.release; + List subtitles = new ArrayList<>(worker.subtitles); /* set the score of the found subtitles */ ScoreCalculator calculator = this.scoreCalculators.get(release); - subtitles.forEach(subtitle -> subtitle.setScore(calculator.calculate(subtitle))); + subtitles.forEach(subtitle -> subtitle.score = calculator.calculate(subtitle)); synchronized (this) { calculateProgress(); /* Tell the progress listener our total progress */ - this.progressListener.progress(this.getProgress()); + this.progressListener.progress(this.progress); } onFound.onFound(release, subtitles); @@ -155,7 +130,7 @@ private int jobsLeft() { for (Entry> provider : this.queue.entrySet()) { jobsLeft += provider.getValue().size(); SearchWorker worker = this.workers.get(provider.getKey()); - if (worker.isAlive() && worker.isBusy()) { + if (worker.isAlive() && worker.busy) { jobsLeft++; } } diff --git a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchWorker.java b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchWorker.java index eb1ceb64..0809689b 100644 --- a/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchWorker.java +++ b/MultiSubDownloader/src/main/java/org/lodder/subtools/multisubdownloader/workers/SearchWorker.java @@ -1,7 +1,12 @@ package org.lodder.subtools.multisubdownloader.workers; +import static manifold.ext.props.rt.api.PropOption.*; + import java.util.Set; +import manifold.ext.props.rt.api.set; +import manifold.ext.props.rt.api.val; +import manifold.ext.props.rt.api.var; import org.lodder.subtools.multisubdownloader.subtitleproviders.SubtitleProvider; import org.lodder.subtools.sublibrary.Language; import org.lodder.subtools.sublibrary.exception.SubtitlesProviderInitException; @@ -12,13 +17,14 @@ public class SearchWorker extends Thread { - protected final SubtitleProvider provider; + private static final Logger LOGGER = LoggerFactory.getLogger(SearchWorker.class); + + @val SubtitleProvider provider; private final SearchManager scheduler; - private boolean busy = false; + @var @set(Private) boolean busy = false; private boolean isInterrupted = false; - private Release release; - private Set subtitles; - private static final Logger LOGGER = LoggerFactory.getLogger(SearchWorker.class); + @var @set(Private) Release release; + @var @set(Private) Set subtitles; public SearchWorker(SubtitleProvider provider, SearchManager scheduler) { this.provider = provider; @@ -27,7 +33,7 @@ public SearchWorker(SubtitleProvider provider, SearchManager scheduler) { @Override public void run() { - Language language = this.scheduler.getLanguage(); + Language language = this.scheduler.language; this.busy = false; try { while (!this.isInterrupted()) { @@ -39,7 +45,7 @@ public void run() { break; } this.release = release; - LOGGER.debug("[Search] {} searching {} ", this.provider.getName(), release); + LOGGER.debug("[Search] {} searching {} ", this.provider.name, release); Set subtitles = this.provider.search(release, language); @@ -47,14 +53,15 @@ public void run() { this.subtitles = Set.copyOf(subtitles); this.busy = false; - LOGGER.debug("[Search] {} found {} subtitles for {} ", this.provider.getName(), subtitles.size(), release); + LOGGER.debug("[Search] {} found {} subtitles for {} ", this.provider.name, subtitles.size(), + release); if (!this.isInterrupted()) { this.scheduler.onCompleted(this); } } } catch (SubtitlesProviderInitException e) { - LOGGER.error("API %s INIT (%s)".formatted(e.getProviderName(), e.getMessage()), e); + LOGGER.error("API %s INIT (%s)".formatted(e.providerName, e.getMessage()), e); } } @@ -69,20 +76,4 @@ public void interrupt() { this.isInterrupted = true; super.interrupt(); } - - public boolean isBusy() { - return busy; - } - - public Release getRelease() { - return release; - } - - public Set getSubtitles() { - return subtitles; - } - - public SubtitleProvider getProvider() { - return provider; - } } diff --git a/MultiSubDownloader/src/main/resources/gestdown/gestdown-swagger.json b/MultiSubDownloader/src/main/resources/gestdown/gestdown-swagger.json index 9cd2fa77..bceb0226 100644 --- a/MultiSubDownloader/src/main/resources/gestdown/gestdown-swagger.json +++ b/MultiSubDownloader/src/main/resources/gestdown/gestdown-swagger.json @@ -3,7 +3,7 @@ "info": { "title": "Gestdown: Addicted Proxy", "description": "Provide a full api to search and download subtitles from Addic7ed website.", - "version": "4.22.5" + "version": "4.29.11" }, "servers": [ { @@ -46,7 +46,7 @@ "required": true, "schema": { "maximum": 50, - "minimum": 0, + "minimum": 1, "type": "integer", "format": "int32" } @@ -125,6 +125,60 @@ } } }, + "/media/{showId}/episodes/{language}": { + "get": { + "tags": [ + "Media" + ], + "summary": "Get the show details with the last season and episodes", + "parameters": [ + { + "name": "showId", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "language", + "in": "path", + "description": "", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaDetailsWithEpisodeAndSubtitlesDto" + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Not Found" + } + } + } + }, "/stats/downloads/{top}": { "get": { "tags": [ @@ -970,6 +1024,33 @@ }, "additionalProperties": false }, + "MediaDetailsWithEpisodeAndSubtitlesDto": { + "required": [ + "details", + "episodeWithSubtitles" + ], + "type": "object", + "properties": { + "details": { + "$ref": "#/components/schemas/MediaDetailsDto" + }, + "episodeWithSubtitles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EpisodeWithSubtitlesDto" + }, + "description": "" + }, + "lastSeasonNumber": { + "type": "integer", + "description": "", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Represent a media with its details and episodes with subtitles" + }, "MediaType": { "enum": [ "Show", diff --git a/MultiSubDownloader/src/main/resources/opensubtitles/open-api.json b/MultiSubDownloader/src/main/resources/opensubtitles/open-api.json new file mode 100644 index 00000000..9e0483ae --- /dev/null +++ b/MultiSubDownloader/src/main/resources/opensubtitles/open-api.json @@ -0,0 +1,5842 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "OpenSubtitles API", + "description": "Explore subtitles API here", + "contact": { + "name": "OpenSubtitles API Support", + "url": "https://www.opensubtitles.com/en/contact/", + "email": "support@opensubtitles.org" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "1.0.1", + "termsOfService": "https://www.opensubtitles.com/en/tos/" + }, + "servers": [ + { + "url": "https://api.opensubtitles.com/api/v1", + "description": "Default server" + }, + { + "url": "https://vip-api.opensubtitles.com/api/v1", + "description": "VIP server" + } + ], + "tags": [ + { + "name": "Infos", + "description": "General API infos" + }, + { + "name": "Authentication", + "description": "Authentification of user" + }, + { + "name": "Discover", + "description": "Discover popular, latest and most downloaded subtitles" + }, + { + "name": "Download", + "description": "Download of subtitles" + }, + { + "name": "Subtitles", + "description": "Subtitles search for a specific release" + }, + { + "name": "Features", + "description": "Search for feature" + }, + { + "name": "Utilities", + "description": "Various utilities" + } + ], + "paths": { + "/login": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Login", + "description": "Create a token to authenticate a user. If response code is ```401 Unathorized``` stop sending further requests with the same credentials, login is \"expensive\" operation.\n\nRequest rate limit is 1 request per 1 second.\n\nFurther API requests must continue on returned ```base_url``` host, which can have different cache time for search results and different request rate limits. If ```base_url``` equals ```vip-api.opensubtitles.com``` make sure you always send with every request JWT token (if available), otherwise request might fail with 4xx code.", + "operationId": "login", + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "description": "application/json", + "required": true, + "schema": { + "type": "string", + "default": "application/json" + } + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "x-examples": { + "example": { + "username": "", + "password": "" + } + }, + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ] + } + } + }, + "required": false + }, + "responses": { + "200": { + "description": "Create session and token", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "object", + "required": [ + "allowed_translations", + "allowed_downloads", + "level", + "user_id", + "ext_installed", + "vip" + ], + "properties": { + "allowed_translations": { + "type": "number" + }, + "allowed_downloads": { + "type": "number" + }, + "level": { + "minLength": 1, + "type": "string", + "example": "VIP Member" + }, + "user_id": { + "type": "number" + }, + "ext_installed": { + "type": "boolean" + }, + "vip": { + "type": "boolean" + } + } + }, + "base_url": { + "type": "string", + "format": "hostname", + "default": "api.opensubtitles.com", + "example": "api.opensubtitles.com", + "enum": [ + "api.opensubtitles.com", + "vip-api.opensubtitles.com" + ] + }, + "token": { + "minLength": 1, + "type": "string" + }, + "status": { + "type": "number" + } + }, + "required": [ + "user", + "base_url", + "token", + "status" + ] + }, + "example": { + "user": { + "allowed_downloads": 100, + "allowed_translations": 5, + "level": "Sub leecher", + "user_id": 66, + "ext_installed": false, + "vip": false + }, + "base_url": "api.opensubtitles.com", + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJEOU5aaWUyVjhWOU1hTnJVZWVvcEEwWUNoWEt6Wkx3NiIsImV4cCI6MTYwNDM1ODAwMH0.sMibjAFnkcs-HJ4zhdCwBeGrZ_UvzMbgl5NxYV2uALM", + "status": 200 + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Api-Key": [] + } + ] + } + }, + "/logout": { + "delete": { + "tags": [ + "Authentication" + ], + "summary": "Logout", + "description": "Destroy a user token to end a session. Bearer token is required for this endpoint.", + "operationId": "logout", + "responses": { + "200": { + "description": "Destroy session and current token", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "message": "token successfully destroyed", + "status": 200 + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Bearer": [] + }, + { + "Api-Key": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ] + } + }, + "/infos/formats": { + "get": { + "tags": [ + "Infos" + ], + "summary": "Subtitle Formats", + "description": "List subtitle formats recognized by the API ", + "operationId": "formats", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "description": "", + "type": "object", + "x-examples": { + "example-1": { + "data": { + "output_formats": [ + "srt", + "sub", + "mpl", + "webvtt", + "dfxp", + "txt" + ] + } + } + }, + "properties": { + "data": { + "type": "object", + "required": [ + "output_formats" + ], + "properties": { + "output_formats": { + "type": "array", + "items": {} + } + } + } + }, + "required": [ + "data" + ] + }, + "examples": { + "example": { + "value": { + "data": { + "output_formats": [ + "srt", + "sub", + "mpl", + "webvtt", + "dfxp", + "txt" + ] + } + } + } + } + }, + "example": { + "example": { + "data": { + "output_formats": [ + "srt", + "sub", + "mpl", + "webvtt", + "dfxp", + "txt" + ] + }, + "value": { + "data": { + "output_formats": [ + "srt", + "sub", + "mpl", + "webvtt", + "dfxp", + "txt" + ] + } + } + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Api-Key": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ] + } + }, + "/infos/languages": { + "get": { + "tags": [ + "Infos" + ], + "summary": "Languages", + "description": "Get the languages information", + "operationId": "languages", + "responses": { + "200": { + "description": "Get the languages table containing the codes and names used through the API", + "content": { + "application/json": { + "schema": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "minItems": 1, + "uniqueItems": true, + "type": "array", + "items": { + "required": [ + "language_code", + "language_name" + ], + "type": "object", + "properties": { + "language_code": { + "minLength": 1, + "type": "string" + }, + "language_name": { + "minLength": 1, + "type": "string" + } + } + } + } + }, + "description": "" + }, + "example": { + "data": [ + { + "language_code": "af", + "language_name": "Afrikaans" + }, + { + "language_code": "sq", + "language_name": "Albanian" + }, + { + "language_code": "ar", + "language_name": "Arabic" + }, + { + "language_code": "an", + "language_name": "Aragonese" + }, + { + "language_code": "hy", + "language_name": "Armenian" + }, + { + "language_code": "at", + "language_name": "Asturian" + }, + { + "language_code": "eu", + "language_name": "Basque" + }, + { + "language_code": "be", + "language_name": "Belarusian" + }, + { + "language_code": "bn", + "language_name": "Bengali" + }, + { + "language_code": "bs", + "language_name": "Bosnian" + }, + { + "language_code": "br", + "language_name": "Breton" + }, + { + "language_code": "bg", + "language_name": "Bulgarian" + }, + { + "language_code": "my", + "language_name": "Burmese" + }, + { + "language_code": "ca", + "language_name": "Catalan" + }, + { + "language_code": "zh-cn", + "language_name": "Chinese (simplified)" + }, + { + "language_code": "cs", + "language_name": "Czech" + }, + { + "language_code": "da", + "language_name": "Danish" + }, + { + "language_code": "nl", + "language_name": "Dutch" + }, + { + "language_code": "en", + "language_name": "English" + }, + { + "language_code": "eo", + "language_name": "Esperanto" + }, + { + "language_code": "et", + "language_name": "Estonian" + }, + { + "language_code": "fi", + "language_name": "Finnish" + }, + { + "language_code": "fr", + "language_name": "French" + }, + { + "language_code": "ka", + "language_name": "Georgian" + }, + { + "language_code": "de", + "language_name": "German" + }, + { + "language_code": "gl", + "language_name": "Galician" + }, + { + "language_code": "el", + "language_name": "Greek" + }, + { + "language_code": "he", + "language_name": "Hebrew" + }, + { + "language_code": "hi", + "language_name": "Hindi" + }, + { + "language_code": "hr", + "language_name": "Croatian" + }, + { + "language_code": "hu", + "language_name": "Hungarian" + }, + { + "language_code": "is", + "language_name": "Icelandic" + }, + { + "language_code": "id", + "language_name": "Indonesian" + }, + { + "language_code": "it", + "language_name": "Italian" + }, + { + "language_code": "ja", + "language_name": "Japanese" + }, + { + "language_code": "kk", + "language_name": "Kazakh" + }, + { + "language_code": "km", + "language_name": "Khmer" + }, + { + "language_code": "ko", + "language_name": "Korean" + }, + { + "language_code": "lv", + "language_name": "Latvian" + }, + { + "language_code": "lt", + "language_name": "Lithuanian" + }, + { + "language_code": "lb", + "language_name": "Luxembourgish" + }, + { + "language_code": "mk", + "language_name": "Macedonian" + }, + { + "language_code": "ml", + "language_name": "Malayalam" + }, + { + "language_code": "ms", + "language_name": "Malay" + }, + { + "language_code": "ma", + "language_name": "Manipuri" + }, + { + "language_code": "mn", + "language_name": "Mongolian" + }, + { + "language_code": "no", + "language_name": "Norwegian" + }, + { + "language_code": "oc", + "language_name": "Occitan" + }, + { + "language_code": "fa", + "language_name": "Persian" + }, + { + "language_code": "pl", + "language_name": "Polish" + }, + { + "language_code": "pt-pt", + "language_name": "Portuguese" + }, + { + "language_code": "ru", + "language_name": "Russian" + }, + { + "language_code": "sr", + "language_name": "Serbian" + }, + { + "language_code": "si", + "language_name": "Sinhalese" + }, + { + "language_code": "sk", + "language_name": "Slovak" + }, + { + "language_code": "sl", + "language_name": "Slovenian" + }, + { + "language_code": "es", + "language_name": "Spanish" + }, + { + "language_code": "sw", + "language_name": "Swahili" + }, + { + "language_code": "sv", + "language_name": "Swedish" + }, + { + "language_code": "sy", + "language_name": "Syriac" + }, + { + "language_code": "ta", + "language_name": "Tamil" + }, + { + "language_code": "te", + "language_name": "Telugu" + }, + { + "language_code": "tl", + "language_name": "Tagalog" + }, + { + "language_code": "th", + "language_name": "Thai" + }, + { + "language_code": "tr", + "language_name": "Turkish" + }, + { + "language_code": "uk", + "language_name": "Ukrainian" + }, + { + "language_code": "ur", + "language_name": "Urdu" + }, + { + "language_code": "uz", + "language_name": "Uzbek" + }, + { + "language_code": "vi", + "language_name": "Vietnamese" + }, + { + "language_code": "ro", + "language_name": "Romanian" + }, + { + "language_code": "pt-br", + "language_name": "Portuguese (Brazilian)" + }, + { + "language_code": "me", + "language_name": "Montenegrin" + }, + { + "language_code": "zh-tw", + "language_name": "Chinese (traditional)" + }, + { + "language_code": "ze", + "language_name": "Chinese bilingual" + } + ] + } + }, + "example": { + "example": { + "data": [ + { + "language_code": "af", + "language_name": "Afrikaans" + }, + { + "language_code": "sq", + "language_name": "Albanian" + }, + { + "language_code": "ar", + "language_name": "Arabic" + }, + { + "language_code": "an", + "language_name": "Aragonese" + }, + { + "language_code": "hy", + "language_name": "Armenian" + }, + { + "language_code": "at", + "language_name": "Asturian" + }, + { + "language_code": "eu", + "language_name": "Basque" + }, + { + "language_code": "be", + "language_name": "Belarusian" + }, + { + "language_code": "bn", + "language_name": "Bengali" + }, + { + "language_code": "bs", + "language_name": "Bosnian" + }, + { + "language_code": "br", + "language_name": "Breton" + }, + { + "language_code": "bg", + "language_name": "Bulgarian" + }, + { + "language_code": "my", + "language_name": "Burmese" + }, + { + "language_code": "ca", + "language_name": "Catalan" + }, + { + "language_code": "zh-cn", + "language_name": "Chinese (simplified)" + }, + { + "language_code": "cs", + "language_name": "Czech" + }, + { + "language_code": "da", + "language_name": "Danish" + }, + { + "language_code": "nl", + "language_name": "Dutch" + }, + { + "language_code": "en", + "language_name": "English" + }, + { + "language_code": "eo", + "language_name": "Esperanto" + }, + { + "language_code": "et", + "language_name": "Estonian" + }, + { + "language_code": "fi", + "language_name": "Finnish" + }, + { + "language_code": "fr", + "language_name": "French" + }, + { + "language_code": "ka", + "language_name": "Georgian" + }, + { + "language_code": "de", + "language_name": "German" + }, + { + "language_code": "gl", + "language_name": "Galician" + }, + { + "language_code": "el", + "language_name": "Greek" + }, + { + "language_code": "he", + "language_name": "Hebrew" + }, + { + "language_code": "hi", + "language_name": "Hindi" + }, + { + "language_code": "hr", + "language_name": "Croatian" + }, + { + "language_code": "hu", + "language_name": "Hungarian" + }, + { + "language_code": "is", + "language_name": "Icelandic" + }, + { + "language_code": "id", + "language_name": "Indonesian" + }, + { + "language_code": "it", + "language_name": "Italian" + }, + { + "language_code": "ja", + "language_name": "Japanese" + }, + { + "language_code": "kk", + "language_name": "Kazakh" + }, + { + "language_code": "km", + "language_name": "Khmer" + }, + { + "language_code": "ko", + "language_name": "Korean" + }, + { + "language_code": "lv", + "language_name": "Latvian" + }, + { + "language_code": "lt", + "language_name": "Lithuanian" + }, + { + "language_code": "lb", + "language_name": "Luxembourgish" + }, + { + "language_code": "mk", + "language_name": "Macedonian" + }, + { + "language_code": "ml", + "language_name": "Malayalam" + }, + { + "language_code": "ms", + "language_name": "Malay" + }, + { + "language_code": "ma", + "language_name": "Manipuri" + }, + { + "language_code": "mn", + "language_name": "Mongolian" + }, + { + "language_code": "no", + "language_name": "Norwegian" + }, + { + "language_code": "oc", + "language_name": "Occitan" + }, + { + "language_code": "fa", + "language_name": "Persian" + }, + { + "language_code": "pl", + "language_name": "Polish" + }, + { + "language_code": "pt-pt", + "language_name": "Portuguese" + }, + { + "language_code": "ru", + "language_name": "Russian" + }, + { + "language_code": "sr", + "language_name": "Serbian" + }, + { + "language_code": "si", + "language_name": "Sinhalese" + }, + { + "language_code": "sk", + "language_name": "Slovak" + }, + { + "language_code": "sl", + "language_name": "Slovenian" + }, + { + "language_code": "es", + "language_name": "Spanish" + }, + { + "language_code": "sw", + "language_name": "Swahili" + }, + { + "language_code": "sv", + "language_name": "Swedish" + }, + { + "language_code": "sy", + "language_name": "Syriac" + }, + { + "language_code": "ta", + "language_name": "Tamil" + }, + { + "language_code": "te", + "language_name": "Telugu" + }, + { + "language_code": "tl", + "language_name": "Tagalog" + }, + { + "language_code": "th", + "language_name": "Thai" + }, + { + "language_code": "tr", + "language_name": "Turkish" + }, + { + "language_code": "uk", + "language_name": "Ukrainian" + }, + { + "language_code": "ur", + "language_name": "Urdu" + }, + { + "language_code": "uz", + "language_name": "Uzbek" + }, + { + "language_code": "vi", + "language_name": "Vietnamese" + }, + { + "language_code": "ro", + "language_name": "Romanian" + }, + { + "language_code": "pt-br", + "language_name": "Portuguese (Brazilian)" + }, + { + "language_code": "me", + "language_name": "Montenegrin" + }, + { + "language_code": "zh-tw", + "language_name": "Chinese (traditional)" + }, + { + "language_code": "ze", + "language_name": "Chinese bilingual" + }, + { + "language_code": "se", + "language_name": "Northern Sami" + } + ] + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Api-Key": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ] + } + }, + "/infos/user": { + "get": { + "tags": [ + "Infos" + ], + "summary": "User Informations", + "description": "Gather informations about the user authenticated by a bearer token. User information are already sent when user is authenticated, and the remaining downloads is returned with each download, but you can also get these information here.", + "operationId": "userinfo", + "responses": { + "200": { + "description": "Get user data", + "content": { + "application/json": { + "schema": { + "required": [ + "data" + ], + "type": "object", + "properties": { + "data": { + "required": [ + "allowed_downloads", + "downloads_count", + "ext_installed", + "level", + "remaining_downloads", + "user_id", + "vip" + ], + "type": "object", + "properties": { + "allowed_downloads": { + "type": "number" + }, + "level": { + "minLength": 1, + "type": "string" + }, + "user_id": { + "type": "number" + }, + "ext_installed": { + "type": "boolean" + }, + "vip": { + "type": "boolean" + }, + "downloads_count": { + "type": "number" + }, + "remaining_downloads": { + "type": "number" + } + } + } + }, + "description": "" + }, + "example": { + "data": { + "allowed_downloads": 100, + "level": "Sub leecher", + "user_id": 66, + "ext_installed": false, + "vip": false, + "downloads_count": 1, + "remaining_downloads": 99 + } + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Bearer": [] + }, + { + "Api-Key": [] + } + ], + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ] + } + }, + "/discover/popular": { + "get": { + "tags": [ + "Discover" + ], + "summary": "Popular features", + "description": "Discover popular features on opensubtitles.com, according to last 30 days downloads.", + "operationId": "popular", + "parameters": [ + { + "name": "language", + "in": "query", + "description": "Language code, 1 language per query, or \"all\"", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "description": "Type (movie or tvshow)", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subtitle" + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Api-Key": [] + } + ] + } + }, + "/discover/latest": { + "get": { + "tags": [ + "Discover" + ], + "summary": "Latest subtitles", + "description": "Lists 60 latest uploaded subtitles", + "operationId": "latest", + "parameters": [ + { + "name": "language", + "in": "query", + "description": "Language code, 1 language per query, or \"all\"", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "description": "Type (movie or tvshow)", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "required": [ + "data", + "page", + "total_count", + "total_pages" + ], + "type": "object", + "properties": { + "total_pages": { + "type": "number" + }, + "total_count": { + "type": "number" + }, + "page": { + "type": "number" + }, + "data": { + "minItems": 1, + "uniqueItems": true, + "type": "array", + "items": { + "required": [ + "id", + "type" + ], + "type": "object", + "properties": { + "id": { + "minLength": 1, + "type": "string" + }, + "type": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "required": [ + "ai_translated", + "comments", + "download_count", + "feature_details", + "files", + "foreign_parts_only", + "fps", + "from_trusted", + "hd", + "hearing_impaired", + "language", + "legacy_subtitle_id", + "new_download_count", + "points", + "ratings", + "related_links", + "release", + "subtitle_id", + "upload_date", + "uploader", + "url", + "votes" + ], + "type": "object", + "properties": { + "subtitle_id": { + "minLength": 1, + "type": "string" + }, + "language": { + "minLength": 1, + "type": "string" + }, + "download_count": { + "type": "number" + }, + "new_download_count": { + "type": "number" + }, + "hearing_impaired": { + "type": "boolean" + }, + "hd": { + "type": "boolean" + }, + "format": { + "type": "object" + }, + "fps": { + "type": "number" + }, + "votes": { + "type": "number" + }, + "points": { + "type": "number" + }, + "ratings": { + "type": "number" + }, + "from_trusted": { + "type": "boolean" + }, + "foreign_parts_only": { + "type": "boolean" + }, + "ai_translated": { + "type": "boolean" + }, + "machine_translated": { + "type": "object" + }, + "upload_date": { + "minLength": 1, + "type": "string" + }, + "release": { + "minLength": 1, + "type": "string" + }, + "comments": { + "type": "string" + }, + "legacy_subtitle_id": { + "type": "number" + }, + "uploader": { + "required": [ + "name", + "rank", + "uploader_id" + ], + "type": "object", + "properties": { + "uploader_id": { + "type": "number" + }, + "name": { + "minLength": 1, + "type": "string" + }, + "rank": { + "minLength": 1, + "type": "string" + } + } + }, + "feature_details": { + "required": [ + "feature_id", + "feature_type", + "imdb_id", + "movie_name", + "title", + "year" + ], + "type": "object", + "properties": { + "feature_id": { + "type": "number" + }, + "feature_type": { + "minLength": 1, + "type": "string" + }, + "year": { + "type": "number" + }, + "title": { + "minLength": 1, + "type": "string" + }, + "movie_name": { + "minLength": 1, + "type": "string" + }, + "imdb_id": { + "type": "number" + }, + "tmdb_id": { + "type": "object" + } + } + }, + "url": { + "minLength": 1, + "type": "string" + }, + "related_links": { + "required": [ + "img_url", + "label", + "url" + ], + "type": "object", + "properties": { + "label": { + "minLength": 1, + "type": "string" + }, + "url": { + "minLength": 1, + "type": "string" + }, + "img_url": { + "minLength": 1, + "type": "string" + } + } + }, + "files": { + "minItems": 1, + "uniqueItems": true, + "type": "array", + "items": { + "required": [ + "cd_number", + "file_id", + "file_name" + ], + "type": "object", + "properties": { + "file_id": { + "type": "number" + }, + "cd_number": { + "type": "number" + }, + "file_name": { + "minLength": 1, + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "description": "" + }, + "examples": { + "example": { + "value": { + "total_pages": 1, + "total_count": 10, + "page": 1, + "data": [ + { + "id": "string", + "type": "string", + "attributes": { + "subtitle_id": "string", + "language": "string", + "download_count": 0, + "new_download_count": 0, + "hearing_impaired": true, + "hd": true, + "format": {}, + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": true, + "foreign_parts_only": true, + "ai_translated": true, + "machine_translated": {}, + "upload_date": "string", + "release": "string", + "comments": "string", + "legacy_subtitle_id": 0, + "uploader": { + "uploader_id": 0, + "name": "string", + "rank": "string" + }, + "feature_details": { + "feature_id": 0, + "feature_type": "string", + "year": 0, + "title": "string", + "movie_name": "string", + "imdb_id": 0, + "tmdb_id": {} + }, + "url": "string", + "related_links": { + "label": "string", + "url": "string", + "img_url": "string" + }, + "files": [ + { + "file_id": 0, + "cd_number": 0, + "file_name": "string" + } + ] + } + } + ] + } + } + } + }, + "example": { + "example": { + "total_pages": 1, + "total_count": 60, + "page": 1, + "data": [ + { + "id": "6150116", + "type": "subtitle", + "attributes": { + "subtitle_id": "6150116", + "language": "sv", + "download_count": 0, + "new_download_count": 0, + "hearing_impaired": false, + "hd": true, + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": true, + "foreign_parts_only": false, + "ai_translated": false, + "upload_date": "2021-11-10T13:48:58.000Z", + "release": "No.Sudden.Move.2021.1080p.WEB.H264-TIMECUT", + "comments": "", + "legacy_subtitle_id": 8871896, + "uploader": { + "uploader_id": 83676, + "name": "Nemo_", + "rank": "Trusted member" + }, + "feature_details": { + "feature_id": 1226096, + "feature_type": "Movie", + "year": 2021, + "title": "No Sudden Move", + "movie_name": "2021 - No Sudden Move", + "imdb_id": 11525644, + "tmdb_id": 649409 + }, + "url": "https://www.opensubtitles.com/sv/subtitles/legacy/8871896", + "related_links": { + "label": "All subtitles for No Sudden Move", + "url": "https://www.opensubtitles.com/sv/movies/2021-no-sudden-move", + "img_url": "https://s9.osdb.link/features/6/9/0/1226096.jpg" + }, + "files": [ + { + "file_id": 7086008, + "cd_number": 1, + "file_name": "No.Sudden.Move.2021.1080p.WEB.H264-TIMECUT.srt" + } + ] + } + }, + { + "id": "6150127", + "type": "subtitle", + "attributes": { + "subtitle_id": "6150127", + "language": "zh-CN", + "download_count": 12, + "new_download_count": 2, + "hearing_impaired": false, + "hd": true, + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "upload_date": "2021-11-10T13:45:35.000Z", + "release": "Shang.Chi.and.the.Legend.of.the.Ten.Rings.2021.COMPLETE.UHD.BLURAY-SURCODE", + "comments": "", + "legacy_subtitle_id": 8871895, + "uploader": { + "uploader_id": 0, + "name": "Anonymous", + "rank": "anonymous" + }, + "feature_details": { + "feature_id": 1218697, + "feature_type": "Movie", + "year": 2021, + "title": "Shang-Chi and the Legend of the Ten Rings", + "movie_name": "2021 - Shang-Chi and the Legend of the Ten Rings", + "imdb_id": 9376612, + "tmdb_id": 566525 + }, + "url": "https://www.opensubtitles.com/zh-CN/subtitles/legacy/8871895", + "related_links": { + "label": "All subtitles for Shang-Chi and the Legend of the Ten Rings", + "url": "https://www.opensubtitles.com/zh-CN/movies/2021-shang-chi-and-the-legend-of-the-ten-rings", + "img_url": "https://s9.osdb.link/features/7/9/6/1218697.jpg" + }, + "files": [ + { + "file_id": 7086014, + "cd_number": 1, + "file_name": "Shang.Chi.and.the.Legend.of.the.Ten.Rings.2021.COMPLETE.UHD.BLURAY-SURCODE.ass" + } + ] + } + } + ], + "value": { + "total_pages": 1, + "total_count": 60, + "page": 1, + "data": [ + { + "id": "6150116", + "type": "subtitle", + "attributes": { + "subtitle_id": "6150116", + "language": "sv", + "download_count": 0, + "new_download_count": 0, + "hearing_impaired": false, + "hd": true, + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": true, + "foreign_parts_only": false, + "ai_translated": false, + "upload_date": "2021-11-10T13:48:58.000Z", + "release": "No.Sudden.Move.2021.1080p.WEB.H264-TIMECUT", + "comments": "", + "legacy_subtitle_id": 8871896, + "uploader": { + "uploader_id": 83676, + "name": "Nemo_", + "rank": "Trusted member" + }, + "feature_details": { + "feature_id": 1226096, + "feature_type": "Movie", + "year": 2021, + "title": "No Sudden Move", + "movie_name": "2021 - No Sudden Move", + "imdb_id": 11525644, + "tmdb_id": 649409 + }, + "url": "https://www.opensubtitles.com/sv/subtitles/legacy/8871896", + "related_links": { + "label": "All subtitles for No Sudden Move", + "url": "https://www.opensubtitles.com/sv/movies/2021-no-sudden-move", + "img_url": "https://s9.osdb.link/features/6/9/0/1226096.jpg" + }, + "files": [ + { + "file_id": 7086008, + "cd_number": 1, + "file_name": "No.Sudden.Move.2021.1080p.WEB.H264-TIMECUT.srt" + } + ] + } + }, + { + "id": "6150127", + "type": "subtitle", + "attributes": { + "subtitle_id": "6150127", + "language": "zh-CN", + "download_count": 12, + "new_download_count": 2, + "hearing_impaired": false, + "hd": true, + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "upload_date": "2021-11-10T13:45:35.000Z", + "release": "Shang.Chi.and.the.Legend.of.the.Ten.Rings.2021.COMPLETE.UHD.BLURAY-SURCODE", + "comments": "", + "legacy_subtitle_id": 8871895, + "uploader": { + "name": "Anonymous", + "rank": "anonymous" + }, + "feature_details": { + "feature_id": 1218697, + "feature_type": "Movie", + "year": 2021, + "title": "Shang-Chi and the Legend of the Ten Rings", + "movie_name": "2021 - Shang-Chi and the Legend of the Ten Rings", + "imdb_id": 9376612, + "tmdb_id": 566525 + }, + "url": "https://www.opensubtitles.com/zh-CN/subtitles/legacy/8871895", + "related_links": { + "label": "All subtitles for Shang-Chi and the Legend of the Ten Rings", + "url": "https://www.opensubtitles.com/zh-CN/movies/2021-shang-chi-and-the-legend-of-the-ten-rings", + "img_url": "https://s9.osdb.link/features/7/9/6/1218697.jpg" + }, + "files": [ + { + "file_id": 7086014, + "cd_number": 1, + "file_name": "Shang.Chi.and.the.Legend.of.the.Ten.Rings.2021.COMPLETE.UHD.BLURAY-SURCODE.ass" + } + ] + } + } + ] + } + } + } + } + } + }, + "security": [ + { + "Api-Key": [] + } + ] + } + }, + "/discover/most_downloaded": { + "get": { + "tags": [ + "Discover" + ], + "summary": "Most downloaded subtitles", + "description": "Discover popular subtitles, according to last 30 days downloads on opensubtitles.com. This list can be filtered by language code or feature type (movie, episode)", + "operationId": "most_downloaded", + "parameters": [ + { + "name": "language", + "in": "query", + "description": "Language code, 1 language per query, or \"all\"", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "description": "Type (movie or tvshow)", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ], + "responses": { + "200": { + "description": "Lists most downloaded movie subtitles ", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "", + "properties": { + "total_pages": { + "type": "number" + }, + "total_count": { + "type": "number" + }, + "page": { + "type": "number" + }, + "data": { + "minItems": 1, + "uniqueItems": true, + "type": "array", + "items": { + "$ref": "#/components/schemas/Subtitle" + } + } + }, + "required": [ + "total_pages", + "total_count", + "page", + "data" + ] + }, + "example": { + "total_pages": 1, + "total_count": 46, + "page": 1, + "data": [ + { + "id": "493023", + "type": "subtitle", + "attributes": { + "subtitle_id": "493023", + "language": "nl", + "download_count": 3889, + "new_download_count": 11, + "hearing_impaired": false, + "hd": false, + "format": "srt", + "fps": 29.97, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2010-11-15T14:55:39.000Z", + "release": "Major League WS DVDRip", + "comments": "", + "legacy_subtitle_id": 3956266, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 518105, + "feature_type": "Movie", + "year": 1989, + "title": "Major League", + "movie_name": "1989 - Major League", + "imdb_id": 97815, + "tmdb_id": 9942 + }, + "url": "https://www.opensubtitles.com/nl/subtitles/legacy/3956266", + "related_links": [ + { + "label": "All subtitles for Major League", + "url": "https://www.opensubtitles.com/nl/movies/1989-major-league", + "img_url": "https://s9.osdb.link/features/5/0/1/518105.jpg" + } + ], + "files": [ + { + "file_id": 544077, + "cd_number": 1, + "file_name": "Major League WS DVDRip.srt" + } + ] + } + }, + { + "id": "496423", + "type": "subtitle", + "attributes": { + "subtitle_id": "496423", + "language": "es", + "download_count": 674, + "new_download_count": 5, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 29.97, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2008-03-23T21:04:04.000Z", + "release": "Le mépris", + "comments": "", + "legacy_subtitle_id": 3264195, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 518020, + "feature_type": "Movie", + "year": 1963, + "title": "Contempt", + "movie_name": "1963 - Contempt", + "imdb_id": 57345, + "tmdb_id": 266 + }, + "url": "https://www.opensubtitles.com/es/subtitles/legacy/3264195", + "related_links": [ + { + "label": "All subtitles for Contempt", + "url": "https://www.opensubtitles.com/es/movies/1963-contempt", + "img_url": "https://s9.osdb.link/features/0/2/0/518020.jpg" + } + ], + "files": [ + { + "file_id": 6028018, + "cd_number": 1, + "file_name": "Le mepris-es.srt" + } + ] + } + }, + { + "id": "495449", + "type": "subtitle", + "attributes": { + "subtitle_id": "495449", + "language": "pt-BR", + "download_count": 189, + "new_download_count": 1, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 23.976, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2012-07-14T19:47:18.000Z", + "release": "Gnger Snaps", + "comments": "", + "legacy_subtitle_id": 4617243, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 518342, + "feature_type": "Movie", + "year": 2000, + "title": "Ginger Snaps", + "movie_name": "2000 - Ginger Snaps", + "imdb_id": 210070, + "tmdb_id": 9871 + }, + "url": "https://www.opensubtitles.com/pt-BR/subtitles/legacy/4617243", + "related_links": [ + { + "label": "All subtitles for Ginger Snaps", + "url": "https://www.opensubtitles.com/pt-BR/movies/2000-ginger-snaps", + "img_url": "https://s9.osdb.link/features/2/4/3/518342.jpg" + } + ], + "files": [ + { + "file_id": 546827, + "cd_number": 1, + "file_name": "Ginger Snaps [2000] DvDrip [Eng] Bugz.srt" + } + ] + } + }, + { + "id": "496964", + "type": "subtitle", + "attributes": { + "subtitle_id": "496964", + "language": "es", + "download_count": 1190, + "new_download_count": 7, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 23.976, + "votes": 1, + "points": 10, + "ratings": 10, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2012-05-18T00:13:46.000Z", + "release": "1963.El desprecio (subt)", + "comments": "", + "legacy_subtitle_id": 4552383, + "uploader": { + "uploader_id": 63170, + "name": "robot2xl", + "rank": "read only" + }, + "feature_details": { + "feature_id": 518020, + "feature_type": "Movie", + "year": 1963, + "title": "Contempt", + "movie_name": "1963 - Contempt", + "imdb_id": 57345, + "tmdb_id": 266 + }, + "url": "https://www.opensubtitles.com/es/subtitles/legacy/4552383", + "related_links": [ + { + "label": "All subtitles for Contempt", + "url": "https://www.opensubtitles.com/es/movies/1963-contempt", + "img_url": "https://s9.osdb.link/features/0/2/0/518020.jpg" + } + ], + "files": [ + { + "file_id": 548446, + "cd_number": 1, + "file_name": "1963.El desprecio (subt).srt" + } + ] + } + }, + { + "id": "492641", + "type": "subtitle", + "attributes": { + "subtitle_id": "492641", + "language": "es", + "download_count": 1477, + "new_download_count": 2, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2005-08-19T22:00:00.000Z", + "release": "Philadelphia Story, The (1940)", + "comments": "", + "legacy_subtitle_id": 101000, + "uploader": { + "uploader_id": 14715, + "name": "marlowe62 (a)", + "rank": "bronze member" + }, + "feature_details": { + "feature_id": 518136, + "feature_type": "Movie", + "year": 1940, + "title": "The Philadelphia Story", + "movie_name": "1940 - The Philadelphia Story", + "imdb_id": 32904, + "tmdb_id": 981 + }, + "url": "https://www.opensubtitles.com/es/subtitles/legacy/101000", + "related_links": [ + { + "label": "All subtitles for The Philadelphia Story", + "url": "https://www.opensubtitles.com/es/movies/1940-the-philadelphia-story", + "img_url": "https://s9.osdb.link/features/6/3/1/518136.jpg" + } + ], + "files": [ + { + "file_id": 6026506, + "cd_number": 1, + "file_name": "George Cukor - Historias de Filadelfia (1940) DvdRip XviD Mp3 Dual Divxclasico.Esp.srt" + } + ] + } + }, + { + "id": "493247", + "type": "subtitle", + "attributes": { + "subtitle_id": "493247", + "language": "el", + "download_count": 1052, + "new_download_count": 16, + "hearing_impaired": false, + "hd": true, + "format": "", + "fps": 23.976, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2011-05-17T07:28:54.000Z", + "release": "Superman.1978.720.BluRay.x264-VarK", + "comments": "", + "legacy_subtitle_id": 4179141, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 517956, + "feature_type": "Movie", + "year": 1978, + "title": "Superman", + "movie_name": "1978 - Superman", + "imdb_id": 78346, + "tmdb_id": 1924 + }, + "url": "https://www.opensubtitles.com/el/subtitles/legacy/4179141", + "related_links": [ + { + "label": "All subtitles for Superman", + "url": "https://www.opensubtitles.com/el/movies/1978-superman", + "img_url": "https://s9.osdb.link/features/6/5/9/517956.jpg" + } + ], + "files": [ + { + "file_id": 544327, + "cd_number": 1, + "file_name": "Superman.1978.720.BluRay.x264-VarK.srt" + } + ] + } + }, + { + "id": "496017", + "type": "subtitle", + "attributes": { + "subtitle_id": "496017", + "language": "en", + "download_count": 24642, + "new_download_count": 8, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 23.98, + "votes": 13, + "points": 81, + "ratings": 6.2, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2002-10-10T22:00:00.000Z", + "release": "Jingle All the Way (1996)", + "comments": "none", + "legacy_subtitle_id": 32268, + "uploader": { + "uploader_id": 6872, + "name": "alxmota (a)", + "rank": "bronze member" + }, + "feature_details": { + "feature_id": 518213, + "feature_type": "Movie", + "year": 1996, + "title": "Jingle All the Way", + "movie_name": "1996 - Jingle All the Way", + "imdb_id": 116705, + "tmdb_id": 9279 + }, + "url": "https://www.opensubtitles.com/en/subtitles/legacy/32268", + "related_links": [ + { + "label": "All subtitles for Jingle All the Way", + "url": "https://www.opensubtitles.com/en/movies/1996-jingle-all-the-way", + "img_url": "https://s9.osdb.link/features/3/1/2/518213.jpg" + } + ], + "files": [ + { + "file_id": 547446, + "cd_number": 1, + "file_name": "ingles.srt" + } + ] + } + }, + { + "id": "496396", + "type": "subtitle", + "attributes": { + "subtitle_id": "496396", + "language": "nl", + "download_count": 1993, + "new_download_count": 16, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 25, + "votes": 1, + "points": 7, + "ratings": 7, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2013-10-10T22:54:26.000Z", + "release": "Come and See (Idi i smotri) (1985)", + "comments": "", + "legacy_subtitle_id": 5217608, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 518417, + "feature_type": "Movie", + "year": 1985, + "title": "Come and See", + "movie_name": "1985 - Come and See", + "imdb_id": 91251, + "tmdb_id": 25237 + }, + "url": "https://www.opensubtitles.com/nl/subtitles/legacy/5217608", + "related_links": [ + { + "label": "All subtitles for Come and See", + "url": "https://www.opensubtitles.com/nl/movies/1985-come-and-see", + "img_url": "https://s9.osdb.link/features/7/1/4/518417.jpg" + } + ], + "files": [ + { + "file_id": 547857, + "cd_number": 1, + "file_name": "Come and See (Idi i smotri) (1985)-dut(1).srt" + } + ] + } + }, + { + "id": "492427", + "type": "subtitle", + "attributes": { + "subtitle_id": "492427", + "language": "pt-BR", + "download_count": 358, + "new_download_count": 3, + "hearing_impaired": false, + "hd": true, + "format": "", + "fps": 23.98, + "votes": 1, + "points": 1, + "ratings": 1, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2015-12-26T15:19:56.000Z", + "release": "Rambo.First.Blood.II.1985.Ultimate.Uncut.Remastered.Edition.1080p.BluRay.x264.AAC-ETRG", + "comments": "PT-BR subtitle uploaded by saidleugim", + "legacy_subtitle_id": 6437707, + "uploader": { + "uploader_id": 71162, + "name": "saidleugim", + "rank": "bronze member" + }, + "feature_details": { + "feature_id": 517867, + "feature_type": "Movie", + "year": 1985, + "title": "Rambo: First Blood Part II", + "movie_name": "1985 - Rambo: First Blood Part II", + "imdb_id": 89880, + "tmdb_id": 1369 + }, + "url": "https://www.opensubtitles.com/pt-BR/subtitles/legacy/6437707", + "related_links": [ + { + "label": "All subtitles for Rambo: First Blood Part II", + "url": "https://www.opensubtitles.com/pt-BR/movies/1985-rambo-first-blood-part-ii", + "img_url": "https://s9.osdb.link/features/7/6/8/517867.jpg" + } + ], + "files": [ + { + "file_id": 6026410, + "cd_number": 1, + "file_name": "Rambo.First.Blood.II.1985.Ultimate.Uncut.Remastered.Edition.1080p.BluRay.x264.AAC-ETRG.srt" + } + ] + } + }, + { + "id": "493721", + "type": "subtitle", + "attributes": { + "subtitle_id": "493721", + "language": "en", + "download_count": 16956, + "new_download_count": 4, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 25, + "votes": 7, + "points": 67, + "ratings": 9.6, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2007-05-16T09:07:38.000Z", + "release": "Hannibal", + "comments": "", + "legacy_subtitle_id": 3123830, + "uploader": { + "uploader_id": 34153, + "name": "arkymedes", + "rank": "bronze member" + }, + "feature_details": { + "feature_id": 517991, + "feature_type": "Movie", + "year": 2001, + "title": "Hannibal", + "movie_name": "2001 - Hannibal", + "imdb_id": 212985, + "tmdb_id": 9740 + }, + "url": "https://www.opensubtitles.com/en/subtitles/legacy/3123830", + "related_links": [ + { + "label": "All subtitles for Hannibal", + "url": "https://www.opensubtitles.com/en/movies/2001-hannibal", + "img_url": "https://s9.osdb.link/features/1/9/9/517991.jpg" + } + ], + "files": [ + { + "file_id": 544852, + "cd_number": 1, + "file_name": "Hannibal.en.srt" + } + ] + } + }, + { + "id": "496722", + "type": "subtitle", + "attributes": { + "subtitle_id": "496722", + "language": "es", + "download_count": 544, + "new_download_count": 21, + "hearing_impaired": false, + "hd": true, + "format": "", + "fps": 25, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": true, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2011-05-28T08:48:56.000Z", + "release": "Le Mepris (1963) HDRip Dual XviD Ac3 by FitoCorleone", + "comments": "El.Desprecio.(Le.Mepris).(1963).HDRip.Dual.(Spa.Fr.).(Xvid+2Ac3).(proteinicos.es)...FitoCorleone.avi [1.47 Gb]/ Corregidos por el GTC de DivXClasico para Proteinicos y DXC", + "legacy_subtitle_id": 4184944, + "uploader": { + "uploader_id": 32127, + "name": "marlowe62", + "rank": "trusted" + }, + "feature_details": { + "feature_id": 518020, + "feature_type": "Movie", + "year": 1963, + "title": "Contempt", + "movie_name": "1963 - Contempt", + "imdb_id": 57345, + "tmdb_id": 266 + }, + "url": "https://www.opensubtitles.com/es/subtitles/legacy/4184944", + "related_links": [ + { + "label": "All subtitles for Contempt", + "url": "https://www.opensubtitles.com/es/movies/1963-contempt", + "img_url": "https://s9.osdb.link/features/0/2/0/518020.jpg" + } + ], + "files": [ + { + "file_id": 548202, + "cd_number": 1, + "file_name": "Le Mepris (1963) HDRip Dual XviD Ac3 by FitoCorleone.Esp.srt" + } + ] + } + }, + { + "id": "492688", + "type": "subtitle", + "attributes": { + "subtitle_id": "492688", + "language": "en", + "download_count": 72506, + "new_download_count": 62, + "hearing_impaired": true, + "hd": false, + "format": "", + "fps": 25, + "votes": 2, + "points": 20, + "ratings": 10, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2008-07-09T13:19:22.000Z", + "release": "Rocky.I[1976]DvDrip-aXXo", + "comments": "Rocky-The.Complete.Saga[2007]DvDrip-aXXo", + "legacy_subtitle_id": 3302711, + "uploader": { + "uploader_id": 119465, + "name": "os_robot", + "rank": "bronze member" + }, + "feature_details": { + "feature_id": 517932, + "feature_type": "Movie", + "year": 1976, + "title": "Rocky", + "movie_name": "1976 - Rocky", + "imdb_id": 75148, + "tmdb_id": 1366 + }, + "url": "https://www.opensubtitles.com/en/subtitles/legacy/3302711", + "related_links": [ + { + "label": "All subtitles for Rocky", + "url": "https://www.opensubtitles.com/en/movies/1976-rocky", + "img_url": "https://s9.osdb.link/features/2/3/9/517932.jpg" + } + ], + "files": [ + { + "file_id": 543715, + "cd_number": 1, + "file_name": "Rocky.I[1976]DvDrip-aXXo.srt" + } + ] + } + }, + { + "id": "490625", + "type": "subtitle", + "attributes": { + "subtitle_id": "490625", + "language": "en", + "download_count": 7989, + "new_download_count": 20, + "hearing_impaired": false, + "hd": true, + "format": "", + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2013-07-06T11:07:28.000Z", + "release": "9.Songs.2004.720p.BluRay.x264.anoXmous", + "comments": "", + "legacy_subtitle_id": 5075303, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 518052, + "feature_type": "Movie", + "year": 2004, + "title": "9 Songs", + "movie_name": "2004 - 9 Songs", + "imdb_id": 411705, + "tmdb_id": 27 + }, + "url": "https://www.opensubtitles.com/en/subtitles/legacy/5075303", + "related_links": [ + { + "label": "All subtitles for 9 Songs", + "url": "https://www.opensubtitles.com/en/movies/2004-9-songs", + "img_url": "https://s9.osdb.link/features/2/5/0/518052.jpg" + } + ], + "files": [ + { + "file_id": 541296, + "cd_number": 1, + "file_name": "9.Songs.2004.720p.BluRay.x264.anoXmous_eng.srt" + } + ] + } + }, + { + "id": "494835", + "type": "subtitle", + "attributes": { + "subtitle_id": "494835", + "language": "pt-BR", + "download_count": 2028, + "new_download_count": 10, + "hearing_impaired": false, + "hd": true, + "format": "", + "fps": 24, + "votes": 2, + "points": 20, + "ratings": 10, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2014-09-07T13:57:21.000Z", + "release": "Red Sonja.1985.BDRip.720p.MultiLang.MultiSub-Pitt", + "comments": "", + "legacy_subtitle_id": 5815278, + "uploader": { + "uploader_id": 62530, + "name": "fjones1979", + "rank": "trusted" + }, + "feature_details": { + "feature_id": 518067, + "feature_type": "Movie", + "year": 1985, + "title": "Red Sonja", + "movie_name": "1985 - Red Sonja", + "imdb_id": 89893, + "tmdb_id": 9626 + }, + "url": "https://www.opensubtitles.com/pt-BR/subtitles/legacy/5815278", + "related_links": [ + { + "label": "All subtitles for Red Sonja", + "url": "https://www.opensubtitles.com/pt-BR/movies/1985-red-sonja", + "img_url": "https://s9.osdb.link/features/7/6/0/518067.jpg" + } + ], + "files": [ + { + "file_id": 546129, + "cd_number": 1, + "file_name": "Red Sonja.1985.BDRip.720p.MultiLang.MultiSub-Pitt.por.srt" + } + ] + } + }, + { + "id": "492618", + "type": "subtitle", + "attributes": { + "subtitle_id": "492618", + "language": "pl", + "download_count": 147, + "new_download_count": 4, + "hearing_impaired": false, + "hd": true, + "format": "", + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2015-07-08T20:47:42.000Z", + "release": "Presumed.Innocent.1990.720p.BRRip.XviD.AC3-RARBG", + "comments": "", + "legacy_subtitle_id": 6228423, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 517957, + "feature_type": "Movie", + "year": 1990, + "title": "Presumed Innocent", + "movie_name": "1990 - Presumed Innocent", + "imdb_id": 100404, + "tmdb_id": 11092 + }, + "url": "https://www.opensubtitles.com/pl/subtitles/legacy/6228423", + "related_links": [ + { + "label": "All subtitles for Presumed Innocent", + "url": "https://www.opensubtitles.com/pl/movies/1990-presumed-innocent", + "img_url": "https://s9.osdb.link/features/7/5/9/517957.jpg" + } + ], + "files": [ + { + "file_id": 6026497, + "cd_number": 1, + "file_name": "Presumed.Innocent.1990.720p.BRRip.XviD.AC3-RARBG.txt" + } + ] + } + }, + { + "id": "496727", + "type": "subtitle", + "attributes": { + "subtitle_id": "496727", + "language": "en", + "download_count": 1912, + "new_download_count": 63, + "hearing_impaired": false, + "hd": true, + "format": "", + "fps": 23.976, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2016-08-12T06:53:45.000Z", + "release": "Idi.i.smotri.AKA.Come.and.See.1985.IVC.1080p.BluRay.Remux.AVC.FLAC.2.0-oddset", + "comments": "", + "legacy_subtitle_id": 6708061, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 518417, + "feature_type": "Movie", + "year": 1985, + "title": "Come and See", + "movie_name": "1985 - Come and See", + "imdb_id": 91251, + "tmdb_id": 25237 + }, + "url": "https://www.opensubtitles.com/en/subtitles/legacy/6708061", + "related_links": [ + { + "label": "All subtitles for Come and See", + "url": "https://www.opensubtitles.com/en/movies/1985-come-and-see", + "img_url": "https://s9.osdb.link/features/7/1/4/518417.jpg" + } + ], + "files": [ + { + "file_id": 6028156, + "cd_number": 1, + "file_name": "Idi.i.smotri.AKA.Come.and.See.1985.IVC.1080p.BluRay.Remux.AVC.FLAC.2.0-oddset.srt" + } + ] + } + }, + { + "id": "493563", + "type": "subtitle", + "attributes": { + "subtitle_id": "493563", + "language": "en", + "download_count": 14796, + "new_download_count": 12, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 23.976, + "votes": 1, + "points": 10, + "ratings": 10, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2001-12-04T23:00:00.000Z", + "release": "The Secret Garden", + "comments": "", + "legacy_subtitle_id": 106540, + "uploader": { + "uploader_id": 4143, + "name": "Panayot (a)", + "rank": "bronze member" + }, + "feature_details": { + "feature_id": 518028, + "feature_type": "Movie", + "year": 1993, + "title": "The Secret Garden", + "movie_name": "1993 - The Secret Garden", + "imdb_id": 108071, + "tmdb_id": 11236 + }, + "url": "https://www.opensubtitles.com/en/subtitles/legacy/106540", + "related_links": [ + { + "label": "All subtitles for The Secret Garden", + "url": "https://www.opensubtitles.com/en/movies/1993-the-secret-garden", + "img_url": "https://s9.osdb.link/features/8/2/0/518028.jpg" + } + ], + "files": [ + { + "file_id": 544691, + "cd_number": 1, + "file_name": "TheSecretGarden_EN.sub" + } + ] + } + }, + { + "id": "492466", + "type": "subtitle", + "attributes": { + "subtitle_id": "492466", + "language": "he", + "download_count": 128, + "new_download_count": 1, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2008-02-08T02:00:42.000Z", + "release": "Rocky.1976.DVDRip.XviD.AC3.iNTERNAL-QiM", + "comments": "", + "legacy_subtitle_id": 3246129, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 517932, + "feature_type": "Movie", + "year": 1976, + "title": "Rocky", + "movie_name": "1976 - Rocky", + "imdb_id": 75148, + "tmdb_id": 1366 + }, + "url": "https://www.opensubtitles.com/he/subtitles/legacy/3246129", + "related_links": [ + { + "label": "All subtitles for Rocky", + "url": "https://www.opensubtitles.com/he/movies/1976-rocky", + "img_url": "https://s9.osdb.link/features/2/3/9/517932.jpg" + } + ], + "files": [ + { + "file_id": 6026439, + "cd_number": 2, + "file_name": "qim-rockyb.srt" + }, + { + "file_id": 6026437, + "cd_number": 1, + "file_name": "qim-rockya.srt" + } + ] + } + }, + { + "id": "492560", + "type": "subtitle", + "attributes": { + "subtitle_id": "492560", + "language": "pt-BR", + "download_count": 64, + "new_download_count": 2, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 0, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2017-07-29T22:49:40.000Z", + "release": "jornada", + "comments": "", + "legacy_subtitle_id": 7052825, + "uploader": { + "uploader_id": 3282, + "name": "COF7CJpS", + "rank": "app developer" + }, + "feature_details": { + "feature_id": 517867, + "feature_type": "Movie", + "year": 1985, + "title": "Rambo: First Blood Part II", + "movie_name": "1985 - Rambo: First Blood Part II", + "imdb_id": 89880, + "tmdb_id": 1369 + }, + "url": "https://www.opensubtitles.com/pt-BR/subtitles/legacy/7052825", + "related_links": [ + { + "label": "All subtitles for Rambo: First Blood Part II", + "url": "https://www.opensubtitles.com/pt-BR/movies/1985-rambo-first-blood-part-ii", + "img_url": "https://s9.osdb.link/features/7/6/8/517867.jpg" + } + ], + "files": [ + { + "file_id": 6026472, + "cd_number": 1, + "file_name": "jornada.srt" + } + ] + } + }, + { + "id": "496159", + "type": "subtitle", + "attributes": { + "subtitle_id": "496159", + "language": "de", + "download_count": 226, + "new_download_count": 7, + "hearing_impaired": false, + "hd": false, + "format": "", + "fps": 25, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": true, + "foreign_parts_only": false, + "ai_translated": false, + "machine_translated": false, + "upload_date": "2012-07-21T17:11:45.000Z", + "release": "Komm.und.sieh.1985.German.DVDRip.Retail", + "comments": "RUSCICO / Russian Cinema Council", + "legacy_subtitle_id": 4622375, + "uploader": { + "uploader_id": 41812, + "name": "Ralle1", + "rank": "administrator" + }, + "feature_details": { + "feature_id": 518417, + "feature_type": "Movie", + "year": 1985, + "title": "Come and See", + "movie_name": "1985 - Come and See", + "imdb_id": 91251, + "tmdb_id": 25237 + }, + "url": "https://www.opensubtitles.com/de/subtitles/legacy/4622375", + "related_links": [ + { + "label": "All subtitles for Come and See", + "url": "https://www.opensubtitles.com/de/movies/1985-come-and-see", + "img_url": "https://s9.osdb.link/features/7/1/4/518417.jpg" + } + ], + "files": [ + { + "file_id": 6027938, + "cd_number": 2, + "file_name": "Komm.und.sieh.1985.German.DVDRip.CD2.Retail.srt" + }, + { + "file_id": 6027936, + "cd_number": 1, + "file_name": "Komm.und.sieh.1985.German.DVDRip.CD1.Retail.srt" + } + ] + } + } + ] + } + }, + "example": { + "example": { + "total_pages": 1, + "total_count": 60, + "page": 1, + "data": [ + { + "id": "766099", + "type": "subtitle", + "attributes": { + "subtitle_id": "766099", + "language": "ko", + "download_count": 2836, + "new_download_count": 190, + "hearing_impaired": false, + "hd": true, + "fps": 23.976, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "upload_date": "2016-08-24T17:25:17.000Z", + "release": "The.Twilight.Saga.Breaking.Dawn.Part 1.2011.720p.BrRip.x264.YIFY", + "comments": "", + "legacy_subtitle_id": 6717006, + "uploader": { + "uploader_id": 3282, + "name": "os-auto", + "rank": "Application Developers" + }, + "feature_details": { + "feature_id": 568028, + "feature_type": "Movie", + "year": 2011, + "title": "The Twilight Saga: Breaking Dawn - Part 1", + "movie_name": "2011 - The Twilight Saga: Breaking Dawn - Part 1", + "imdb_id": 1324999, + "tmdb_id": 50619 + }, + "url": "https://www.opensubtitles.com/ko/subtitles/legacy/6717006", + "related_links": { + "label": "All subtitles for The Twilight Saga: Breaking Dawn - Part 1", + "url": "https://www.opensubtitles.com/ko/movies/2011-the-twilight-saga-breaking-dawn-part-1", + "img_url": "https://s9.osdb.link/features/8/2/0/568028.jpg" + }, + "files": [ + { + "file_id": 841356, + "cd_number": 1, + "file_name": "The.Twilight.Saga.Breaking.Dawn.Part 1.2011.720p.BrRip.x264.YIFY.smi" + } + ] + } + }, + { + "id": "4885905", + "type": "subtitle", + "attributes": { + "subtitle_id": "4885905", + "language": "ko", + "download_count": 3838, + "new_download_count": 78, + "hearing_impaired": false, + "hd": true, + "fps": 23.976, + "votes": 0, + "points": 0, + "ratings": 0, + "from_trusted": false, + "foreign_parts_only": false, + "ai_translated": false, + "upload_date": "2019-07-28T19:18:09.000Z", + "release": "The.Wolf.of.Wall.Street.2013.1080p.BluRay.x264-AMIABLE", + "comments": "ksub", + "legacy_subtitle_id": 7845982, + "uploader": { + "uploader_id": 82010, + "name": "ksubscene", + "rank": "Gold member" + }, + "feature_details": { + "feature_id": 586864, + "feature_type": "Movie", + "year": 2013, + "title": "The Wolf of Wall Street", + "movie_name": "2013 - The Wolf of Wall Street", + "imdb_id": 993846, + "tmdb_id": 106646 + }, + "url": "https://www.opensubtitles.com/ko/subtitles/legacy/7845982", + "related_links": { + "label": "All subtitles for The Wolf of Wall Street", + "url": "https://www.opensubtitles.com/ko/movies/2013-the-wolf-of-wall-street", + "img_url": "https://s9.osdb.link/features/4/6/8/586864.jpg" + }, + "files": [ + { + "file_id": 5009225, + "cd_number": 1, + "file_name": "The.Wolf.of.Wall.Street.2013.1080p.BluRay.x264-AMIABLE.smi" + } + ] + } + } + ] + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Api-Key": [] + } + ] + } + }, + "/features": { + "get": { + "tags": [ + "Features" + ], + "summary": "Search for features", + "description": "With the \"query\" parameter, search for a Feature from a simple text input. Typically used for a text search or autocomplete.\n\nWith an ID, get basic information and subtitles count for a specific title.\n\nWith the \"query_match\" you can define the matched applied to the query: \n - \"start\" is the default behavior, it will query on the first letter entered to offer suggestions\n - \"word\" will return the match on the word, but not always matching the fulll title, for example searching \"roma\" will return \"holiday in roma\"\n - \"exact\" will exactly match the title, so here searching for \"roma\" will only return the movie(s) named \"roma\" \n\nWith the \"full_search\" you can extend the search to the translations of the title, so \"roma\" will also return \"rome\" \n\n\n\n> ### Watch Out!\n>\n> If you create an autocomplete, don't set a too small refresh limit, remember you must not go over 40 requests per 10 seconds!", + "operationId": "features", + "parameters": [ + { + "name": "query", + "in": "query", + "description": "query to search, release/file name accepted", + "schema": { + "minLength": 3, + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "description": "empty to list all or **movie**, **tvshow** or **episode**.", + "schema": { + "type": "string" + } + }, + { + "name": "feature_id", + "in": "query", + "description": "opensubtitles **feature_id**", + "schema": { + "type": "integer" + } + }, + { + "name": "imdb_id", + "in": "query", + "description": "IMDB ID, delete leading zeroes", + "schema": { + "type": "string" + } + }, + { + "name": "tmdb_id", + "in": "query", + "description": "TheMovieDB ID - combine with type to avoid errors", + "schema": { + "type": "string" + } + }, + { + "name": "year", + "in": "query", + "description": "Filter by year. Can only be used in combination with a query", + "schema": { + "type": "integer" + } + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "query_match", + "description": "Type of matching applied to the query: **start** (default), **word**, **exact** " + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "full_search", + "description": "Search on original title and title aka (translations) (default false)" + } + ], + "responses": { + "200": { + "description": "Search for a feature", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "movie": { + "$ref": "#/components/schemas/Feature-Movie" + }, + "episode": { + "$ref": "#/components/schemas/Feature-Episode" + }, + "tv": { + "$ref": "#/components/schemas/Feature-Tvshow" + } + }, + "description": "" + }, + "example": { + "data": [ + { + "id": "9803", + "type": "feature", + "attributes": { + "title": "Waking the Dead", + "original_title": "", + "year": "2000", + "subtitles_counts": { + "en": 68, + "es": 41, + "nl": 40, + "ro": 40, + "ru": 30, + "sr": 21, + "pl": 7, + "pt-BR": 4, + "hr": 3, + "pt-PT": 3, + "bg": 2, + "el": 2, + "he": 2, + "fa": 2, + "sl": 2, + "tr": 2, + "ar": 1, + "bs": 1, + "cs": 1, + "et": 1, + "fi": 1, + "fr": 1, + "hu": 1, + "id": 1, + "ja": 1, + "sk": 1, + "th": 1, + "vi": 1 + }, + "subtitles_count": 11, + "seasons_count": 9, + "parent_title": "", + "season_number": 0, + "episode_number": "", + "imdb_id": 259733, + "tmdb_id": 4860, + "parent_imdb_id": "", + "feature_id": "9803", + "title_aka": [ + "Waking the Dead", + " Waking the Dead – Im Auftrag der Toten" + ], + "feature_type": "Tvshow", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead", + "img_url": "https://s9.osdb.link/features/3/0/8/9803.jpg", + "seasons": [ + { + "season_number": 1, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Pilot: Part 1", + "feature_id": 126854, + "feature_imdb_id": 743365 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Pilot: Part 2", + "feature_id": 126863, + "feature_imdb_id": 936019 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" Burn Out: Part 1", + "feature_id": 126861, + "feature_imdb_id": 743355 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" Burn Out: Part 2", + "feature_id": 126858, + "feature_imdb_id": 936016 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Blind Beggar: Part 1", + "feature_id": 126862, + "feature_imdb_id": 743353 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Blind Beggar: Part 2", + "feature_id": 126859, + "feature_imdb_id": 936017 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" A Simple Sacrifice: Part 1", + "feature_id": 126860, + "feature_imdb_id": 743350 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" A Simple Sacrifice: Part 2", + "feature_id": 126864, + "feature_imdb_id": 936015 + }, + { + "episode_number": 9, + "title": "\"Waking the Dead\" Every Breath You Take: Part 1", + "feature_id": 126865, + "feature_imdb_id": 743358 + }, + { + "episode_number": 10, + "title": "\"Waking the Dead\" Every Breath You Take: Part 2", + "feature_id": 126869, + "feature_imdb_id": 936018 + } + ] + }, + { + "season_number": 2, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Life Sentence: Part 1", + "feature_id": 126868, + "feature_imdb_id": 743363 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Life Sentence: Part 2", + "feature_id": 126871, + "feature_imdb_id": 1108804 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" Deathwatch: Part 1", + "feature_id": 126867, + "feature_imdb_id": 743357 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" Deathwatch: Part 2", + "feature_id": 126870, + "feature_imdb_id": 1108805 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Special Relationship: Part 1", + "feature_id": 126872, + "feature_imdb_id": 743367 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Special Relationship: Part 2", + "feature_id": 126878, + "feature_imdb_id": 1091606 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" Thin Air: Part 1", + "feature_id": 126875, + "feature_imdb_id": 743371 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" Thin Air: Part 2", + "feature_id": 126877, + "feature_imdb_id": 1167299 + } + ] + }, + { + "season_number": 3, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Multistorey: Part 1", + "feature_id": 126876, + "feature_imdb_id": 743364 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Multistorey: Part 2", + "feature_id": 126883, + "feature_imdb_id": 1167294 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" Walking on Water: Part 1", + "feature_id": 126884, + "feature_imdb_id": 743374 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" Walking on Water: Part 2", + "feature_id": 126879, + "feature_imdb_id": 1167302 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Breaking Glass: Part 1", + "feature_id": 126885, + "feature_imdb_id": 743354 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Breaking Glass: Part 2", + "feature_id": 126881, + "feature_imdb_id": 1167289 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" Final Cut: Part 1", + "feature_id": 126887, + "feature_imdb_id": 743360 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" Final Cut: Part 2", + "feature_id": 126882, + "feature_imdb_id": 1167292 + } + ] + }, + { + "season_number": 4, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" In Sight of the Lord: Part 1", + "feature_id": 126888, + "feature_imdb_id": 743362 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" In Sight of the Lord: Part 2", + "feature_id": 126866, + "feature_imdb_id": 1103924 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" False Flag: Part 1", + "feature_id": 126852, + "feature_imdb_id": 743359 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" False Flag: Part 2", + "feature_id": 126857, + "feature_imdb_id": 1167291 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Fugue States: Part 1", + "feature_id": 126880, + "feature_imdb_id": 743361 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Fugue States: Part 2", + "feature_id": 126886, + "feature_imdb_id": 1167293 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" Anger Management: Part 1", + "feature_id": 126889, + "feature_imdb_id": 743351 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" Anger Management: Part 2", + "feature_id": 126874, + "feature_imdb_id": 1167287 + }, + { + "episode_number": 9, + "title": "\"Waking the Dead\" The Hardest Word: Part 1", + "feature_id": 126891, + "feature_imdb_id": 743370 + }, + { + "episode_number": 10, + "title": "\"Waking the Dead\" The Hardest Word: Part 2", + "feature_id": 126890, + "feature_imdb_id": 1167298 + }, + { + "episode_number": 11, + "title": "\"Waking the Dead\" Shadowplay: Part 1", + "feature_id": 126893, + "feature_imdb_id": 743366 + }, + { + "episode_number": 12, + "title": "\"Waking the Dead\" Shadowplay: Part 2", + "feature_id": 126892, + "feature_imdb_id": 1167295 + } + ] + }, + { + "season_number": 5, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Towers of Silence: Part 1", + "feature_id": 126810, + "feature_imdb_id": 743372 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Towers of Silence: Part 2", + "feature_id": 126811, + "feature_imdb_id": 1167300 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" Black Run: Part 1", + "feature_id": 126814, + "feature_imdb_id": 743352 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" Black Run: Part 2", + "feature_id": 126808, + "feature_imdb_id": 1167288 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Subterraneans: Part 1", + "feature_id": 126816, + "feature_imdb_id": 743369 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Subterraneans: Part 2", + "feature_id": 126818, + "feature_imdb_id": 1167297 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" Straw Dog: Part 1", + "feature_id": 126820, + "feature_imdb_id": 743368 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" Straw Dog: Part 2", + "feature_id": 126822, + "feature_imdb_id": 1167296 + }, + { + "episode_number": 9, + "title": "\"Waking the Dead\" Undertow: Part 1", + "feature_id": 126819, + "feature_imdb_id": 743373 + }, + { + "episode_number": 10, + "title": "\"Waking the Dead\" Undertow: Part 2", + "feature_id": 126821, + "feature_imdb_id": 1167301 + }, + { + "episode_number": 11, + "title": "\"Waking the Dead\" Cold Fusion: Part 1", + "feature_id": 126823, + "feature_imdb_id": 743356 + }, + { + "episode_number": 12, + "title": "\"Waking the Dead\" Cold Fusion: Part 2", + "feature_id": 126817, + "feature_imdb_id": 1167290 + } + ] + }, + { + "season_number": 6, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Wren Boys: Part 1", + "feature_id": 126895, + "feature_imdb_id": 930851 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Wren Boys: Part 2", + "feature_id": 126855, + "feature_imdb_id": 932475 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" Deus Ex Machina: Part 1", + "feature_id": 126894, + "feature_imdb_id": 932472 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" Deus Ex Machina: Part 2", + "feature_id": 126896, + "feature_imdb_id": 932473 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" The Fall: Part 1", + "feature_id": 126898, + "feature_imdb_id": 875569 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" The Fall: Part 2", + "feature_id": 126873, + "feature_imdb_id": 938259 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" Mask of Sanity: Part 1", + "feature_id": 126899, + "feature_imdb_id": 952542 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" Mask of Sanity: Part 2", + "feature_id": 126902, + "feature_imdb_id": 952543 + }, + { + "episode_number": 9, + "title": "\"Waking the Dead\" Double Bind: Part 1", + "feature_id": 126897, + "feature_imdb_id": 952540 + }, + { + "episode_number": 10, + "title": "\"Waking the Dead\" Double Bind: Part 2", + "feature_id": 126903, + "feature_imdb_id": 952541 + }, + { + "episode_number": 11, + "title": "\"Waking the Dead\" Yahrzeit: Part 1", + "feature_id": 126901, + "feature_imdb_id": 892727 + }, + { + "episode_number": 12, + "title": "\"Waking the Dead\" Yahrzeit: Part 2", + "feature_id": 126900, + "feature_imdb_id": 942279 + } + ] + }, + { + "season_number": 7, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Missing Persons: Part 1", + "feature_id": 126825, + "feature_imdb_id": 1215441 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Missing Persons: Part 2", + "feature_id": 126824, + "feature_imdb_id": 1215442 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" Sins: Part 1", + "feature_id": 126815, + "feature_imdb_id": 1218284 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" Sins: Part 2", + "feature_id": 126826, + "feature_imdb_id": 1218285 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Duty and Honour: Part 1", + "feature_id": 126828, + "feature_imdb_id": 1221781 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Duty and Honour: Part 2", + "feature_id": 126830, + "feature_imdb_id": 1221782 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" Skin: Part 1", + "feature_id": 126829, + "feature_imdb_id": 1225236 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" Skin: Part 2", + "feature_id": 126831, + "feature_imdb_id": 1225237 + }, + { + "episode_number": 9, + "title": "\"Waking the Dead\" Wounds: Part 1", + "feature_id": 126827, + "feature_imdb_id": 1227115 + }, + { + "episode_number": 10, + "title": "\"Waking the Dead\" Wounds: Part 2", + "feature_id": 126834, + "feature_imdb_id": 1227116 + }, + { + "episode_number": 11, + "title": "\"Waking the Dead\" Pieta: Part 1", + "feature_id": 126833, + "feature_imdb_id": 1231221 + }, + { + "episode_number": 12, + "title": "\"Waking the Dead\" Pietà: Part 2", + "feature_id": 126832, + "feature_imdb_id": 1231222 + } + ] + }, + { + "season_number": 8, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Magdalene 26: Part 1", + "feature_id": 126836, + "feature_imdb_id": 1506431 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Magdalene 26: Part 2", + "feature_id": 126839, + "feature_imdb_id": 1506432 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" End of the Night: Part 1", + "feature_id": 126837, + "feature_imdb_id": 1509622 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" End of the Night: Part 2", + "feature_id": 126838, + "feature_imdb_id": 1509623 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Substitute: Part 1", + "feature_id": 126840, + "feature_imdb_id": 1513669 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Substitute: Part 2", + "feature_id": 126842, + "feature_imdb_id": 1514406 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" End Game: Part 1", + "feature_id": 126846, + "feature_imdb_id": 1519231 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" End Game: Part 2", + "feature_id": 126843, + "feature_imdb_id": 1519232 + } + ] + }, + { + "season_number": 9, + "episodes": [ + { + "episode_number": 1, + "title": "\"Waking the Dead\" Harbinger: Part 1", + "feature_id": 126856, + "feature_imdb_id": 1862440 + }, + { + "episode_number": 2, + "title": "\"Waking the Dead\" Harbinger: Part 2", + "feature_id": 126853, + "feature_imdb_id": 1862441 + }, + { + "episode_number": 3, + "title": "\"Waking the Dead\" Care: Part 1", + "feature_id": 126844, + "feature_imdb_id": 1865234 + }, + { + "episode_number": 4, + "title": "\"Waking the Dead\" Care: Part 2", + "feature_id": 126845, + "feature_imdb_id": 1865235 + }, + { + "episode_number": 5, + "title": "\"Waking the Dead\" Solidarity: Part 1", + "feature_id": 126849, + "feature_imdb_id": 1869183 + }, + { + "episode_number": 6, + "title": "\"Waking the Dead\" Solidarity: Part 2", + "feature_id": 126851, + "feature_imdb_id": 1869184 + }, + { + "episode_number": 7, + "title": "\"Waking the Dead\" Conviction: Part 1", + "feature_id": 126848, + "feature_imdb_id": 1877523 + }, + { + "episode_number": 8, + "title": "\"Waking the Dead\" Conviction: Part 2", + "feature_id": 126841, + "feature_imdb_id": 1877524 + }, + { + "episode_number": 9, + "title": "\"Waking the Dead\" Waterloo, Part 1", + "feature_id": 126850, + "feature_imdb_id": 1886367 + }, + { + "episode_number": 10, + "title": "\"Waking the Dead\" Waterloo, Part 2", + "feature_id": 126847, + "feature_imdb_id": 1886368 + } + ] + } + ] + } + }, + { + "id": "126842", + "type": "feature", + "attributes": { + "title": "\"Waking the Dead\" Substitute: Part 2", + "original_title": "", + "year": "2009", + "subtitles_counts": { + "nl": 1, + "en": 1, + "sr": 1, + "es": 1 + }, + "subtitles_count": 4, + "seasons_count": 0, + "parent_title": "Waking the Dead", + "season_number": 8, + "episode_number": 6, + "imdb_id": 1514406, + "tmdb_id": "", + "parent_imdb_id": 259733, + "feature_id": "126842", + "title_aka": [ + "\"Waking the Dead\" Substitute: Part 2" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead/seasons/8/episodes/6-waking-the-dead-substitute-part-2", + "img_url": "https://s9.osdb.link/features/2/4/8/126842.jpg", + "seasons": [] + } + }, + { + "id": "126810", + "type": "feature", + "attributes": { + "title": "\"Waking the Dead\" Towers of Silence: Part 1", + "original_title": "", + "year": "2005", + "subtitles_counts": { + "es": 10, + "en": 4, + "pl": 3, + "pt-PT": 3, + "bg": 2, + "he": 2, + "fa": 2, + "ru": 2, + "sr": 2, + "tr": 2, + "ro": 2, + "pt-BR": 2, + "ar": 1, + "bs": 1, + "cs": 1, + "nl": 1, + "et": 1, + "fi": 1, + "fr": 1, + "el": 1, + "hr": 1, + "hu": 1, + "id": 1, + "ja": 1, + "sk": 1, + "sl": 1, + "th": 1, + "vi": 1 + }, + "subtitles_count": 52, + "seasons_count": 0, + "parent_title": "Waking the Dead", + "season_number": 5, + "episode_number": 1, + "imdb_id": 743372, + "tmdb_id": "", + "parent_imdb_id": 259733, + "feature_id": "126810", + "title_aka": [ + "\"Waking the Dead\" Towers of Silence: Part 1" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead/seasons/5/episodes/1-waking-the-dead-towers-of-silence-part-1", + "img_url": "https://s9.osdb.link/features/0/1/8/126810.jpg", + "seasons": [] + } + }, + { + "id": "646786", + "type": "feature", + "attributes": { + "title": "Waking the Dead", + "original_title": "Waking the Dead", + "year": "2000", + "subtitles_counts": { + "en": 68, + "es": 41, + "nl": 40, + "ro": 40, + "ru": 30, + "sr": 21, + "pl": 7, + "pt-BR": 4, + "hr": 3, + "pt-PT": 3, + "bg": 2, + "el": 2, + "he": 2, + "fa": 2, + "sl": 2, + "tr": 2, + "ar": 1, + "bs": 1, + "cs": 1, + "et": 1, + "fi": 1, + "fr": 1, + "hu": 1, + "id": 1, + "ja": 1, + "sk": 1, + "th": 1, + "vi": 1 + }, + "subtitles_count": 25, + "seasons_count": 0, + "parent_title": "", + "season_number": 0, + "episode_number": "", + "imdb_id": 127349, + "tmdb_id": 37722, + "parent_imdb_id": "", + "feature_id": "646786", + "title_aka": [ + "Waking the Dead", + " Resucitar un amor", + " Le Fantôme de Sarah Williams", + " Szerelmem szelleme", + " Amor Maior que a Vida", + " Пробуждая мертвецов", + " 死亡中惊醒" + ], + "feature_type": "Movie", + "url": "https://www.opensubtitles.com/en/movies/2000-waking-the-dead-5559", + "img_url": "https://s9.osdb.link/features/6/8/7/646786.jpg", + "seasons": [] + } + }, + { + "id": "126847", + "type": "feature", + "attributes": { + "title": "\"Waking the Dead\" Waterloo, Part 2", + "original_title": "", + "year": "2011", + "subtitles_counts": { + "nl": 1, + "en": 1, + "sr": 1, + "ro": 1 + }, + "subtitles_count": 4, + "seasons_count": 0, + "parent_title": "Waking the Dead", + "season_number": 9, + "episode_number": 10, + "imdb_id": 1886368, + "tmdb_id": "", + "parent_imdb_id": 259733, + "feature_id": "126847", + "title_aka": [ + "\"Waking the Dead\" Waterloo", + " Part 2" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead/seasons/9/episodes/10-waking-the-dead-waterloo-part-2", + "img_url": "", + "seasons": [] + } + }, + { + "id": "126857", + "type": "feature", + "attributes": { + "title": "\"Waking the Dead\" False Flag: Part 2", + "original_title": "", + "year": "2004", + "subtitles_counts": { + "ru": 2, + "ro": 2, + "en": 1 + }, + "subtitles_count": 5, + "seasons_count": 0, + "parent_title": "Waking the Dead", + "season_number": 4, + "episode_number": 4, + "imdb_id": 1167291, + "tmdb_id": "", + "parent_imdb_id": 259733, + "feature_id": "126857", + "title_aka": [ + "\"Waking the Dead\" False Flag: Part 2" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead/seasons/4/episodes/4-waking-the-dead-false-flag-part-2", + "img_url": "https://s9.osdb.link/features/7/5/8/126857.jpg", + "seasons": [] + } + }, + { + "id": "62212", + "type": "feature", + "attributes": { + "title": "\"Point Pleasant\" Waking the Dead", + "original_title": "", + "year": "2005", + "subtitles_counts": { + "pl": 3, + "en": 2, + "hu": 2, + "cs": 1, + "nl": 1, + "et": 1, + "fi": 1, + "fr": 1, + "el": 1, + "pt-PT": 1, + "ro": 1, + "pt-BR": 1 + }, + "subtitles_count": 16, + "seasons_count": 0, + "parent_title": "Point Pleasant", + "season_number": 1, + "episode_number": 9, + "imdb_id": 676101, + "tmdb_id": "", + "parent_imdb_id": 435576, + "feature_id": "62212", + "title_aka": [ + "\"Point Pleasant\" Waking the Dead" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2005-point-pleasant/seasons/1/episodes/9-point-pleasant-waking-the-dead", + "img_url": "https://s9.osdb.link/features/2/1/2/62212.jpg", + "seasons": [] + } + }, + { + "id": "126869", + "type": "feature", + "attributes": { + "title": "\"Waking the Dead\" Every Breath You Take: Part 2", + "original_title": "", + "year": "2001", + "subtitles_counts": { + "en": 1, + "es": 1, + "ro": 1 + }, + "subtitles_count": 3, + "seasons_count": 0, + "parent_title": "Waking the Dead", + "season_number": 1, + "episode_number": 10, + "imdb_id": 936018, + "tmdb_id": "", + "parent_imdb_id": 259733, + "feature_id": "126869", + "title_aka": [ + "\"Waking the Dead\" Every Breath You Take: Part 2" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead/seasons/1/episodes/10-waking-the-dead-every-breath-you-take-part-2", + "img_url": "", + "seasons": [] + } + }, + { + "id": "126826", + "type": "feature", + "attributes": { + "title": "\"Waking the Dead\" Sins: Part 2", + "original_title": "", + "year": "2008", + "subtitles_counts": { + "ru": 2, + "nl": 1, + "en": 1 + }, + "subtitles_count": 4, + "seasons_count": 0, + "parent_title": "Waking the Dead", + "season_number": 7, + "episode_number": 4, + "imdb_id": 1218285, + "tmdb_id": "", + "parent_imdb_id": 259733, + "feature_id": "126826", + "title_aka": [ + "\"Waking the Dead\" Sins: Part 2" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead/seasons/7/episodes/4-waking-the-dead-sins-part-2", + "img_url": "", + "seasons": [] + } + }, + { + "id": "126837", + "type": "feature", + "attributes": { + "title": "\"Waking the Dead\" End of the Night: Part 1", + "original_title": "", + "year": "2009", + "subtitles_counts": { + "nl": 2, + "en": 1, + "sr": 1, + "es": 1 + }, + "subtitles_count": 5, + "seasons_count": 0, + "parent_title": "Waking the Dead", + "season_number": 8, + "episode_number": 3, + "imdb_id": 1509622, + "tmdb_id": "", + "parent_imdb_id": 259733, + "feature_id": "126837", + "title_aka": [ + "\"Waking the Dead\" End of the Night: Part 1" + ], + "feature_type": "Episode", + "url": "https://www.opensubtitles.com/en/tvshows/2000-waking-the-dead/seasons/8/episodes/3-waking-the-dead-end-of-the-night-part-1", + "img_url": "https://s9.osdb.link/features/7/3/8/126837.jpg", + "seasons": [] + } + } + ], + "value": "this is test how it is rendered" + } + }, + "example": { + "example": { + "value": { + "data": [ + { + "id": "646193", + "type": "feature", + "attributes": { + "title": "The Matrix", + "original_title": "The Matrix", + "year": "1999", + "subtitles_counts": { + "pl": 151, + "en": 120, + "tr": 68, + "ro": 57, + "es": 55, + "cs": 54, + "pt-BR": 49, + "sl": 41, + "pt-PT": 39, + "sr": 39, + "el": 38, + "he": 31, + "bg": 30, + "nl": 24, + "fr": 20, + "fi": 19, + "hu": 17, + "ar": 16, + "ru": 15, + "hr": 14, + "da": 13, + "et": 11, + "sv": 11, + "sq": 10, + "de": 8, + "bs": 7, + "it": 7, + "ko": 7, + "no": 7, + "fa": 7, + "sk": 7, + "zh-CN": 6, + "mk": 6, + "ms": 4, + "zh-TW": 4, + "bn": 3, + "id": 3, + "lt": 3, + "is": 2, + "ja": 2, + "th": 2, + "my": 1, + "ca": 1, + "hi": 1, + "ml": 1, + "mn": 1, + "uk": 1, + "vi": 1 + }, + "subtitles_count": 1034, + "seasons_count": 0, + "parent_title": "", + "season_number": 0, + "imdb_id": 133093, + "tmdb_id": 603, + "feature_id": "646193", + "title_aka": [ + "Матрицата", + " The Matrix", + " Matrix", + " ماتریکس", + " מטריקס", + " Mátrix", + " マトリックス", + " 매트릭스", + " Matrica", + " 黑客帝国", + " Матрица", + " Матрикс", + " เดอะ เมทริกซ์ : เพาะพันธุ์มนุษย์เหนือโลก 2199", + " Матриця" + ], + "feature_type": "Movie", + "url": "https://www.opensubtitles.com/en/movies/1999-the-matrix", + "img_url": "https://m.media-amazon.com/images/M/MV5BNzQzOTk3OTAtNDQ0Zi00ZTVkLWI0MTEtMDllZjNkYzNjNTc4L2ltYWdlXkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_UX182_CR0,0,182,268_AL_.jpg", + "seasons": [] + } + }, + { + "id": "646060", + "type": "feature", + "attributes": { + "title": "The Matrix Revolutions", + "original_title": "The Matrix Revolutions", + "year": "2003", + "subtitles_counts": { + "tr": 29, + "es": 26, + "en": 22, + "sr": 22, + "ro": 20, + "pt-BR": 16, + "bg": 15, + "cs": 14, + "pt-PT": 12, + "sl": 11, + "pl": 10, + "fi": 9, + "fr": 8, + "ar": 7, + "et": 7, + "nl": 6, + "he": 6, + "el": 5, + "hu": 5, + "da": 4, + "mk": 4, + "hr": 3, + "id": 3, + "sk": 3, + "sq": 1, + "zh-CN": 1, + "de": 1, + "is": 1, + "it": 1, + "lt": 1, + "no": 1, + "ru": 1, + "sv": 1 + }, + "subtitles_count": 818, + "seasons_count": 0, + "parent_title": "", + "season_number": 0, + "imdb_id": 242653, + "tmdb_id": 605, + "feature_id": "646060", + "title_aka": [ + "Матрицата: Революции", + " Matrix Revolutions", + " The Matrix: Revolutions", + " The Matrix Revolutions", + " Matrix revolutions", + " انقلاب‌های ماتریکس", + " מטריקס רבולושנס", + " Mátrix - Forradalmak", + " 매트릭스 3: 레볼루션", + " Matrix - Revoluții", + " Матрица: Революция", + " ปฎิวัติมนุษย์เหนือโลก", + " Матриця: Революція", + " 黑客帝国3:矩阵革命" + ], + "feature_type": "Movie", + "url": "https://www.opensubtitles.com/en/movies/2003-the-matrix-revolutions", + "img_url": "https://m.media-amazon.com/images/M/MV5BNzNlZTZjMDctZjYwNi00NzljLWIwN2QtZWZmYmJiYzQ0MTk2XkEyXkFqcGdeQXVyNTAyODkwOQ@@._V1_UX182_CR0,0,182,268_AL_.jpg", + "seasons": [] + } + } + ] + } + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Api-Key": [] + } + ] + } + }, + "/subtitles": { + "get": { + "tags": [ + "Subtitles" + ], + "summary": "Search for subtitles", + "description": "Find subtitle for a video file. All parameters can be combined following various logics: searching by a specific external id (imdb, tmdb), a file moviehash, or a simple text query.\n\n\n> Something wrong? Read about [common mistakes and best practices](docs/2-Best-Practices.md). \n\n> Getting no results? Follow HTTP redirects! ```curl --location``` and use verbose mode\n\n> Use ```imdb_id for``` movie or episode. Use ```parent_imdb_id``` for TV Shows\n\n\n\nImplement the logic that best fits your needs, keeping in mind the following guidelines:\n\n- If you can obtain the moviehash from the file, please send it along.\n- If you possess the ID, whether it's IMDB or TMDB, send it instead of a query, as an ID provides more precision.\n- When searching for TV show episodes, it is recommended to send the parent ID, along with the episode and season number for optimal results. If you have the unique ID of an episode, only send this ID, excluding the episode or season number.\n- Include the filename as a query parameter along with the moviehash for improved results. If your filenames are generally irrelevant, such as dynamically generated filenames from a streaming service, there's no need to include them.\n- Consider treating parameters as filters rather than additional criteria. If you have a specific ID and send a query with conflicting data, like a wrong year, it could result in fewer matches.\n- Explore querying the /features endpoint to gather the exact list of available episodes.\n- Keep in mind that this is a collaborative project where subtitles are submitted by users, filtered by admins, and movie/show results are processed through various APIs. Occasionally, errors may occur, and we depend on user feedback to address and rectify them.\n\n\n> Avoid http redirection by sending request parameters sorted and without default values, and send all queries in lowercase. Remove leading zeroes in ID parameters (IMDB ID, TMDB ID...)\n\n### Moviehash \nIf a ```moviehash``` is sent with a request, a ```moviehash_match``` boolean field will be added to the response.\n\nThe matching subtitles will always come first in the response.\n\n\n### Ordering\n\n\n> If possible, don't order results, because sorting on server is \"expensive, time consuming operation\" and also you have much higher chance to get cached result when not using this function.\n\nYou can order the results using the ```order_by``` parameter. Ordering is possible on the following fields:\n```language```, ```download_count```, ```new_download_count```, ```hearing_impaired```, ```hd```, ```fps```, ```votes```, ```points```, ```ratings```, ```from_trusted```, ```foreign_parts_only```, ```ai_translated```, ```machine_translated```, ```upload_date```, ```release```, ```comments```\n\nChange the order direction with *order_direction* (asc/desc)\n\n### Final notes\n```ai_translated``` (default include in search results) subtitles should be much better quality than ```machine_translated``` subtitles (excluded in search results).", + "operationId": "subtitles", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "ID of the movie or episode", + "schema": { + "type": "integer" + } + }, + { + "name": "imdb_id", + "in": "query", + "description": "IMDB ID of the movie or episode", + "schema": { + "type": "integer" + } + }, + { + "name": "tmdb_id", + "in": "query", + "description": "TMDB ID of the movie or episode", + "schema": { + "type": "integer" + } + }, + { + "name": "type", + "in": "query", + "description": "movie, episode or all, (default: all) ", + "schema": { + "type": "string" + } + }, + { + "name": "query", + "in": "query", + "description": "file name or text search", + "schema": { + "type": "string" + } + }, + { + "name": "languages", + "in": "query", + "description": "Language code(s), comma separated, sorted in alphabetical order (en,fr)", + "schema": { + "type": "string" + } + }, + { + "name": "moviehash", + "in": "query", + "description": "Moviehash of the moviefile", + "schema": { + "maxLength": 16, + "minLength": 16, + "pattern": "^[a-f0-9]{16}$", + "type": "string" + } + }, + { + "name": "uploader_id", + "in": "query", + "description": "To be used alone - for user uploads listing", + "schema": { + "type": "integer" + } + }, + { + "name": "hearing_impaired", + "in": "query", + "description": "include, exclude, only. (default: include)", + "schema": { + "type": "string" + } + }, + { + "name": "foreign_parts_only", + "in": "query", + "description": "exclude, include, only (default: include)", + "schema": { + "type": "string" + } + }, + { + "name": "trusted_sources", + "in": "query", + "description": "include, only (default: include)", + "schema": { + "type": "string" + } + }, + { + "name": "machine_translated", + "in": "query", + "description": "exclude, include (default: exclude)", + "schema": { + "type": "string" + } + }, + { + "name": "ai_translated", + "in": "query", + "description": "exclude, include (default: include)", + "schema": { + "type": "string" + } + }, + { + "name": "order_by", + "in": "query", + "description": "Order of the returned results, accept any of above fields", + "schema": { + "type": "string" + } + }, + { + "name": "order_direction", + "in": "query", + "description": "Order direction of the returned results (asc,desc)", + "schema": { + "type": "string" + } + }, + { + "name": "parent_feature_id", + "in": "query", + "description": "For Tvshows", + "schema": { + "type": "integer" + } + }, + { + "name": "parent_imdb_id", + "in": "query", + "description": "For Tvshows", + "schema": { + "type": "integer" + } + }, + { + "name": "parent_tmdb_id", + "in": "query", + "description": "For Tvshows", + "schema": { + "type": "integer" + } + }, + { + "name": "season_number", + "in": "query", + "description": "For Tvshows\n", + "schema": { + "type": "integer" + } + }, + { + "name": "episode_number", + "in": "query", + "description": "For Tvshows", + "schema": { + "type": "integer" + } + }, + { + "name": "year", + "in": "query", + "description": "Filter by movie/episode year", + "schema": { + "type": "integer" + } + }, + { + "name": "moviehash_match", + "in": "query", + "description": "include, only (default: include)", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Results page to display", + "schema": { + "type": "integer" + } + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ], + "responses": { + "200": { + "description": "Find subtitles for a video file ", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "", + "properties": { + "total_pages": { + "type": "number" + }, + "total_count": { + "type": "number" + }, + "page": { + "type": "number" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Subtitle" + } + } + }, + "required": [ + "total_pages", + "total_count", + "page", + "data" + ] + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Api-Key": [] + } + ] + } + }, + "/download": { + "post": { + "tags": [ + "Download" + ], + "summary": "Download", + "description": "Request a download url for a subtitle. Subtitle file in temporary URL will be always in UTF-8 encoding.\n\n\n\n> VERY IMPORTANT: In HTTP request must be both headers: ```Api-Key``` and ```Authorization``` stoplight.io doesn't allow to use in shown example both headers\n\n\n> The download count is calculated on this action, not the file download itself\n\n> IN and OUT FPS must be indicated for subtitle conversions, we want to make sure you know what you are doing, and therefore collected the current FPS from the subtitle search result, or calculated it somehow.\n\n\n\n> The download URL is temporary, and cannot be used more than 3 hours, so do not cache it, but you can download the file more than once if needed.", + "operationId": "download", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "x-examples": { + "example-1": { + "file_id": "", + "sub_format": "", + "file_name": "", + "in_fps": "", + "out_fps": "", + "timeshift": "", + "force_download": "" + } + }, + "required": [ + "file_id" + ], + "properties": { + "file_id": { + "type": "integer", + "description": "file_id from /subtitles search results", + "format": "int32", + "example": 123 + }, + "sub_format": { + "type": "string", + "description": "from /infos/formats" + }, + "file_name": { + "type": "string", + "description": "desired file name" + }, + "in_fps": { + "type": "number", + "description": "used for conversions, in_fps and out_fps must then be indicated" + }, + "out_fps": { + "type": "number", + "description": "used for conversions, in_fps and out_fps must then be indicated" + }, + "timeshift": { + "type": "number", + "description": "delay to add or remove to the subtitle, + or - value, in seconds, i.e. 2.5s or -1s " + }, + "force_download": { + "type": "boolean", + "description": "(1/0) set subtitle file headers to \"application/force-download\"" + } + } + }, + "examples": { + "example-1": { + "value": { + "file_id": 123 + } + } + } + } + }, + "required": false, + "description": "" + }, + "responses": { + "200": { + "description": "Request a download URL for a subtitle. \n", + "content": { + "application/json": { + "schema": { + "description": "", + "type": "object", + "properties": { + "link": { + "type": "string", + "minLength": 1 + }, + "file_name": { + "type": "string", + "minLength": 1 + }, + "requests": { + "type": "number" + }, + "remaining": { + "type": "number" + }, + "message": { + "type": "string", + "minLength": 1 + }, + "reset_time": { + "type": "string", + "minLength": 1 + }, + "reset_time_utc": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "link", + "file_name", + "requests", + "remaining", + "message", + "reset_time", + "reset_time_utc" + ], + "x-examples": { + "example-1": { + "link": "https://www.opensubtitles.com/download/A184A5EA6302F2CA7FD9D49BCEA49A1F36662BBEFB8C9B0ECDC9BB6CAF4BF09A5AA8D7B95C7FBD01615021D1973BAC18D431A8E6A1F627E4617341E8508A6968532088A68B6DDDA996C0116E2CE6F778ED9096A9CAB942B42B59C4EA93F1A7D61FCD6CBBC29C720EBD40CE674A55375862F00981E5D2F315A0982766A2004E0ED0AD9ADABEB506A638F1B829DBC2BE15979F22DA123523967F4D4069BC32098F1086F09AAA776CC365ED744633FD5FA7160B65A2C83539DF30134F5BE6272E46019AF9FD2423AFE12E1DC8642CDB56B8FEB9A4C1F30BF68EF431A3D4ABD3A7E44559E3E572210E5A5A33EC282D3445C537C5DA9DA598300A9900FA1B3B92983FD1504FDDFB34F89E409BF03EC662FC5734F25843C277A64B7C603156926FC6C74AA1D14AABEA6E20/subfile/castle.rock.s01e03.webrip.x264-tbs.ettv.-eng.ro.srt", + "file_name": "castle.rock.s01e03.webrip.x264-tbs.ettv.-eng.ro.srt", + "requests": 3, + "remaining": 97, + "message": "Your quota will be renewed in 07 hours and 40 minutes (2022-04-08 13:03:15 UTC) ", + "reset_time": "07 hours and 40 minutes", + "reset_time_utc": "2022-04-08T13:03:15.000Z" + } + } + }, + "examples": { + "example-1": { + "value": { + "link": "https://www.opensubtitles.com/download/A184A5EA6302F2CA7FD9D49BCEA49A1F36662BBEFB8C9B0ECDC9BB6CAF4BF09A5AA8D7B95C7FBD01615021D1973BAC18D431A8E6A1F627E4617341E8508A6968532088A68B6DDDA996C0116E2CE6F778ED9096A9CAB942B42B59C4EA93F1A7D61FCD6CBBC29C720EBD40CE674A55375862F00981E5D2F315A0982766A2004E0ED0AD9ADABEB506A638F1B829DBC2BE15979F22DA123523967F4D4069BC32098F1086F09AAA776CC365ED744633FD5FA7160B65A2C83539DF30134F5BE6272E46019AF9FD2423AFE12E1DC8642CDB56B8FEB9A4C1F30BF68EF431A3D4ABD3A7E44559E3E572210E5A5A33EC282D3445C537C5DA9DA598300A9900FA1B3B92983FD1504FDDFB34F89E409BF03EC662FC5734F25843C277A64B7C603156926FC6C74AA1D14AABEA6E20/subfile/castle.rock.s01e03.webrip.x264-tbs.ettv.-eng.ro.srt", + "file_name": "castle.rock.s01e03.webrip.x264-tbs.ettv.-eng.ro.srt", + "requests": 3, + "remaining": 97, + "message": "Your quota will be renewed in 07 hours and 30 minutes (2022-04-08 13:03:16 UTC) ", + "reset_time": "07 hours and 30 minutes", + "reset_time_utc": "2022-04-08T13:03:16.000Z" + } + } + } + } + } + } + }, + "deprecated": false, + "security": [ + { + "Bearer": [] + }, + { + "Api-Key": [] + } + ], + "x-codegen-request-body-name": "body" + }, + "parameters": [] + }, + "/utilities/guessit": { + "get": { + "tags": [ + "Utilities" + ], + "summary": "Guessit", + "description": "Extracts as much information as possible from a video filename.\n\nIt has a very powerful matcher that allows to guess properties from a video using its filename only. This matcher works with both movies and tv shows episodes.\n\nThis is a simple implementation of the python guessit library.\nhttps://guessit-io.github.io/guessit/\n\nFind examples of the returned data.\nhttps://guessit-io.github.io/guessit/properties/", + "operationId": "guessit", + "parameters": [ + { + "name": "filename", + "in": "query", + "description": "File name", + "schema": { + "type": "string" + } + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "User-Agent", + "description": "<<{{APP_NAME}} v{{APP_VERSION}}>>" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "required": [ + "audio_channels", + "audio_codec", + "language", + "other", + "release_group", + "screen_size", + "source", + "streaming_service", + "subtitle_language", + "title", + "type", + "video_codec", + "year" + ], + "type": "object", + "properties": { + "title": { + "minLength": 1, + "type": "string" + }, + "year": { + "type": "number" + }, + "language": { + "minLength": 1, + "type": "string" + }, + "subtitle_language": { + "minLength": 1, + "type": "string" + }, + "screen_size": { + "minLength": 1, + "type": "string" + }, + "streaming_service": { + "minLength": 1, + "type": "string" + }, + "source": { + "minLength": 1, + "type": "string" + }, + "other": { + "minLength": 1, + "type": "string" + }, + "audio_codec": { + "minLength": 1, + "type": "string" + }, + "audio_channels": { + "minLength": 1, + "type": "string" + }, + "video_codec": { + "minLength": 1, + "type": "string" + }, + "release_group": { + "minLength": 1, + "type": "string" + }, + "type": { + "minLength": 1, + "type": "string" + } + }, + "description": "" + } + } + } + } + }, + "security": [ + { + "Api-Key": [] + } + ] + } + } + }, + "components": { + "schemas": { + "Subtitle": { + "type": "object", + "x-tags": [ + "Models" + ], + "properties": { + "id": { + "minLength": 1, + "type": "string" + }, + "type": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "type": "object", + "required": [ + "subtitle_id", + "language", + "download_count", + "new_download_count", + "from_trusted", + "foreign_parts_only", + "ai_translated", + "machine_translated", + "upload_date", + "feature_details", + "url", + "files" + ], + "properties": { + "subtitle_id": { + "minLength": 1, + "type": "string" + }, + "language": { + "minLength": 1, + "type": "string" + }, + "download_count": { + "type": "number" + }, + "new_download_count": { + "type": "number" + }, + "hearing_impaired": { + "type": "boolean" + }, + "hd": { + "type": "boolean" + }, + "fps": { + "type": "number" + }, + "votes": { + "type": "number" + }, + "points": { + "type": "number" + }, + "ratings": { + "type": "number" + }, + "from_trusted": { + "type": "boolean" + }, + "foreign_parts_only": { + "type": "boolean" + }, + "ai_translated": { + "type": "boolean" + }, + "machine_translated": { + "type": "boolean" + }, + "upload_date": { + "minLength": 1, + "type": "string" + }, + "release": { + "minLength": 1, + "type": "string" + }, + "comments": { + "type": "string" + }, + "legacy_subtitle_id": { + "type": "number" + }, + "uploader": { + "type": "object", + "properties": { + "uploader_id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "rank": { + "type": "string" + } + } + }, + "feature_details": { + "type": "object", + "required": [ + "feature_id", + "feature_type", + "title", + "movie_name", + "imdb_id" + ], + "properties": { + "feature_id": { + "type": "number" + }, + "feature_type": { + "minLength": 1, + "type": "string" + }, + "year": { + "type": "number" + }, + "title": { + "minLength": 1, + "type": "string" + }, + "movie_name": { + "minLength": 1, + "type": "string" + }, + "imdb_id": { + "type": "number" + }, + "tmdb_id": { + "type": "number" + } + } + }, + "url": { + "minLength": 1, + "type": "string" + }, + "related_links": { + "type": "array", + "items": { + "type": "object" + } + }, + "files": { + "minItems": 1, + "uniqueItems": true, + "type": "array", + "items": { + "type": "object", + "properties": { + "file_id": { + "type": "number" + }, + "cd_number": { + "type": "number" + }, + "file_name": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "file_id", + "file_name" + ] + } + } + } + } + }, + "required": [ + "id", + "type", + "attributes" + ] + }, + "Feature-Tvshow": { + "required": [ + "attributes", + "id", + "type" + ], + "type": "object", + "properties": { + "id": { + "minLength": 1, + "type": "string" + }, + "type": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "required": [ + "feature_id", + "imdb_id", + "img_url", + "original_title", + "seasons", + "subtitles_count", + "subtitles_counts", + "title", + "title_aka", + "tmdb_id", + "url", + "year" + ], + "type": "object", + "properties": { + "title": { + "minLength": 1, + "type": "string" + }, + "original_title": { + "minLength": 1, + "type": "string" + }, + "year": { + "minLength": 1, + "type": "string" + }, + "imdb_id": { + "type": "number" + }, + "tmdb_id": { + "type": "number" + }, + "title_aka": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "feature_id": { + "minLength": 1, + "type": "string" + }, + "url": { + "minLength": 1, + "type": "string" + }, + "img_url": { + "minLength": 1, + "type": "string" + }, + "subtitles_counts": { + "required": [ + "ar", + "bg", + "bs", + "ca", + "cs", + "da", + "de", + "el", + "en", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hr", + "hu", + "id", + "it", + "ja", + "ko", + "mk", + "nl", + "no", + "pl", + "pt-BR", + "pt-PT", + "ro", + "ru", + "sk", + "sl", + "sr", + "sv", + "th", + "tr", + "vi", + "zh-CN", + "zh-TW" + ], + "type": "object", + "properties": { + "pl": { + "type": "number" + }, + "en": { + "type": "number" + }, + "pt-BR": { + "type": "number" + }, + "ro": { + "type": "number" + }, + "nl": { + "type": "number" + }, + "pt-PT": { + "type": "number" + }, + "es": { + "type": "number" + }, + "he": { + "type": "number" + }, + "hu": { + "type": "number" + }, + "el": { + "type": "number" + }, + "fr": { + "type": "number" + }, + "tr": { + "type": "number" + }, + "cs": { + "type": "number" + }, + "fi": { + "type": "number" + }, + "ar": { + "type": "number" + }, + "hr": { + "type": "number" + }, + "sl": { + "type": "number" + }, + "bg": { + "type": "number" + }, + "sr": { + "type": "number" + }, + "sv": { + "type": "number" + }, + "de": { + "type": "number" + }, + "et": { + "type": "number" + }, + "da": { + "type": "number" + }, + "bs": { + "type": "number" + }, + "it": { + "type": "number" + }, + "mk": { + "type": "number" + }, + "ru": { + "type": "number" + }, + "no": { + "type": "number" + }, + "th": { + "type": "number" + }, + "vi": { + "type": "number" + }, + "ja": { + "type": "number" + }, + "fa": { + "type": "number" + }, + "zh-CN": { + "type": "number" + }, + "ca": { + "type": "number" + }, + "id": { + "type": "number" + }, + "sk": { + "type": "number" + }, + "ko": { + "type": "number" + }, + "zh-TW": { + "type": "number" + } + } + }, + "subtitles_count": { + "type": "number" + }, + "seasons": { + "minItems": 1, + "uniqueItems": true, + "type": "array", + "items": { + "required": [ + "season_number" + ], + "type": "object", + "properties": { + "season_number": { + "type": "number" + }, + "episodes": { + "minItems": 1, + "uniqueItems": true, + "type": "array", + "items": { + "required": [ + "episode_number", + "feature_id", + "feature_imdb_id", + "title" + ], + "type": "object", + "properties": { + "episode_number": { + "type": "number" + }, + "title": { + "minLength": 1, + "type": "string" + }, + "feature_id": { + "type": "number" + }, + "feature_imdb_id": { + "type": "number" + } + } + } + } + } + } + } + } + } + }, + "description": "", + "x-tags": [ + "Models" + ] + }, + "Feature-Episode": { + "required": [ + "attributes", + "id", + "type" + ], + "type": "object", + "properties": { + "id": { + "minLength": 1, + "type": "string" + }, + "type": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "required": [ + "episode_number", + "feature_id", + "imdb_id", + "img_url", + "parent_title", + "season_number", + "subtitles_count", + "subtitles_counts", + "title", + "title_aka", + "tmdb_id", + "url", + "year" + ], + "type": "object", + "properties": { + "title": { + "minLength": 1, + "type": "string" + }, + "original_title": { + "type": "object" + }, + "year": { + "minLength": 1, + "type": "string" + }, + "parent_imdb_id": { + "type": "object" + }, + "parent_title": { + "minLength": 1, + "type": "string" + }, + "season_number": { + "type": "number" + }, + "episode_number": { + "type": "number" + }, + "imdb_id": { + "type": "number" + }, + "tmdb_id": { + "type": "number" + }, + "title_aka": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "feature_id": { + "minLength": 1, + "type": "string" + }, + "url": { + "minLength": 1, + "type": "string" + }, + "img_url": { + "minLength": 1, + "type": "string" + }, + "subtitles_counts": { + "required": [ + "ar", + "bg", + "bs", + "cs", + "da", + "de", + "el", + "en", + "es", + "et", + "fi", + "fr", + "he", + "hr", + "hu", + "mk", + "nl", + "no", + "pl", + "pt-BR", + "pt-PT", + "ro", + "ru", + "sl", + "sr", + "sv", + "th", + "tr" + ], + "type": "object", + "properties": { + "pl": { + "type": "number" + }, + "en": { + "type": "number" + }, + "pt-BR": { + "type": "number" + }, + "es": { + "type": "number" + }, + "ro": { + "type": "number" + }, + "nl": { + "type": "number" + }, + "tr": { + "type": "number" + }, + "he": { + "type": "number" + }, + "pt-PT": { + "type": "number" + }, + "cs": { + "type": "number" + }, + "fi": { + "type": "number" + }, + "hu": { + "type": "number" + }, + "ar": { + "type": "number" + }, + "bg": { + "type": "number" + }, + "fr": { + "type": "number" + }, + "sl": { + "type": "number" + }, + "el": { + "type": "number" + }, + "hr": { + "type": "number" + }, + "sr": { + "type": "number" + }, + "et": { + "type": "number" + }, + "sv": { + "type": "number" + }, + "th": { + "type": "number" + }, + "bs": { + "type": "number" + }, + "da": { + "type": "number" + }, + "de": { + "type": "number" + }, + "mk": { + "type": "number" + }, + "no": { + "type": "number" + }, + "ru": { + "type": "number" + } + } + }, + "subtitles_count": { + "type": "number" + } + } + } + }, + "description": "", + "x-tags": [ + "Models" + ] + }, + "Feature-Movie": { + "required": [ + "attributes", + "id", + "type" + ], + "type": "object", + "properties": { + "id": { + "minLength": 1, + "type": "string" + }, + "type": { + "minLength": 1, + "type": "string" + }, + "attributes": { + "required": [ + "feature_id", + "feature_type", + "imdb_id", + "img_url", + "original_title", + "parent_title", + "season_number", + "seasons_count", + "subtitles_count", + "subtitles_counts", + "title", + "title_aka", + "tmdb_id", + "url", + "year" + ], + "type": "object", + "properties": { + "title": { + "minLength": 1, + "type": "string" + }, + "original_title": { + "minLength": 1, + "type": "string" + }, + "year": { + "minLength": 1, + "type": "string" + }, + "subtitles_counts": { + "required": [ + "ar", + "bg", + "bn", + "cs", + "da", + "de", + "el", + "en", + "es", + "et", + "eu", + "fa", + "fi", + "fr", + "he", + "hr", + "hu", + "id", + "it", + "ja", + "ka", + "ko", + "lt", + "mk", + "ml", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt-PT", + "ro", + "ru", + "sk", + "sl", + "sr", + "sv", + "ta", + "tr", + "uk", + "vi", + "ze", + "zh-CN", + "zh-TW" + ], + "type": "object", + "properties": { + "en": { + "type": "number" + }, + "pt-PT": { + "type": "number" + }, + "fi": { + "type": "number" + }, + "pt-BR": { + "type": "number" + }, + "es": { + "type": "number" + }, + "ar": { + "type": "number" + }, + "pl": { + "type": "number" + }, + "sr": { + "type": "number" + }, + "id": { + "type": "number" + }, + "ro": { + "type": "number" + }, + "zh-CN": { + "type": "number" + }, + "nl": { + "type": "number" + }, + "el": { + "type": "number" + }, + "hu": { + "type": "number" + }, + "fr": { + "type": "number" + }, + "sl": { + "type": "number" + }, + "tr": { + "type": "number" + }, + "et": { + "type": "number" + }, + "bg": { + "type": "number" + }, + "cs": { + "type": "number" + }, + "de": { + "type": "number" + }, + "he": { + "type": "number" + }, + "it": { + "type": "number" + }, + "vi": { + "type": "number" + }, + "hr": { + "type": "number" + }, + "ko": { + "type": "number" + }, + "no": { + "type": "number" + }, + "sv": { + "type": "number" + }, + "ta": { + "type": "number" + }, + "eu": { + "type": "number" + }, + "da": { + "type": "number" + }, + "fa": { + "type": "number" + }, + "sk": { + "type": "number" + }, + "uk": { + "type": "number" + }, + "zh-TW": { + "type": "number" + }, + "bn": { + "type": "number" + }, + "ka": { + "type": "number" + }, + "ja": { + "type": "number" + }, + "lt": { + "type": "number" + }, + "mk": { + "type": "number" + }, + "ml": { + "type": "number" + }, + "ms": { + "type": "number" + }, + "ru": { + "type": "number" + }, + "ze": { + "type": "number" + } + } + }, + "subtitles_count": { + "type": "number" + }, + "seasons_count": { + "type": "number" + }, + "parent_title": { + "type": "string" + }, + "season_number": { + "type": "number" + }, + "episode_number": { + "type": "object" + }, + "imdb_id": { + "type": "number" + }, + "tmdb_id": { + "type": "number" + }, + "parent_imdb_id": { + "type": "object" + }, + "feature_id": { + "minLength": 1, + "type": "string" + }, + "title_aka": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "feature_type": { + "minLength": 1, + "type": "string" + }, + "url": { + "minLength": 1, + "type": "string" + }, + "img_url": { + "minLength": 1, + "type": "string" + } + } + } + }, + "description": "", + "x-tags": [ + "Models" + ] + } + }, + "securitySchemes": { + "Api-Key": { + "type": "apiKey", + "description": "Application API key obtained in the user profile of app developer on opensubtitles.com to authorise the **application**", + "name": "Api-Key", + "in": "header" + }, + "Bearer": { + "type": "http", + "description": "User token created in the login endpoint to authorise opensubtitles.com **user**", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "security": [ + { + "Api-Key": [] + }, + { + "Bearer": [] + } + ] +} \ No newline at end of file diff --git a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFilteringTest.java b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFilteringTest.java index 8b9ddbd3..59aa093d 100644 --- a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFilteringTest.java +++ b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/SubtitleFilteringTest.java @@ -1,16 +1,15 @@ package org.lodder.subtools.multisubdownloader.lib.control.subtitles; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; -import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.lodder.subtools.multisubdownloader.settings.model.Settings; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; +import org.lodder.subtools.sublibrary.model.TvRelease; class SubtitleFilteringTest { @@ -31,10 +30,14 @@ void testKeywordMatchFilter() { Release release = createRelease("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.mkv", "DIMENSION"); Subtitle subtitle1 = createSubtitle("Criminal.Minds.S10E12.HDTV.XviD-AFG.srt", "AFG", false, ""); Subtitle subtitle2 = createSubtitle("criminal.minds.1012.hdtv-lol.srt", "lol", false, ""); - Subtitle subtitle3 = createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", true, ""); - Subtitle subtitle4 = createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); - Subtitle subtitle5 = createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, "720p HDTV X264"); - Subtitle subtitle6 = createSubtitle("Criminal.Minds.S10E12.Anonymous.1080p.WEB-DL.DD5.1.H.264-CtrlHD", "CtrlHD", false, ""); + Subtitle subtitle3 = + createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", true, ""); + Subtitle subtitle4 = + createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); + Subtitle subtitle5 = createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, + "720p HDTV X264"); + Subtitle subtitle6 = + createSubtitle("Criminal.Minds.S10E12.Anonymous.1080p.WEB-DL.DD5.1.H.264-CtrlHD", "CtrlHD", false, ""); // only keyword assertThatFilter(new SubtitleFiltering(createSettings(true, false, false))) @@ -54,9 +57,12 @@ void testExactMatchFilter() { Release release = createRelease("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.mkv", "DIMENSION"); Subtitle subtitle1 = createSubtitle("Criminal.Minds.S10E12.HDTV.XviD-AFG.srt", "AFG", false, ""); Subtitle subtitle2 = createSubtitle("criminal.minds.1012.hdtv-lol.srt", "lol", false, ""); - Subtitle subtitle3 = createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", true, ""); - Subtitle subtitle4 = createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); - Subtitle subtitle5 = createSubtitle("Criminal.Minds.S10E12.Anonymous.1080p.WEB-DL.DD5.1.H.264-CtrlHD", "CtrlHD", false, ""); + Subtitle subtitle3 = + createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", true, ""); + Subtitle subtitle4 = + createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); + Subtitle subtitle5 = + createSubtitle("Criminal.Minds.S10E12.Anonymous.1080p.WEB-DL.DD5.1.H.264-CtrlHD", "CtrlHD", false, ""); // only exact match assertThatFilter(new SubtitleFiltering(createSettings(false, true, false))) @@ -76,10 +82,14 @@ void testExactMatchAndKeywordMatchFilter() { Release release = createRelease("Criminal.Minds.S10E12.Anonymous.720p.HDTV.X264-DIMENSION.mkv", "DIMENSION"); Subtitle subtitle1 = createSubtitle("Criminal.Minds.S10E12.HDTV.XviD-AFG.srt", "AFG", false, ""); Subtitle subtitle2 = createSubtitle("criminal.minds.1012.hdtv-lol.srt", "lol", false, ""); - Subtitle subtitle3 = createSubtitle("Criminal.Minds.S10E12.Anonymous.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", true, ""); - Subtitle subtitle4 = createSubtitle("Criminal.Minds.S10E12.Anonymous.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); - Subtitle subtitle5 = createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); - Subtitle subtitle6 = createSubtitle("Criminal.Minds.S10E12.Anonymous.1080p.WEB-DL.DD5.1.H.264-CtrlHD", "CtrlHD", false, ""); + Subtitle subtitle3 = + createSubtitle("Criminal.Minds.S10E12.Anonymous.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", true, ""); + Subtitle subtitle4 = + createSubtitle("Criminal.Minds.S10E12.Anonymous.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); + Subtitle subtitle5 = + createSubtitle("Criminal.Minds.S10E12.720p.HDTV.X264-DIMENSION.srt", "DIMENSION", false, ""); + Subtitle subtitle6 = + createSubtitle("Criminal.Minds.S10E12.Anonymous.1080p.WEB-DL.DD5.1.H.264-CtrlHD", "CtrlHD", false, ""); // only exact match assertThatFilter(new SubtitleFiltering(createSettings(true, true, false))) @@ -97,19 +107,19 @@ void testExactMatchAndKeywordMatchFilter() { private Settings createSettings(boolean keyword, boolean exact, boolean excludehearing) { Settings settings = mock(Settings.class); - when(settings.isOptionSubtitleExactMatch()).thenReturn(exact); - when(settings.isOptionSubtitleKeywordMatch()).thenReturn(keyword); - when(settings.isOptionSubtitleExcludeHearingImpaired()).thenReturn(excludehearing); + when(settings.optionSubtitleExactMatch).thenReturn(exact); + when(settings.optionSubtitleKeywordMatch).thenReturn(keyword); + when(settings.optionSubtitleExcludeHearingImpaired).thenReturn(excludehearing); return settings; } private Release createRelease(String filename, String releasegroup) { - Release release = mock(Release.class); + Release release = mock(TvRelease.class); - when(release.getFileName()).thenReturn(filename); - when(release.getExtension()).thenReturn("mkv"); - when(release.getReleaseGroup()).thenReturn(releasegroup); + when(release.fileName).thenReturn(filename); + when(release.extension).thenReturn("mkv"); + when(release.releaseGroup).thenReturn(releasegroup); return release; } @@ -117,10 +127,10 @@ private Release createRelease(String filename, String releasegroup) { private Subtitle createSubtitle(String filename, String releaseGroup, boolean excludeHearing, String quality) { Subtitle subtitle = mock(Subtitle.class); - when(subtitle.getFileName()).thenReturn(filename); - when(subtitle.getReleaseGroup()).thenReturn(releaseGroup); - when(subtitle.getQuality()).thenReturn(quality); - when(subtitle.isHearingImpaired()).thenReturn(excludeHearing); + when(subtitle.fileName).thenReturn(filename); + when(subtitle.releaseGroup).thenReturn(releaseGroup); + when(subtitle.quality).thenReturn(quality); + when(subtitle.hearingImpaired).thenReturn(excludeHearing); return subtitle; } @@ -143,7 +153,8 @@ private interface TestSetupMatchesIntf { void matchesSubtitles(Subtitle... subtitles); } - private static class TestSetupFiltering implements TestSetupSubtitlesIntf, TestSetupReleaseIntf, TestSetupMatchesIntf { + private static class TestSetupFiltering + implements TestSetupSubtitlesIntf, TestSetupReleaseIntf, TestSetupMatchesIntf { private SubtitleFiltering filter; private List subtitles; private Release release; @@ -154,7 +165,7 @@ public TestSetupFiltering assertThatFilter(SubtitleFiltering filter) { } public TestSetupFiltering appliedOnSubtitles(Subtitle... subtitles) { - this.subtitles = Arrays.stream(subtitles).toList(); + this.subtitles = subtitles.stream().toList(); return this; } @@ -164,10 +175,11 @@ public TestSetupFiltering forRelease(Release release) { } public void matchesSubtitles(Subtitle... subtitles) { - List filteredSubtitles = this.subtitles.stream().filter(subtitle -> filter.useSubtitle(subtitle, release)).toList(); + List filteredSubtitles = + this.subtitles.stream().filter(subtitle -> filter.useSubtitle(subtitle, release)).toList(); assertThat(filteredSubtitles) .withFailMessage("Expected the filtered subtitles to contain exactly %s, but found %s".formatted( - Arrays.stream(subtitles).map(Subtitle::getFileName).toList(), + subtitles.stream().map(Subtitle::getFileName).toList(), filteredSubtitles.stream().map(Subtitle::getFileName).toList())) .containsExactlyInAnyOrder(subtitles); } diff --git a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculatorTest.java b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculatorTest.java index 311ebc7f..01adc265 100644 --- a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculatorTest.java +++ b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/ScoreCalculatorTest.java @@ -8,11 +8,12 @@ import org.junit.jupiter.api.Test; import org.lodder.subtools.sublibrary.model.Release; import org.lodder.subtools.sublibrary.model.Subtitle; +import org.lodder.subtools.sublibrary.model.TvRelease; class ScoreCalculatorTest { @Test - void test_it_calculates_the_score_for_subtitle() throws Exception { + void test_it_calculates_the_score_for_subtitle() { SortWeight weights = createWeights("DVDRip XviD", "MEDiEVAL"); ScoreCalculator calculator = new ScoreCalculator(weights); @@ -44,17 +45,17 @@ void test_it_calculates_the_score_for_subtitle() throws Exception { private Subtitle createSubtitle(String filename, String quality, String team) { Subtitle subtitle = mock(Subtitle.class); - when(subtitle.getFileName()).thenReturn(filename); - when(subtitle.getQuality()).thenReturn(quality); - when(subtitle.getReleaseGroup()).thenReturn(team); + when(subtitle.fileName).thenReturn(filename); + when(subtitle.quality).thenReturn(quality); + when(subtitle.releaseGroup).thenReturn(team); return subtitle; } private SortWeight createWeights(String quality, String group) { // Arrested.Development.S01E01.DVDRip.XviD-MEDiEVAL - Release release = mock(Release.class); - when(release.getQuality()).thenReturn(quality); - when(release.getReleaseGroup()).thenReturn(group); + Release release = mock(TvRelease.class); + when(release.quality).thenReturn(quality); + when(release.releaseGroup).thenReturn(group); HashMap definedWeights = new HashMap<>(); definedWeights.put("hdtv", 2); diff --git a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeightTest.java b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeightTest.java index 16651f04..24bc96f6 100644 --- a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeightTest.java +++ b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/SortWeightTest.java @@ -8,15 +8,16 @@ import org.junit.jupiter.api.Test; import org.lodder.subtools.sublibrary.model.Release; +import org.lodder.subtools.sublibrary.model.TvRelease; class SortWeightTest { @Test void test_it_generates_weights_for_release() throws Exception { // Arrested.Development.S01E01.DVDRip.XviD-MEDiEVAL - Release release = mock(Release.class); - when(release.getQuality()).thenReturn("DVDRip XviD"); - when(release.getReleaseGroup()).thenReturn("MEDiEVAL"); + Release release = mock(TvRelease.class); + when(release.quality).thenReturn("DVDRip XviD"); + when(release.releaseGroup).thenReturn("MEDiEVAL"); HashMap definedWeights = new HashMap<>(); definedWeights.put("dvdrip", 2); @@ -25,17 +26,16 @@ void test_it_generates_weights_for_release() throws Exception { definedWeights.put("%GROUP%", 5); SortWeight sortWeight = new SortWeight(release, definedWeights); - Map weights = sortWeight.getWeights(); + Map weights = sortWeight.weights; /* check if we have the 3 weights */ - assertThat(weights).hasSize(3).containsKeys("dvdrip", "xvid", "medieval"); - - /* check if the weights are correct */ - assertThat(weights.get("dvdrip")).isEqualTo(2); - assertThat(weights.get("xvid")).isEqualTo(1); - assertThat(weights.get("medieval")).isEqualTo(5); + assertThat(weights) + .hasSize(3) + .containsEntry("dvdrip", 2) + .containsEntry("xvid", 1) + .containsEntry("medieval", 5); /* check if the maxScore is correct */ - assertThat(sortWeight.getMaxScore()).isEqualTo(8); + assertThat(sortWeight.maxScore).isEqualTo(8); } } diff --git a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacerTest.java b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacerTest.java index 8cdf59d4..255f5dee 100644 --- a/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacerTest.java +++ b/MultiSubDownloader/src/test/java/org/lodder/subtools/multisubdownloader/lib/control/subtitles/sorting/replacers/GroupReplacerTest.java @@ -8,20 +8,21 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.lodder.subtools.sublibrary.model.Release; +import org.lodder.subtools.sublibrary.model.TvRelease; class GroupReplacerTest { protected GroupReplacer replacer; @BeforeEach - public void setUp() throws Exception { + public void setUp() { replacer = new GroupReplacer(); } @Test - void test_it_replaces_the_keyword_group_to_a_releasename() throws Exception { - Release release = mock(Release.class); - when(release.getReleaseGroup()).thenReturn("Acme"); + void test_it_replaces_the_keyword_group_to_a_releasename() { + Release release = mock(TvRelease.class); + when(release.releaseGroup).thenReturn("Acme"); HashMap definedWeights = new HashMap<>(); definedWeights.put("%GROUP%", 5); @@ -36,9 +37,9 @@ void test_it_replaces_the_keyword_group_to_a_releasename() throws Exception { } @Test - void testEmptyWeights() throws Exception { - Release release = mock(Release.class); - when(release.getReleaseGroup()).thenReturn("Acme"); + void testEmptyWeights() { + Release release = mock(TvRelease.class); + when(release.releaseGroup).thenReturn("Acme"); HashMap definedWeights = new HashMap<>(); diff --git a/SubLibrary/pom.xml b/SubLibrary/pom.xml index cadd27b4..32b81be0 100644 --- a/SubLibrary/pom.xml +++ b/SubLibrary/pom.xml @@ -1,162 +1,198 @@ - - org.lodder.subtools - subtools - 1.5.1-SNAPSHOT - + + org.lodder.subtools + subtools + 1.5.1-SNAPSHOT + - 4.0.0 - sublibrary - SubLibrary + 4.0.0 + sublibrary + SubLibrary - - - org.openapitools - openapi-generator-core - - - javax.annotation - javax.annotation-api - - - io.swagger - swagger-annotations - - - com.squareup.okhttp3 - logging-interceptor - - - com.google.code.gson - gson - - - io.gsonfire - gson-fire - - - com.fasterxml.jackson.core - jackson-databind - - - com.pivovarit - throwing-function - - - name.falgout.jeffrey - throwing-interfaces - - - name.falgout.jeffrey - throwing-streams - - - com.optimaize.languagedetector - language-detector - - - org.apache.commons - commons-lang3 - - - org.jsoup - jsoup - - - org.hsqldb - hsqldb - - - ch.qos.logback - logback-classic - - - javax.ws.rs - javax.ws.rs-api - - - com.uwetrottmann.thetvdb-java - thetvdb-java - - - org.projectlombok - lombok - provided - - - - org.codehaus.plexus - plexus-interactivity-api - - - - com.massisframework - j-text-utils - - - org.json - json - - - net.jodah - typetools - - - - org.mockito - mockito-core - test - - - org.assertj - assertj-core - test - - - org.junit.jupiter - junit-jupiter-api - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.openapitools - openapi-generator-maven-plugin - - - - generate - - - java - ${project.basedir}/src/main/resources/opensubtitles/open-api.json - ${project.build.directory}/generated-source/openapi - org.opensubtitles.model - org.opensubtitles.api - org.opensubtitles.invoker - org.opensubtitles - - - Authentication,Download,Subtitles - false - - integer=int,int=int - false - true - is - false - - - - - - - - + + + org.openapitools + openapi-generator-core + + + io.swagger.core.v3 + swagger-annotations + + + com.squareup.okhttp3 + logging-interceptor + + + com.google.code.gson + gson + + + io.gsonfire + gson-fire + + + com.fasterxml.jackson.core + jackson-databind + + + com.pivovarit + throwing-function + + + name.falgout.jeffrey + throwing-interfaces + + + name.falgout.jeffrey + throwing-streams + + + com.optimaize.languagedetector + language-detector + + + org.apache.commons + commons-lang3 + + + org.jsoup + jsoup + + + org.jspecify + jspecify + + + org.hsqldb + hsqldb + + + ch.qos.logback + logback-classic + + + jakarta.ws.rs + jakarta.ws.rs-api + + + com.uwetrottmann.thetvdb-java + thetvdb-java + + + systems.manifold + manifold-ext-rt + + + systems.manifold + manifold-props-rt + + + systems.manifold + manifold-tuple-rt + + + systems.manifold + manifold-params-rt + + + systems.manifold + manifold-science + + + org.projectlombok + lombok + provided + + + + org.codehaus.plexus + plexus-interactivity-api + + + + com.massisframework + j-text-utils + + + org.json + json + + + net.jodah + typetools + + + com.pivovarit + more-gatherers + + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -Xplugin:Manifold + + + + systems.manifold + manifold-ext + ${manifold.version} + + + systems.manifold + manifold-props + ${manifold.version} + + + systems.manifold + manifold-strings + ${manifold.version} + + + systems.manifold + manifold-tuple + ${manifold.version} + + + systems.manifold + manifold-params + ${manifold.version} + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + java,class + + + + + + \ No newline at end of file diff --git a/SubLibrary/src/main/java/extensions/java/io/InputStream/InputStreamExt.java b/SubLibrary/src/main/java/extensions/java/io/InputStream/InputStreamExt.java new file mode 100644 index 00000000..09d754a5 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/io/InputStream/InputStreamExt.java @@ -0,0 +1,18 @@ +package extensions.java.io.InputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class InputStreamExt { + + public static String asString(@This InputStream inputStream, Charset charset) throws IOException { + return new String(inputStream.readAllBytes(), charset); + } +} diff --git a/SubLibrary/src/main/java/extensions/java/lang/Iterable/IterableExt.java b/SubLibrary/src/main/java/extensions/java/lang/Iterable/IterableExt.java new file mode 100644 index 00000000..9de4bdca --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/lang/Iterable/IterableExt.java @@ -0,0 +1,22 @@ +package extensions.java.lang.Iterable; + +import java.util.Collection; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class IterableExt { + + public static Stream stream(@This Iterable iterable) { + return StreamSupport.stream(iterable.spliterator(), false); + } + + public static int size(@This Iterable iterable) { + return iterable instanceof Collection collection ? collection.size() : (int) iterable.stream().count(); + } +} \ No newline at end of file diff --git a/SubLibrary/src/main/java/extensions/java/lang/String/StringExt.java b/SubLibrary/src/main/java/extensions/java/lang/String/StringExt.java new file mode 100644 index 00000000..0e0d1750 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/lang/String/StringExt.java @@ -0,0 +1,48 @@ +package extensions.java.lang.String; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.function.Function; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; + +@UtilityClass +@Extension +public class StringExt { + + public static String removeIllegalFilenameChars(@This String s) { + return s.replace("/", "").replace("\0", ""); + } + + public static String removeIllegalWindowsChars(@This String text) { + return StringUtils.removeEnd(text.replaceAll("[\\\\/:*?\"<>|]", ""), ".").trim(); + } + + public static String urlEncode(@This String text) { + return URLEncoder.encode(text, StandardCharsets.UTF_8); + } + + public static InputStream toInputStream(@This String text, Charset charset) { + return new ByteArrayInputStream(text.getBytes(charset)); + } + + public static Optional parseAsNumber(@This @Nullable String text, + Function mapper) { + if (text == null) { + return Optional.empty(); + } + try { + return Optional.of(mapper.apply(text)); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } +} diff --git a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/util/FileUtils.java b/SubLibrary/src/main/java/extensions/java/nio/file/Path/PathExt.java similarity index 67% rename from SubLibrary/src/main/java/org/lodder/subtools/sublibrary/util/FileUtils.java rename to SubLibrary/src/main/java/extensions/java/nio/file/Path/PathExt.java index ddbed2ed..5f3deffb 100644 --- a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/util/FileUtils.java +++ b/SubLibrary/src/main/java/extensions/java/nio/file/Path/PathExt.java @@ -1,4 +1,4 @@ -package org.lodder.subtools.sublibrary.util; +package extensions.java.nio.file.Path; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -15,44 +15,55 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.apache.commons.lang3.StringUtils; - -import lombok.experimental.ExtensionMethod; +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; import name.falgout.jeffrey.throwing.ThrowingConsumer; import name.falgout.jeffrey.throwing.ThrowingFunction; +import org.apache.commons.lang3.StringUtils; +import org.lodder.subtools.sublibrary.util.CopyDirVisitor; +import org.lodder.subtools.sublibrary.util.DeleteDirVisitor; -@ExtensionMethod({ StreamExtension.class }) -public class FileUtils { +@UtilityClass +@Extension +public class PathExt { - public static String getExtension(Path path) { + public static String getExtension(@This Path path) { return StringUtils.substringAfterLast(path.getFileName().toString(), "."); } - public static boolean hasExtension(Path path, String extension) { + public static boolean hasExtension(@This Path path, String extension) { return extension.equalsIgnoreCase(getExtension(path)); } - public static String changeExtension(Path path, String newExtension) { + public static String changeExtension(@This Path path, String newExtension) { return StringUtils.substringBeforeLast(path.getFileName().toString(), ".") + "." + newExtension; } - public static String withoutExtension(Path path) { + public static String withoutExtension(@This Path path) { return changeExtension(path, ""); } + public record FilenameAndExtension(String filename, String extension) {} + + public static FilenameAndExtension splitExtension(@This Path path) { + return new FilenameAndExtension(StringUtils.substringBeforeLast(path.getFileName().toString(), "."), + StringUtils.substringAfterLast(path.getFileName().toString(), ".")); + } + public static String withoutExtension(String path) { return StringUtils.substringBeforeLast(path, "."); } - public static String toAbsolutePathAsString(Path path) { + public static String toAbsolutePathAsString(@This Path path) { return path.toAbsolutePath().toString(); } /** * Moves a file or a complete directory tree. *

- * This method moves the given {@link Path} to the specified destination. Depending on whether - * the path is a directory or a regular file, the behavior of the method is as follows: + * This method moves the given {@link Path} to the specified destination. Depending on whether the path is a + * directory or a regular file, the behavior of the method is as follows: *

    *
  • If the {@link Path} is a directory, it will recursively move all files and subdirectories * within the directory hierarchy, starting from and including the given path, to the @@ -64,25 +75,22 @@ public static String toAbsolutePathAsString(Path path) { * existing file or directory, using the {@link StandardCopyOption} enum. These options are * applied to all files and directories being moved. * - * @param source - * the path to be moved - * @param destinationDir - * the destination directory - * @param copyOptions - * optional move options to apply while moving the path + * @param source the path to be moved + * @param destinationDir the destination directory + * @param copyOptions optional move options to apply while moving the path * @return the destination - * @throws IOException - * if an I/O error occurs while moving the path + * @throws IOException if an I/O error occurs while moving the path */ - public static Path moveToDir(Path source, Path destinationDir, StandardCopyOption... copyOptions) throws IOException { + public static Path moveToDir(@This Path source, Path destinationDir, StandardCopyOption... copyOptions) + throws IOException { return moveToDirAndRename(source, destinationDir, source.getFileName().toString(), copyOptions); } /** * Moves a file or a complete directory tree. *

    - * This method moves the given {@link Path} to the specified destination. Depending on whether - * the path is a directory or a regular file, the behavior of the method is as follows: + * This method moves the given {@link Path} to the specified destination. Depending on whether the path is a + * directory or a regular file, the behavior of the method is as follows: *

      *
    • If the {@link Path} is a directory, it will recursively move all files and subdirectories * within the directory hierarchy, starting from and including the given path, to the @@ -95,20 +103,16 @@ public static Path moveToDir(Path source, Path destinationDir, StandardCopyOptio * existing file or directory, using the {@link StandardCopyOption} enum. These options are * applied to all files and directories being moved. * - * @param source - * the path to be moved - * @param destinationDir - * the destination directory - * @param newFileName - * the new file name - * @param copyOptions - * optional move options to apply while moving the path + * @param source the path to be moved + * @param destinationDir the destination directory + * @param newFileName the new file name + * @param copyOptions optional move options to apply while moving the path * @return the destination - * @throws IOException - * if an I/O error occurs while moving the path + * @throws IOException if an I/O error occurs while moving the path */ - public static Path moveToDirAndRename(Path source, Path destinationDir, String newFileName, StandardCopyOption... copyOptions) - throws IOException { + public static Path moveToDirAndRename(@This Path source, Path destinationDir, String newFileName, + StandardCopyOption... copyOptions) + throws IOException { Files.createDirectories(destinationDir); if (Files.isRegularFile(source)) { Files.move(source, destinationDir.resolve(newFileName), copyOptions); @@ -123,7 +127,8 @@ public static Path moveToDirAndRename(Path source, Path destinationDir, String n return destinationDir.resolve(newFileName); } - private static Path moveNonEmptyDirectory(Path sourceDir, Path targetDir, StandardCopyOption... copyOptions) throws IOException { + private static Path moveNonEmptyDirectory(Path sourceDir, Path targetDir, StandardCopyOption... copyOptions) + throws IOException { if (Files.isDirectory(sourceDir)) { return moveNonEmptyDirectoryRecursively(sourceDir, targetDir, copyOptions); } else { @@ -131,9 +136,10 @@ private static Path moveNonEmptyDirectory(Path sourceDir, Path targetDir, Standa } } - private static Path moveNonEmptyDirectoryRecursively(Path source, Path target, StandardCopyOption... copyOptions) throws IOException { + private static Path moveNonEmptyDirectoryRecursively(Path source, Path target, StandardCopyOption... copyOptions) + throws IOException { foreachSubfile(source, s -> s.asThrowingStream(IOException.class) - .forEach(child -> moveNonEmptyDirectory(child, target.resolve(source.getFileName()), copyOptions))); + .forEach(child -> moveNonEmptyDirectory(child, target.resolve(source.getFileName()), copyOptions))); Files.delete(source); return target; } @@ -141,29 +147,27 @@ private static Path moveNonEmptyDirectoryRecursively(Path source, Path target, S /** * Deletes a {@link Path}. *

      - * If the {@link Path} exists, this method will delete it without any possibility of recovery. - * This method behaves as follows: + * If the {@link Path} exists, this method will delete it without any possibility of recovery. This method behaves + * as follows: *

        *
      • If the {@link Path} is a directory, it will recursively delete all files and * subdirectories within the directory, starting from and including the given path.
      • *
      • If the {@link Path} is a regular file, it will be deleted.
      • *
      * - * @param path - * the path to delete - * @throws IOException - * if an I/O error occurs while deleting the path + * @param path the path to delete + * @throws IOException if an I/O error occurs while deleting the path */ // TODO change name? (nameclash) - public static void delete(Path path) throws IOException { + public static void deletePath(@This Path path) throws IOException { Files.walkFileTree(path, new DeleteDirVisitor()); } /** * Copies a file or a complete directory tree. *

      - * This method copies the given {@link Path} to the specified destination. Depending on whether - * the path is a directory or a regular file, the behavior of the method is as follows: + * This method copies the given {@link Path} to the specified destination. Depending on whether the path is a + * directory or a regular file, the behavior of the method is as follows: *

        *
      • If the {@link Path} is a directory, it will recursively copy all files and subdirectories * within the directory hierarchy, starting from and including the given path, to the @@ -175,25 +179,22 @@ public static void delete(Path path) throws IOException { * existing file or directory, using the {@link StandardCopyOption} enum. These options are * applied to all files and directories being copied. * - * @param source - * the path to be copied - * @param destinationDir - * the destination directory - * @param copyOptions - * optional copy options to apply while copying the path + * @param source the path to be copied + * @param destinationDir the destination directory + * @param copyOptions optional copy options to apply while copying the path * @return the location of the copied path - * @throws IOException - * if an I/O error occurs while deleting the path + * @throws IOException if an I/O error occurs while deleting the path */ - public static Path copyToDir(Path source, Path destinationDir, StandardCopyOption... copyOptions) throws IOException { + public static Path copyToDir(@This Path source, Path destinationDir, StandardCopyOption... copyOptions) + throws IOException { return copyToDirAndRename(source, destinationDir, source.getFileName().toString(), copyOptions); } /** * Copies a file or a complete directory tree. *

        - * This method copies the given {@link Path} to the specified destination. Depending on whether - * the path is a directory or a regular file, the behavior of the method is as follows: + * This method copies the given {@link Path} to the specified destination. Depending on whether the path is a + * directory or a regular file, the behavior of the method is as follows: *

          *
        • If the {@link Path} is a directory, it will recursively copy all files and subdirectories * within the directory hierarchy, starting from and including the given path, to the @@ -206,20 +207,16 @@ public static Path copyToDir(Path source, Path destinationDir, StandardCopyOptio * existing file or directory, using the {@link StandardCopyOption} enum. These options are * applied to all files and directories being copied. * - * @param source - * the path to be copied - * @param destinationDir - * the destination directory - * @param newFileName - * the new file name - * @param copyOptions - * optional copy options to apply while copying the path + * @param source the path to be copied + * @param destinationDir the destination directory + * @param newFileName the new file name + * @param copyOptions optional copy options to apply while copying the path * @return the location of the copied path - * @throws IOException - * if an I/O error occurs while deleting the path + * @throws IOException if an I/O error occurs while deleting the path */ - public static Path copyToDirAndRename(Path source, Path destinationDir, String newFileName, StandardCopyOption... copyOptions) - throws IOException { + public static Path copyToDirAndRename(@This Path source, Path destinationDir, String newFileName, + StandardCopyOption... copyOptions) + throws IOException { if (Files.isRegularFile(source)) { Files.createDirectories(destinationDir); Path destinationFile = destinationDir.resolve(newFileName); @@ -233,36 +230,38 @@ public static Path copyToDirAndRename(Path source, Path destinationDir, String n } } - public static String getFileNameAsString(Path path) { + public static String getFileNameAsString(@This Path path) { return path.getFileName().toString(); } - public static boolean fileNameContains(Path path, String text) { + public static boolean fileNameContains(@This Path path, String text) { return path.getFileName().toString().contains(text); } - public static boolean fileNameContainsIgnoreCase(Path path, String text) { + public static boolean fileNameContainsIgnoreCase(@This Path path, String text) { return StringUtils.containsIgnoreCase(path.getFileName().toString(), text); } - public static boolean isEmptyDir(Path path) throws IOException { + public static boolean isEmptyDir(@This Path path) throws IOException { requireDir(path); return applySubfiles(path, children -> children.findAny().isEmpty()); } - public static T applySubfiles(Path path, ThrowingFunction, T, X> function) throws IOException, X { + public static T applySubfiles(@This Path path, + ThrowingFunction, T, X> function) throws IOException, X { try (Stream pathStream = Files.list(path)) { return function.apply(pathStream); } } - public static void foreachSubfile(Path path, ThrowingConsumer, X> consumer) throws IOException, X { + public static void foreachSubfile(@This Path path, ThrowingConsumer, X> consumer) + throws IOException, X { try (Stream pathStream = Files.list(path)) { consumer.accept(pathStream); } } - public static void requireDir(Path path) { + public static void requireDir(@This Path path) { if (!Files.isDirectory(path)) { throw new IllegalArgumentException("[%s] is not a directory".formatted(path)); } @@ -312,13 +311,13 @@ public static boolean isGZipCompressed(byte[] bytes) { return false; } else { return bytes[0] == (byte) GZIPInputStream.GZIP_MAGIC - && bytes[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8); + && bytes[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8); } } public static byte[] decompressGZip(byte[] data) throws IOException { try (ByteArrayInputStream binput = new ByteArrayInputStream(data); - GZIPInputStream gzinput = new GZIPInputStream(binput)) { + GZIPInputStream gzinput = new GZIPInputStream(binput)) { return gzinput.readAllBytes(); } } diff --git a/SubLibrary/src/main/java/extensions/java/util/Collection/CollectionExt.java b/SubLibrary/src/main/java/extensions/java/util/Collection/CollectionExt.java new file mode 100644 index 00000000..c5b34fed --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/Collection/CollectionExt.java @@ -0,0 +1,12 @@ +package extensions.java.util.Collection; + +import manifold.ext.rt.api.Extension; + +@Extension +public class CollectionExt { + +// @Intercept +// public static int size(@This @Nullable Collection collection) { +// return collection == null ? 0 : collection.size(); +// } +} diff --git a/SubLibrary/src/main/java/extensions/java/util/Iterator/IteratorExt.java b/SubLibrary/src/main/java/extensions/java/util/Iterator/IteratorExt.java new file mode 100644 index 00000000..8c7528b2 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/Iterator/IteratorExt.java @@ -0,0 +1,19 @@ +package extensions.java.util.Iterator; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class IteratorExt { + + public static Stream stream(@This Iterator iterator) { + return Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED).stream(); + } +} \ No newline at end of file diff --git a/SubLibrary/src/main/java/extensions/java/util/Optional/OptionalExt.java b/SubLibrary/src/main/java/extensions/java/util/Optional/OptionalExt.java new file mode 100644 index 00000000..4dc390bc --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/Optional/OptionalExt.java @@ -0,0 +1,54 @@ +package extensions.java.util.Optional; + +import java.util.Optional; +import java.util.OptionalInt; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; +import name.falgout.jeffrey.throwing.ThrowingConsumer; +import name.falgout.jeffrey.throwing.ThrowingFunction; +import name.falgout.jeffrey.throwing.ThrowingSupplier; +import name.falgout.jeffrey.throwing.ThrowingToIntFunction; + +@UtilityClass +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +@Extension +public class OptionalExt { + + /** + * If the value is present, apply the {@link ThrowingFunction} and return the value. Otherwise return {@code null} + * + * @param optional input object of the extension method + * @param function the {@link ThrowingFunction} to apply to the value if present + * @param The type of value of the input {@code Optional} + * @param type of the return value + * @param type of the exception that can be thrown + * @return the result of the {@link ThrowingFunction} if the value is present, otherwise {@code null} + * @throws X exception type of the throwing Function + */ + public static Optional mapThrowing(@This Optional optional, + ThrowingFunction function) throws X { + return optional.isPresent() ? Optional.ofNullable(function.apply(optional.get())) : Optional.empty(); + } + + public static OptionalInt mapToInt(@This Optional optional, + ThrowingToIntFunction mapper) throws X { + return optional.isPresent() ? OptionalInt.of(mapper.applyAsInt(optional.get())) : OptionalInt.empty(); + } + + public static @Self Optional useIfPresent(@This Optional optional, + ThrowingConsumer consumer) throws X { + if (optional.isPresent()) { + consumer.accept(optional.get()); + } + return optional; + } + + public static T orElseGetThrowing(@This Optional optional, + ThrowingSupplier supplier) throws X { + return optional.isPresent() ? optional.get() : supplier.get(); + } + +} diff --git a/SubLibrary/src/main/java/extensions/java/util/OptionalInt/OptionalIntExt.java b/SubLibrary/src/main/java/extensions/java/util/OptionalInt/OptionalIntExt.java new file mode 100644 index 00000000..8ce2be34 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/OptionalInt/OptionalIntExt.java @@ -0,0 +1,50 @@ +package extensions.java.util.OptionalInt; + + +import java.util.Optional; +import java.util.OptionalInt; + +import com.pivovarit.function.ThrowingIntFunction; +import com.pivovarit.function.ThrowingSupplier; +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import name.falgout.jeffrey.throwing.ThrowingIntUnaryOperator; + + +@Extension +@UtilityClass +public class OptionalIntExt { + + /** + * If the value is present, apply the {@link ThrowingIntUnaryOperator} and return the value wrapped in an + * + * @param optional input object of the extension method + * @param function the function to apply to the value if present + * @param type of the exception that can be thrown + * @return the result of the function wrapped in an @{link OptionalInt} if the value is present, otherwise an empty + * {@code OptionalInt} + * @throws X exception type of the throwing Function {@link OptionalInt}. Otherwise, return an empty + * {@code OptionalInt} + */ + public static OptionalInt map(@This OptionalInt optional, + ThrowingIntUnaryOperator function) throws X { + return optional.isPresent() ? OptionalInt.of(function.applyAsInt(optional.getAsInt())) : OptionalInt + .empty(); + } + + public static OptionalInt orElseMap(@This OptionalInt optionalInt, + ThrowingSupplier intSupplier) throws X { + return optionalInt.isPresent() ? optionalInt : intSupplier.get(); + } + + public static Optional mapToObj(@This OptionalInt optionalInt, + ThrowingIntFunction mapper) throws X { + return optionalInt.isPresent() ? Optional.ofNullable(mapper.apply(optionalInt.getAsInt())) : Optional + .empty(); + } + + public static Integer orElseNull(@This OptionalInt optionalInt) { + return optionalInt.isPresent() ? optionalInt.getAsInt() : null; + } +} diff --git a/SubLibrary/src/main/java/extensions/java/util/OptionalLong/OptionalLongExt.java b/SubLibrary/src/main/java/extensions/java/util/OptionalLong/OptionalLongExt.java new file mode 100644 index 00000000..613e9fea --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/OptionalLong/OptionalLongExt.java @@ -0,0 +1,50 @@ +package extensions.java.util.OptionalLong; + +import java.util.Optional; +import java.util.OptionalLong; + +import com.pivovarit.function.ThrowingFunction; +import com.pivovarit.function.ThrowingUnaryOperator; +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import name.falgout.jeffrey.throwing.ThrowingLongUnaryOperator; + +@Extension +@UtilityClass +public class OptionalLongExt { + + /** + * If the value is present, apply the {@link ThrowingLongUnaryOperator} and return the value wrapped in an @{link + * OptionalLong}. Otherwise, return an empty {@code OptionalLong} + * + * @param optional input object of the extension method + * @param function the function to apply to the value if present + * @param type of the exception that can be thrown + * @return the result of the function wrapped in an @{link OptionalLong} if the value is present, otherwise an empty + * {@code OptionalLong} + * @throws X exception type of the throwing Function + */ + public static OptionalLong map(@This OptionalLong optional, + ThrowingLongUnaryOperator function) throws X { + return optional.isPresent() ? OptionalLong.of(function.applyAsLong(optional.getAsLong())) : + OptionalLong.empty(); + } + + /** + * If the value is present, apply the {@link ThrowingUnaryOperator} and return the value wrapped in an @{link + * Optional}. Otherwise, return an empty {@code Optional} + * + * @param optional input object of the extension method + * @param function the function to apply to the value if present + * @param type of the result value wrapped in the @{link Optional} + * @param type of the exception that can be thrown + * @return the result of the function wrapped in an @{link Optional} if the value is present, otherwise an empty + * {@code Optional} + * @throws X exception type of the throwing Function + */ + public static Optional mapToObj(@This OptionalLong optional, + ThrowingFunction function) throws X { + return optional.isPresent() ? Optional.ofNullable(function.apply(optional.getAsLong())) : Optional.empty(); + } +} diff --git a/SubLibrary/src/main/java/extensions/java/util/Spliterator/SpliteratorExt.java b/SubLibrary/src/main/java/extensions/java/util/Spliterator/SpliteratorExt.java new file mode 100644 index 00000000..1732e856 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/Spliterator/SpliteratorExt.java @@ -0,0 +1,17 @@ +package extensions.java.util.Spliterator; + +import java.util.Spliterator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class SpliteratorExt { + public static Stream stream(@This Spliterator spliterator) { + return StreamSupport.stream(spliterator, false); + } +} \ No newline at end of file diff --git a/SubLibrary/src/main/java/extensions/java/util/regex/Matcher/MatcherExt.java b/SubLibrary/src/main/java/extensions/java/util/regex/Matcher/MatcherExt.java new file mode 100644 index 00000000..5a0de0fa --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/regex/Matcher/MatcherExt.java @@ -0,0 +1,20 @@ +package extensions.java.util.regex.Matcher; + +import java.util.Optional; +import java.util.regex.Matcher; + +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; + +@Extension +public class MatcherExt { + + public static Optional getMatch(@This Matcher matcher) { + return matcher.getMatch(0); + } + + public static Optional getMatch(@This Matcher matcher, int idx) { + return matcher.find() ? Optional.of(matcher.group(idx)) : Optional.empty(); + } + +} diff --git a/SubLibrary/src/main/java/extensions/java/util/stream/Stream/StreamExt.java b/SubLibrary/src/main/java/extensions/java/util/stream/Stream/StreamExt.java new file mode 100644 index 00000000..4abcf49b --- /dev/null +++ b/SubLibrary/src/main/java/extensions/java/util/stream/Stream/StreamExt.java @@ -0,0 +1,26 @@ +package extensions.java.util.stream.Stream; + +import java.util.function.IntFunction; +import java.util.stream.Stream; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import name.falgout.jeffrey.throwing.stream.ThrowingStream; + +@UtilityClass +@Extension +public class StreamExt { + + public static ThrowingStream asThrowingStream(@This Stream stream, Class exceptionType) { + return ThrowingStream.of(stream, exceptionType); + } + + public static T[] toTypedArray(@This Stream stream, IntFunction generator) { + return stream.toArray(generator); + } + + public static T[] toTypedArray(@This Stream stream) { + return (T[]) stream.toArray(Object[]::new); + } +} diff --git a/SubLibrary/src/main/java/extensions/manifold/rt/api/Array/ArrayExt.java b/SubLibrary/src/main/java/extensions/manifold/rt/api/Array/ArrayExt.java new file mode 100644 index 00000000..ac5d1a33 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/manifold/rt/api/Array/ArrayExt.java @@ -0,0 +1,35 @@ +package extensions.manifold.rt.api.Array; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.function.Consumer; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Self; +import manifold.ext.rt.api.This; + +@UtilityClass +@Extension +public class ArrayExt { + + /** + * Performs an action for each element of the array. + * + * @param array the Array + * @param action an action to perform on the elements + */ + public static void forEach(@This Object array, Consumer action) { + primitiveCheck(array); + Arrays.stream((Object[]) array, 0, Array.getLength(array)).forEach(action); + } + + + private static void primitiveCheck(Object array) { + Class componentType = array.getClass().getComponentType(); + if (componentType.isPrimitive()) { + throw new IllegalArgumentException("$array has not a primitive component type: " + + array.getClass().getComponentType().getSimpleName()); + } + } +} \ No newline at end of file diff --git a/SubLibrary/src/main/java/extensions/manifold/science/measures/Time/TimeExt.java b/SubLibrary/src/main/java/extensions/manifold/science/measures/Time/TimeExt.java new file mode 100644 index 00000000..c65db7ed --- /dev/null +++ b/SubLibrary/src/main/java/extensions/manifold/science/measures/Time/TimeExt.java @@ -0,0 +1,34 @@ +package extensions.manifold.science.measures.Time; + +import java.math.BigInteger; + +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import manifold.science.measures.Time; +import manifold.science.measures.TimeUnit; +import manifold.science.util.Rational; + +@Extension +public class TimeExt { + + public static boolean isPositive(@This Time time) { + return time.value.numerator.compareTo(BigInteger.ZERO) > 0; + } + + public static boolean isNegative(@This Time time) { + return time.value.numerator.compareTo(BigInteger.ZERO) < 0; + } + + public static boolean isBefore(@This Time time, Time other) { + return time.compareTo(other) < 0; + } + + public static boolean isAfter(@This Time time, Time other) { + return time.compareTo(other) > 0; + } + + @Extension + public static Time create(Number duration, TimeUnit unit) { + return new Time(Rational.get(duration), unit); + } +} diff --git a/SubLibrary/src/main/java/extensions/org/codehaus/plexus/components/interactivity/Prompter/PrompterExt.java b/SubLibrary/src/main/java/extensions/org/codehaus/plexus/components/interactivity/Prompter/PrompterExt.java new file mode 100644 index 00000000..b1f34c23 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/codehaus/plexus/components/interactivity/Prompter/PrompterExt.java @@ -0,0 +1,291 @@ +package extensions.org.codehaus.plexus.components.interactivity.Prompter; + +import static com.pivovarit.gatherers.MoreGatherers.*; +import static java.lang.System.*; +import static org.lodder.subtools.multisubdownloader.Messages.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.function.Function; +import java.util.stream.Collectors; + +import dnl.utils.text.table.TextTable; +import lombok.RequiredArgsConstructor; +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.function.TriFunction; +import org.codehaus.plexus.components.interactivity.Prompter; +import org.codehaus.plexus.components.interactivity.PrompterException; +import org.jspecify.annotations.Nullable; +import org.lodder.subtools.sublibrary.util.Validator; + +@Extension +@UtilityClass +public class PrompterExt { + + public static Validator NON_BLANK_VALIDATOR = + new Validator<>(StringUtils::isNotBlank, getText("Prompter.ValueNonBlank")); + public static Validator INT_VALIDATOR = + new Validator<>(v -> v.parseAsNumber(Integer::parseInt).isPresent()); + public static Validator BOOLEAN_VALIDATOR = + new Validator<>(v -> getText("Prompter.YesAbbreviation").equalsIgnoreCase(v) || + getText("Prompter.Yes").equalsIgnoreCase(v) || + getText("Prompter.NoAbbreviation").equalsIgnoreCase(v) || + getText("Prompter.No").equalsIgnoreCase(v)); + + public static void show(@This Prompter prompter, String message, Object... replacements) { + try { + prompter.showMessage(message.formatted(replacements)); + } catch (PrompterException e) { + throw new IllegalStateException(e); + } + } + + public static void pressAnyKeyToContinue(@This Prompter prompter) { + prompt(prompter:prompter, message:"Press any key to continue", toObjectMapper:Function.identity()); + } + + public static Optional promptString(@This Prompter prompter, String message, + List> inputValidators=List.of(NON_BLANK_VALIDATOR), + List> objectValidators=new ArrayList>()) { + + return prompt(prompter, message, inputValidators, Function.identity(), objectValidators); + } + + public static OptionalInt promptInt(@This Prompter prompter, String message, + List> inputValidators=List.of(NON_BLANK_VALIDATOR, INT_VALIDATOR), + Function toObjectMapper=Integer::parseInt, + List> objectValidators=new ArrayList>()) { + + return prompt(prompter, message, inputValidators, toObjectMapper, objectValidators).mapToInt(v -> v); + } + + public static Optional promptBoolean(@This Prompter prompter, String message, + List> inputValidators=List.of(NON_BLANK_VALIDATOR, BOOLEAN_VALIDATOR)) { + + return prompt(prompter, message, inputValidators, Boolean::parseBoolean, List.of()); + } + + public static Optional promptValueFromList(@This Prompter prompter, + String message, + Iterable elements, + Function toStringMapper=String::valueOf, + boolean includeNull, + @Nullable TableDisplayer tableDisplayer=null, + @Nullable Comparator sorter=null) { + + Validator inputValidator = + new Validator<>(v -> v == null || v.parseAsNumber(Integer::parseUnsignedInt).isPresent()); + Function toObjectMapper = v -> v == null ? null : Integer.parseUnsignedInt(v); + int numberOfElements = elements.size(); + List> objectValidators = + List.of(new Validator<>(number -> number == null || (number > 0 && number <= numberOfElements), + getText("Prompter.ValueNotInRange", numberOfElements))); + + TriFunction>, List, Optional> promptFunction = (choicesMessage, + inputValidators, sortedElements) -> + // TODO use extension method + PrompterExt.promptInt(prompter, choicesMessage, inputValidators, toObjectMapper, objectValidators) + .mapToObj(idx -> sortedElements.get(idx - 1)); + + return promptFromList(message, elements, toStringMapper, includeNull, tableDisplayer, sorter, + List.of(inputValidator), promptFunction); + } + + public static List promptValuesFromList(@This Prompter prompter, + String message, + Iterable elements, + Function toStringMapper=String::valueOf, + boolean includeNull, + @Nullable TableDisplayer tableDisplayer=null, + @Nullable Comparator sorter=null) { + + Validator inputValidator = new Validator<>(v -> v == null || + v.split(",").stream().allMatch(n -> n.parseAsNumber(Integer::parseUnsignedInt).isPresent())); + Function toObjectsMapper = + v -> Arrays.stream(v.split(",")).mapToInt(Integer::parseUnsignedInt).toArray(); + int numberOfElements = elements.size(); + List> objectValidators = List.of( + new Validator<>(numbers -> numbers.stream().distinct().count() == numbers.stream().count(), + getText("Prompter.DistinctValues")), + new Validator<>(numbers -> numbers.stream().allMatch(number -> number > 0 && number <= numberOfElements), + getText("Prompter.ValueNotInRange", numberOfElements))); + + TriFunction>, List, List> promptFunction = (choicesMessage, + inputValidators, sortedElements) -> + // TODO use extension method + PrompterExt.promptValues(prompter, choicesMessage, inputValidators, toObjectsMapper, objectValidators) + .stream() + .map(idx -> sortedElements.get(idx - 1)).toList(); + + return promptFromList(message, elements, toStringMapper, includeNull, tableDisplayer, sorter, + List.of(inputValidator), promptFunction); + } + + private static R promptFromList( + String message, + Iterable elements, + Function toStringMapper=String::valueOf, + boolean includeNull, + @Nullable TableDisplayer tableDisplayer=null, + @Nullable Comparator sorter=null, + List> inputValidators=new ArrayList>(), + TriFunction>, List, R> promptFunction) { + + List sortedElements = + sorter == null ? elements.stream().toList() : elements.stream().sorted(sorter).toList(); + String choicesMessage; + if (tableDisplayer != null) { + choicesMessage = tableDisplayer.getAsString(sortedElements); + } else { + choicesMessage = message + lineSeparator() + + sortedElements.stream() + .gather(zipWithIndex()) + .map(entry -> " - " + (entry.getValue() + 1) + ": " + toStringMapper.apply(entry.getKey())) + .collect(Collectors.joining(lineSeparator())) + + lineSeparator(); + } + List> allInputValidators = new ArrayList<>(); + if (!includeNull) { + allInputValidators.add(NON_BLANK_VALIDATOR); + } + allInputValidators.addAll(inputValidators); + + return promptFunction.apply(choicesMessage, allInputValidators, sortedElements); + } + + private static Optional prompt(Prompter prompter, String message, + List> inputValidators=new ArrayList>(), + Function toObjectMapper, + List> objectValidators=new ArrayList>()) { + try { + String value = prompter.prompt(message + System.lineSeparator()); + for (Validator inputValidator : inputValidators) { + if (inputValidator.isInvalid(value)) { + prompter.showMessage(inputValidator.errorMessage); + return prompt(prompter, message, inputValidators, toObjectMapper, objectValidators); + } + } + T object = toObjectMapper.apply(value); + for (Validator objectValidator : objectValidators) { + if (objectValidator.isInvalid(object)) { + prompter.showMessage(objectValidator.errorMessage); + return prompt(prompter, message, inputValidators, toObjectMapper, objectValidators); + } + } + return Optional.ofNullable(object); + } catch (PrompterException e) { + throw new IllegalStateException(e); + } + } + + public static T promptValues(@This Prompter prompter, String message, + List> inputValidators=new ArrayList>(), + Function toObjectsMapper, + List> objectValidators=new ArrayList>()) { + try { + String value = prompter.prompt(message + System.lineSeparator()); + for (Validator inputValidator : inputValidators) { + if (inputValidator.isInvalid(value)) { + prompter.showMessage(inputValidator.errorMessage); + // TODO use extension method + return PrompterExt.promptValues(prompter, message, inputValidators, toObjectsMapper, + objectValidators); + } + } + T objects = toObjectsMapper.apply(value); + for (Validator objectValidator : objectValidators) { + if (objectValidator.isInvalid(objects)) { + prompter.showMessage(objectValidator.errorMessage); + // TODO use extension method + return PrompterExt.promptValues(prompter, message, inputValidators, toObjectsMapper, + objectValidators); + } + } + return objects; + } catch (PrompterException e) { + throw new IllegalStateException(e); + } + } + + @RequiredArgsConstructor + public static class TableDisplayer { + + private final List> columnDisplayers; + + @SafeVarargs + public final void display(T... tableElements) { + String[] columnNames = columnDisplayers.stream().map(ColumnDisplayer::columnName).toArray(String[]::new); + Object[][] dataTable = tableElements.stream() + .map(tableElement -> columnDisplayers.stream() + .map(columnDisplayer -> columnDisplayer.toStringMapper().apply(tableElement)) + .toArray()) + .toTypedArray(Object[][]::new); + + TextTable tt = new TextTable(columnNames, dataTable); + // this adds the numbering on the left + tt.setAddRowNumbering(true); + tt.printTable(); + } + + public void display(Iterable tableElements) { + writeToPrintStream(tableElements, System.out); + } + + public String getAsString(Iterable tableElements) { + LineReadingOutputStream lineReadingOutputStream = new LineReadingOutputStream(); + PrintStream printStream = new PrintStream(lineReadingOutputStream); + writeToPrintStream(tableElements, printStream); + return lineReadingOutputStream.toString(); + } + + private void writeToPrintStream(Iterable tableElements, PrintStream printStream) { + String[] columnNames = columnDisplayers.stream().map(ColumnDisplayer::columnName).toArray(String[]::new); + Object[][] dataTable = tableElements.stream() + .map(tableElement -> columnDisplayers.stream() + .map(columnDisplayer -> columnDisplayer.toStringMapper().apply(tableElement)) + .toArray()) + .toArray(Object[][]::new); + + TextTable tt = new TextTable(columnNames, dataTable); + // this adds the numbering on the left + tt.setAddRowNumbering(true); + tt.printTable(printStream, 0); + } + + + } + + private static class LineReadingOutputStream extends OutputStream { + private final ByteArrayOutputStream byteArrayOutputStream; + + // Constructor + public LineReadingOutputStream() { + this.byteArrayOutputStream = new ByteArrayOutputStream(); + } + + // Write method, called when you write data to the stream + @Override + public void write(int b) throws IOException { + byteArrayOutputStream.write(b); + } + + public String toString() { + return byteArrayOutputStream.toString(); + } + + } + + public record ColumnDisplayer(String columnName, Function toStringMapper) { + } +} diff --git a/SubLibrary/src/main/java/extensions/org/json/JSONArray/JSONArrayExt.java b/SubLibrary/src/main/java/extensions/org/json/JSONArray/JSONArrayExt.java new file mode 100644 index 00000000..f58f3381 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/json/JSONArray/JSONArrayExt.java @@ -0,0 +1,20 @@ +package extensions.org.json.JSONArray; + +import java.util.Iterator; +import java.util.stream.Stream; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import org.json.JSONArray; +import org.json.JSONObject; + +@Extension +@UtilityClass +public class JSONArrayExt { + + public static Stream streamJsonObjects(@This JSONArray jsonArray) { + Iterator iterator = (Iterator) (Iterator) jsonArray.iterator(); + return iterator.stream(); + } +} \ No newline at end of file diff --git a/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Element/ElementExt.java b/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Element/ElementExt.java new file mode 100644 index 00000000..a6503fdc --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Element/ElementExt.java @@ -0,0 +1,239 @@ +package extensions.org.jsoup.nodes.Element; + +import extensions.org.jsoup.select.Elements.UnmodifiableElements; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Intercept; +import manifold.ext.rt.api.Jailbreak; +import manifold.ext.rt.api.This; +import org.jsoup.helper.Validate; +import org.jsoup.nodes.Element; +import org.jsoup.select.Collector; +import org.jsoup.select.Elements; +import org.jsoup.select.Evaluator; +import org.jsoup.select.QueryParser; +import org.jspecify.annotations.Nullable; +import org.lodder.subtools.sublibrary.exception.WebpageException; + +@Extension +public class ElementExt { + + // --------------- \\ + // Get all Element \\ + // --------------- \\ + + public static Elements selectAllByClass(@This @Nullable Element element, String className) { + return element == null ? UnmodifiableElements.EMPTY : element.getElementsByClass(requireNotEmpty(className)); + } + + public static Elements selectAllByTag(@This @Nullable Element element, String tagName) { + return element == null ? UnmodifiableElements.EMPTY : element.getElementsByTag(requireNotEmpty(tagName)); + } + + public static Elements selectAllByAttribute(@This @Nullable Element element, String attribute) { + return element == null ? UnmodifiableElements.EMPTY : + element.getElementsByAttribute(requireNotEmpty(attribute)); + } + + public static Elements selectAllByCss(@This @Nullable Element element, String cssQuery) { + return element == null ? UnmodifiableElements.EMPTY : element.getElements(requireNotEmpty(cssQuery)); + } + + public static Elements selectAll(@This @Nullable Element element, Evaluator evaluator) { + return element == null ? UnmodifiableElements.EMPTY : Collector.collect(evaluator, element); + } + + // ----------------- \\ + // Get First Element \\ + // ----------------- \\ + + public static @Nullable Element selectFirstByClass(@This @Nullable Element element, String className) { + return element == null ? null : element.selectFirst(new Evaluator.Class(requireNotEmpty(className))); + } + + public static @Nullable Element selectFirstByTag(@This @Nullable Element element, String tagName) { + return element == null ? null : element.selectFirst(new Evaluator.Tag(requireNotEmpty(tagName))); + } + + public static @Nullable Element selectFirstByAttribute(@This @Nullable Element element, String attribute) { + return element == null ? null : element.selectFirst(new Evaluator.Attribute(requireNotEmpty(attribute))); + } + + public static @Nullable Element selectFirstByCss(@This @Nullable Element element, String cssQuery) { + return element == null ? null : element.selectFirst(QueryParser.parse(requireNotEmpty(cssQuery))); + } + + public static @Nullable Element selectFirstById(@This @Nullable Element element, String id) { + return element == null ? null : element.selectFirst(new Evaluator.Id(requireNotEmpty(id))); + } + + public static @Nullable Element selectFirstBy(@This @Nullable Element element, Evaluator evaluator) { + return element == null ? null : Collector.findFirst(evaluator, element); + } + + // ---------------- \\ + // Get n-th Element \\ + // ---------------- \\ + + public static @Nullable Element selectNthByClass(@This @Nullable Element element, String className, int index) { + return element == null ? null : selectNth(element, new Evaluator.Class(requireNotEmpty(className)), index); + } + + public static @Nullable Element selectNthByTag(@This @Nullable Element element, String tagName, int index) { + return element == null ? null : selectNth(element, new Evaluator.Tag(requireNotEmpty(tagName)), index); + } + + public static @Nullable Element selectNthByAttribute(@This @Nullable Element element, String attribute, + int index) { + return element == null ? null : + selectNth(element, new Evaluator.Attribute(requireNotEmpty(attribute)), index); + } + + public static @Nullable Element selectNthByCss(@This @Nullable Element element, String cssQuery, int index) { + return element == null ? null : selectNth(element, QueryParser.parse(requireNotEmpty(cssQuery)), index); + } + + public static @Nullable Element selectNth(@This @Nullable Element element, @Jailbreak Evaluator eval, int idx) { + eval.reset(); + return new NthElementFinder(eval).find(element, element, idx); + } + + // -------------------------- \\ + // Get First Element or Throw \\ + // -------------------------- \\ + + public static Element selectFirstByClassOrThrow(@This @Nullable Element element, String className) + throws WebpageException { + Element n = selectFirstByClass(element, className); + if (n == null) { + throw new WebpageException("Could not find element with class '%s'".formatted(className)); + } + return n; + } + + public static Element selectFirstByTagOrThrow(@This @Nullable Element element, String tagName) + throws WebpageException { + Element n = selectFirstByTag(element, tagName); + if (n == null) { + throw new WebpageException("Could not find element with tag '%s'".formatted(tagName)); + } + return n; + } + + public static Element selectFirstByAttributeOrThrow(@This @Nullable Element element, String attribute) + throws WebpageException { + Element n = selectFirstByAttribute(element, attribute); + if (n == null) { + throw new WebpageException("Could not find element with attribute '%s'".formatted(attribute)); + } + return n; + } + + public static Element selectFirstByCssOrThrow(@This @Nullable Element element, String cssQuery) + throws WebpageException { + Element n = selectFirstByCss(element, cssQuery); + if (n == null) { + throw new WebpageException("Could not find element with css selector '%s'".formatted(cssQuery)); + } + return n; + } + + public static Element selectFirstByIdOrThrow(@This @Nullable Element element, String id) + throws WebpageException { + Element n = selectFirstById(element, id); + if (n == null) { + throw new WebpageException("Could not find element with id '%s'".formatted(id)); + } + return n; + } + + public static Element selectOrThrow(@This @Nullable Element element, Evaluator evaluator) + throws WebpageException { + Element n = selectFirstBy(element, evaluator); + if (n == null) { + throw new WebpageException("Could not find element using selector '%s'".formatted(evaluator)); + } + return n; + } + + // ------------------------- \\ + // Get n-th Element or Throw \\ + // ------------------------- \\ + + public static Element selectNthByClassOrThrow(@This @Nullable Element element, String className, int index) + throws WebpageException { + Element elem = selectNthByClass(element, className, index); + if (elem == null) { + throw new WebpageException("Could not find %sth element with class '%s'".formatted(index, className)); + } + return elem; + } + + public static Element selectNthByTagOrThrow(@This @Nullable Element element, String tagName, int index) + throws WebpageException { + Element elem = selectNthByTag(element, tagName, index); + if (elem == null) { + throw new WebpageException("Could not find %sth element with tag '%s'".formatted(index, tagName)); + } + return elem; + } + + public static Element selectNthByAttributeOrThrow(@This @Nullable Element element, String attribute, int index) + throws WebpageException { + Element elem = selectNthByAttribute(element, attribute, index); + if (elem == null) { + throw new WebpageException("Could not find %sth element with attribute '%s'".formatted(index, attribute)); + } + return elem; + } + + public static Element selectNthByCssOrThrow(@This @Nullable Element element, String cssQuery, int index) + throws WebpageException { + Element elem = selectNthByCss(element, cssQuery, index); + if (elem == null) { + throw new WebpageException("Could not find %sth element with css selector '%s'".formatted(index, cssQuery)); + } + return elem; + } + + public static Element selectNthOrThrow(@This @Nullable Element element, Evaluator eval, int index) + throws WebpageException { + Element elem = selectNth(element, eval, index); + if (elem == null) { + throw new WebpageException( + "Could not find %sth element using selector '%s'".formatted(index, eval.toString())); + } + return elem; + } + + // ------------ \\ + // Get Elements \\ + // ------------ \\ + + public static Elements getElements(@This @Nullable Element element, String cssQuery) { + return element == null ? new Elements() : element.select(cssQuery); + } + + // --------------- \\ + // Element methods \\ + // --------------- \\ + + + @Intercept + public static String text(@This @Nullable Element element) { + return element == null ? "" : element.text(); + } + + @Intercept + public static @Nullable Element parent(@This @Nullable Element element) { + return element == null ? null : element.parent(); + } + + // --------------- \\ + // Utility methods \\ + // --------------- \\ + + private static String requireNotEmpty(String value) { + Validate.notEmpty(value); + return value; + } +} diff --git a/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Element/NthElementFinder.java b/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Element/NthElementFinder.java new file mode 100644 index 00000000..19e6a541 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Element/NthElementFinder.java @@ -0,0 +1,37 @@ +package extensions.org.jsoup.nodes.Element; + +import static org.jsoup.select.NodeFilter.FilterResult.*; + +import lombok.RequiredArgsConstructor; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.select.Evaluator; +import org.jsoup.select.NodeFilter; +import org.jsoup.select.NodeTraversor; +import org.jspecify.annotations.Nullable; + +@RequiredArgsConstructor +class NthElementFinder implements NodeFilter { + private final Evaluator eval; + private Element evalRoot; + private Element match; + private int index; + private int currentIdx; + + @Nullable Element find(Element root, Element start, int index) { + this.index = index; + this.evalRoot = root; + this.match = null; + NodeTraversor.filter(this, start); + return match; + } + + @Override + public FilterResult head(Node node, int depth) { + if (node instanceof Element el && eval.matches(evalRoot, el) && currentIdx++ == index) { + match = el; + return STOP; + } + return CONTINUE; + } +} \ No newline at end of file diff --git a/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Node/NodeExt.java b/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Node/NodeExt.java new file mode 100644 index 00000000..7f560e37 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/jsoup/nodes/Node/NodeExt.java @@ -0,0 +1,110 @@ +package extensions.org.jsoup.nodes.Node; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Intercept; +import manifold.ext.rt.api.This; +import name.falgout.jeffrey.throwing.ThrowingFunction; +import org.jsoup.nodes.Node; +import org.jspecify.annotations.Nullable; + +@Extension +public class NodeExt { + + // ------------ \\ + // Node methods \\ + // ------------ \\ + + @Intercept + public static @Nullable Node parent(@This @Nullable Node node) { + return node == null ? null : node.parent(); + } + + @Intercept + public static String attr(@This @Nullable Node node, String attr) { + return node == null ? null : node.attr(attr); + } + + public static T useAttrOrElse(@This @Nullable Node node, String attr, Function mapper, + Supplier supplier) { + return node != null && node.hasAttr(attr) ? mapper.apply(node.attr(attr)) : supplier.get(); + } + + public static Optional getAttrOptional(@This @Nullable Node node, String attr) { + return Optional.ofNullable(node.attr(attr)); + } + + // ------------- \\ + // Other methods \\ + // ------------- \\ + + public static @Nullable Node filter(@This @Nullable Node node, Predicate predicate) { + return node != null && predicate.test(node) ? node : null; + } + + public static boolean matches(@This @Nullable Node node, Predicate predicate) { + return node != null && predicate.test(node); + } + + public static <@Nullable T> @Nullable T map(@This @Nullable Node node, Function mapper) { + return node != null ? mapper.apply(node) : null; + } + + public static Optional mapOptional(@This @Nullable Node node, Function mapper) { + return node != null ? Optional.ofNullable(mapper.apply(node)) : Optional.empty(); + } + + public static T mapOrElse(@This @Nullable Node node, Function mapper, T obj) { + return node != null ? mapper.apply(node) : obj; + } + + public static T mapOrElseGet(@This @Nullable Node node, Function mapper, + Supplier elseSupplier) { + return node != null ? mapper.apply(node) : elseSupplier.get(); + } + + public static <@Nullable T, X extends Exception> @Nullable T mapThrowing(@This @Nullable Node node, + ThrowingFunction mapper) + throws X { + return node != null ? mapper.apply(node) : null; + } + + public static Node orElseGet(@This @Nullable Node node, Supplier elementSupplier) { + return node != null ? node : elementSupplier.get(); + } + + public static Node orElseThrow(@This @Nullable Node node, + Supplier exceptionSupplier) throws X { + if (node == null) { + throw exceptionSupplier.get(); + } + return node; + } + + public static void ifPresent(@This @Nullable Node node, Consumer consumer) { + if (node != null) { + consumer.accept(node); + } + } + + public static boolean isFound(@This @Nullable Node node) { + return node != null; + } + + public static boolean isNotNull(@This @Nullable Node node) { + return node != null; + } + + public static boolean isNotFound(@This @Nullable Node node) { + return node == null; + } + + public static boolean isNull(@This @Nullable Node node) { + return node == null; + } +} diff --git a/SubLibrary/src/main/java/extensions/org/jsoup/select/Elements/ElementsExt.java b/SubLibrary/src/main/java/extensions/org/jsoup/select/Elements/ElementsExt.java new file mode 100644 index 00000000..2d753322 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/jsoup/select/Elements/ElementsExt.java @@ -0,0 +1,29 @@ +package extensions.org.jsoup.select.Elements; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Intercept; +import manifold.ext.rt.api.This; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.jspecify.annotations.Nullable; + +@Extension +@UtilityClass +public class ElementsExt { + + @Intercept + public static String text(@This @Nullable Elements elements) { + return elements == null ? "" : elements.text(); + } + + @Intercept + public static @Nullable String attr(@This @Nullable Elements elements, String attribute) { + return elements == null ? null : elements.attr(attribute); + } + + @Intercept + public static @Nullable Element first(@This @Nullable Elements elements) { + return elements == null ? null : elements.first(); + } +} diff --git a/SubLibrary/src/main/java/extensions/org/jsoup/select/Elements/UnmodifiableElements.java b/SubLibrary/src/main/java/extensions/org/jsoup/select/Elements/UnmodifiableElements.java new file mode 100644 index 00000000..7b86b424 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/jsoup/select/Elements/UnmodifiableElements.java @@ -0,0 +1,123 @@ +package extensions.org.jsoup.select.Elements; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +public class UnmodifiableElements extends Elements { + + public static UnmodifiableElements EMPTY = new UnmodifiableElements(); + + public UnmodifiableElements() { + } + + public UnmodifiableElements(int initialCapacity) { + super(initialCapacity); + } + + public UnmodifiableElements(Collection elements) { + super(elements); + } + + public UnmodifiableElements(List elements) { + super(elements); + } + + public UnmodifiableElements(Element... elements) { + super(Arrays.asList(elements)); + } + + public Elements removeAttr(String attributeKey) { + return this; + } + + public Elements addClass(String className) { + return this; + } + + public Elements removeClass(String className) { + return this; + } + + public Elements toggleClass(String className) { + return this; + } + + + public Elements prepend(String html) { + return this; + } + + public Elements append(String html) { + return this; + } + + public Elements before(String html) { + return this; + } + + public Elements after(String html) { + return this; + } + + public Elements wrap(String html) { + return this; + } + + public Elements unwrap() { + return this; + } + + public Elements empty() { + return this; + } + + public Elements remove() { + return this; + } + + // filters + + @Override + public Element set(int index, Element element) { + throw new IllegalStateException(); + } + + @Override + public Element remove(int index) { + throw new IllegalStateException(); + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public void clear() { + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public boolean removeIf(Predicate filter) { + return false; + } + + @Override + public void replaceAll(UnaryOperator operator) { + } +} diff --git a/SubLibrary/src/main/java/extensions/org/w3c/dom/Document/DocumentExt.java b/SubLibrary/src/main/java/extensions/org/w3c/dom/Document/DocumentExt.java new file mode 100644 index 00000000..794e0175 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/w3c/dom/Document/DocumentExt.java @@ -0,0 +1,31 @@ +package extensions.org.w3c.dom.Document; + +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.Intercept; +import manifold.ext.rt.api.This; +import org.jsoup.helper.Validate; +import org.jspecify.annotations.Nullable; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +@Extension +public class DocumentExt { + + // ----------------- \\ + // Get First Element \\ + // ----------------- \\ + + @Intercept + public static @Nullable NodeList getElementsByTagName(@This @Nullable Document document, String tagName) { + return document == null ? null : document.getElementsByTagName(requireNotEmpty(tagName)); + } + + // --------------- \\ + // Utility methods \\ + // --------------- \\ + + private static String requireNotEmpty(String value) { + Validate.notEmpty(value); + return value; + } +} diff --git a/SubLibrary/src/main/java/extensions/org/w3c/dom/Node/NodeExt.java b/SubLibrary/src/main/java/extensions/org/w3c/dom/Node/NodeExt.java new file mode 100644 index 00000000..811f6a1b --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/w3c/dom/Node/NodeExt.java @@ -0,0 +1,28 @@ +package extensions.org.w3c.dom.Node; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import org.jspecify.annotations.Nullable; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +@UtilityClass +@Extension +public class NodeExt { + + public static @Nullable String getAttribute(@This @Nullable Node node, String attribute) { + if (node == null) { + return null; + } + NamedNodeMap attributes = node.getAttributes(); + if (attributes == null) { + return null; + } + Node attributeNode = attributes.getNamedItem(attribute); + if (attributeNode == null) { + return null; + } + return attributeNode.getNodeValue(); + } +} diff --git a/SubLibrary/src/main/java/extensions/org/w3c/dom/NodeList/NodeListExt.java b/SubLibrary/src/main/java/extensions/org/w3c/dom/NodeList/NodeListExt.java new file mode 100644 index 00000000..1eb95c01 --- /dev/null +++ b/SubLibrary/src/main/java/extensions/org/w3c/dom/NodeList/NodeListExt.java @@ -0,0 +1,20 @@ +package extensions.org.w3c.dom.NodeList; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import lombok.experimental.UtilityClass; +import manifold.ext.rt.api.Extension; +import manifold.ext.rt.api.This; +import org.jspecify.annotations.Nullable; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +@UtilityClass +@Extension +public class NodeListExt { + + public static Stream stream(@This @Nullable NodeList nodeList) { + return nodeList == null ? null : IntStream.range(0, nodeList.getLength()).mapToObj(nodeList::item); + } +} diff --git a/SubLibrary/src/main/java/org/lodder/subtools/multisubdownloader/Messages.java b/SubLibrary/src/main/java/org/lodder/subtools/multisubdownloader/Messages.java index 8ae5416d..064379af 100644 --- a/SubLibrary/src/main/java/org/lodder/subtools/multisubdownloader/Messages.java +++ b/SubLibrary/src/main/java/org/lodder/subtools/multisubdownloader/Messages.java @@ -5,43 +5,33 @@ import java.util.MissingResourceException; import java.util.ResourceBundle; +import manifold.ext.props.rt.api.var; import org.lodder.subtools.sublibrary.Language; public class Messages { - private static final String BUNDLE_NAME = "messages"; - private static final Language DEFAULT_LANGUAGE = Language.ENGLISH; - private static Language LANGUAGE; - private static ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME, Locale.forLanguageTag(DEFAULT_LANGUAGE.getLangCode())); + private static final String BUNDLE_NAME = "resourcebundle.Message"; + private static ResourceBundle resourceBundle = ResourceBundle.getBundle(BUNDLE_NAME, Locale.ROOT); + static @var Language language; private Messages() { } - public static String getString(String key) { + public static String getText(String key, Object... replacements) { try { - return RESOURCE_BUNDLE.getString(key); + String text = resourceBundle.getString(key); + return replacements == null || replacements.isEmpty() ? text : text.formatted(replacements); } catch (MissingResourceException e) { - return '!' + key + '!'; - } - } - - public static String getString(String key, Object... replacements) { - try { - return RESOURCE_BUNDLE.getString(key).formatted(replacements); - } catch (MissingResourceException e) { - return '!' + key + '!'; + return "!$key!"; } } public static void setLanguage(Language language) { - LANGUAGE = language; - RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME, Locale.forLanguageTag(language.getLangCode())); - } - - public static Language getLanguage() { - return LANGUAGE; + Messages.language = language; + Locale locale = language == Language.ENGLISH ? Locale.ROOT : Locale.forLanguageTag(language.langCode); + resourceBundle = ResourceBundle.getBundle(BUNDLE_NAME, locale); } public static List getAvailableLanguages() { - return List.of(Language.fromId("nl"), DEFAULT_LANGUAGE); + return List.of(Language.DUTCH, Language.ENGLISH); } } diff --git a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/CacheKeyMatchEnum.java b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/CacheKeyMatchEnum.java deleted file mode 100644 index 4680d4a6..00000000 --- a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/CacheKeyMatchEnum.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.lodder.subtools.sublibrary; - -public enum CacheKeyMatchEnum { - STARTING_WITH, ENDING_WITH, CONTAINING, EXACT -} \ No newline at end of file diff --git a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/ConfigProperties.java b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/ConfigProperties.java index bb3c331a..3dd3d930 100644 --- a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/ConfigProperties.java +++ b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/ConfigProperties.java @@ -2,28 +2,37 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Properties; + +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public final class ConfigProperties { - private static ConfigProperties configProps = null; - private final java.util.Properties prop = new java.util.Properties(); + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigProperties.class); + + private static final ConfigProperties instance = new ConfigProperties(); + private final Properties prop = new Properties(); private ConfigProperties() { try (InputStream input = getClass().getResourceAsStream("/config.properties")) { prop.load(input); } catch (IOException ex) { - ex.printStackTrace(); + LOGGER.error("Error loading config properties", ex); } } - public synchronized static ConfigProperties getInstance() { - if (configProps == null) { - configProps = new ConfigProperties(); - } - return configProps; + public static String getProperty(Property property) { + return instance.prop.getProperty(property.value); } - public String getProperty(String key) { - return prop.getProperty(key); + @AllArgsConstructor + public enum Property { + NAME("name"), + VERSION("version"); + + @val String value; } } diff --git a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Credentials.java b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Credentials.java new file mode 100644 index 00000000..32e43b58 --- /dev/null +++ b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Credentials.java @@ -0,0 +1,4 @@ +package org.lodder.subtools.sublibrary; + +public record Credentials(String username, String password) { +} diff --git a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/DetectLanguage.java b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/DetectLanguage.java index 7c781ccc..b376f2f3 100755 --- a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/DetectLanguage.java +++ b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/DetectLanguage.java @@ -7,28 +7,27 @@ import java.nio.file.Path; import java.util.Optional; -import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; -import org.lodder.subtools.sublibrary.util.lazy.LazyThrowingSupplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.optimaize.langdetect.LanguageDetector; import com.optimaize.langdetect.LanguageDetectorBuilder; import com.optimaize.langdetect.ngram.NgramExtractors; import com.optimaize.langdetect.profiles.LanguageProfileReader; import com.optimaize.langdetect.text.CommonTextObjectFactories; import com.optimaize.langdetect.text.TextObjectFactory; +import org.lodder.subtools.sublibrary.util.lazy.LazySupplier; +import org.lodder.subtools.sublibrary.util.lazy.LazyThrowingSupplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DetectLanguage { private static final Logger LOGGER = LoggerFactory.getLogger(DetectLanguage.class); - private static final LazyThrowingSupplier DETECTOR = - new LazyThrowingSupplier<>(() -> LanguageDetectorBuilder.create(NgramExtractors.standard()) - .shortTextAlgorithm(0) - .withProfiles(new LanguageProfileReader().readAllBuiltIn()) - .build()); + private static final LazyThrowingSupplier DETECTOR = new LazyThrowingSupplier<>( + () -> LanguageDetectorBuilder.create(NgramExtractors.standard()) + .shortTextAlgorithm(0) + .withProfiles(new LanguageProfileReader().readAllBuiltIn()) + .build()); private static final LazySupplier TEXT_OBJECT_FACTORY = - new LazySupplier<>(CommonTextObjectFactories::forDetectingOnLargeText); + new LazySupplier<>(CommonTextObjectFactories::forDetectingOnLargeText); private static final double MIN_PROBABILITY = 0.9; public static Language execute(Path file) { @@ -41,11 +40,15 @@ public static Language execute(Path file, Language defaultLang) { public static Optional executeOptional(Path file) { try (Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { - return DETECTOR.get().getProbabilities(TEXT_OBJECT_FACTORY.get().create().append(reader)).stream() - .filter(lang -> lang.getProbability() >= MIN_PROBABILITY).findFirst() - .map(lang -> lang.getLocale().getLanguage()).flatMap(Language::fromValueOptional); + return DETECTOR.get() + .getProbabilities(TEXT_OBJECT_FACTORY.get().create().append(reader)) + .stream() + .filter(lang -> lang.getProbability() >= MIN_PROBABILITY) + .findFirst() + .map(lang -> lang.getLocale().getLanguage()) + .flatMap(Language::fromValueOptional); } catch (IOException e) { - LOGGER.error("Could not detect language of file " + file); + LOGGER.error("Could not detect language of file {} ", file); return Optional.empty(); } } diff --git a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Language.java b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Language.java index 76d9c690..0c0af954 100644 --- a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Language.java +++ b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Language.java @@ -1,15 +1,13 @@ package org.lodder.subtools.sublibrary; -import java.util.Arrays; import java.util.Optional; import java.util.Set; import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public enum Language { ALBANIAN("App.Language.Albanian", "sq", Set.of("alb", "sq", "Albanian")), @@ -74,20 +72,23 @@ public enum Language { VIETNAMESE("App.Language.Vietnamese", "vi", Set.of("vie", "Vietnamese")), WELSH("App.Language.Welsh", "cy", Set.of("wel", "cym", "Welsh")); - private final String msgCode; - private final String langCode; - private final Set langCodesOther; + @val String msgCode; + @val String langCode; + @val Set langCodesOther; public static Language fromValue(String value) { - return Arrays.stream(Language.values()).filter(lang -> lang.name().equalsIgnoreCase(value)).findAny().orElseThrow(); + return Language.values().stream() + .filter(lang -> lang.name().equalsIgnoreCase(value)) + .findAny() + .orElseThrow(); } public static Optional fromValueOptional(String value) { - return Arrays.stream(Language.values()).filter(lang -> lang.name().equalsIgnoreCase(value)).findAny(); + return Language.values().stream().filter(lang -> lang.name().equalsIgnoreCase(value)).findAny(); } public static Optional fromIdOptional(String languageId) { - return Arrays.stream(Language.values()).filter(lang -> lang.getLangCode().equalsIgnoreCase(languageId)).findAny(); + return Language.values().stream().filter(lang -> lang.langCode.equalsIgnoreCase(languageId)).findAny(); } public static Language fromId(String languageId) { diff --git a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Manager.java b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Manager.java index 65681530..93cfdb1c 100644 --- a/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Manager.java +++ b/SubLibrary/src/main/java/org/lodder/subtools/sublibrary/Manager.java @@ -1,5 +1,8 @@ package org.lodder.subtools.sublibrary; +import static manifold.science.measures.TimeUnit.*; +import static manifold.science.util.UnitConstants.*; + import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; @@ -15,43 +18,38 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.OptionalLong; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; import java.util.function.Predicate; -import com.pivovarit.function.ThrowingSupplier; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; -import lombok.experimental.ExtensionMethod; -import name.falgout.jeffrey.throwing.Nothing; +import lombok.AllArgsConstructor; +import manifold.ext.props.rt.api.val; +import manifold.science.measures.Time; +import name.falgout.jeffrey.throwing.ThrowingFunction; +import name.falgout.jeffrey.throwing.ThrowingSupplier; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.jsoup.Jsoup; +import org.jspecify.annotations.Nullable; import org.lodder.subtools.sublibrary.cache.Cache; import org.lodder.subtools.sublibrary.cache.CacheType; import org.lodder.subtools.sublibrary.cache.DiskCache; import org.lodder.subtools.sublibrary.cache.InMemoryCache; -import org.lodder.subtools.sublibrary.util.IOUtils; -import org.lodder.subtools.sublibrary.util.OptionalExtension; +import org.lodder.subtools.sublibrary.util.Nothing; +import org.lodder.subtools.sublibrary.util.Sleep; +import org.lodder.subtools.sublibrary.util.http.CookieManager; import org.lodder.subtools.sublibrary.util.http.HttpClient; import org.lodder.subtools.sublibrary.util.http.HttpClientException; import org.lodder.subtools.sublibrary.xml.XMLHelper; import org.w3c.dom.Document; -@Setter -@RequiredArgsConstructor -@ExtensionMethod({ OptionalExtension.class }) +@AllArgsConstructor public class Manager { - private final HttpClient httpClient; - private final InMemoryCache inMemoryCache; - private final DiskCache diskCache; + @val HttpClient httpClient; + @val InMemoryCache inMemoryCache; + @val DiskCache diskCache; public boolean store(String downloadLink, Path file) throws ManagerException { try { @@ -69,55 +67,30 @@ public void storeCookies(String domain, Map cookieMap) { // POST \\ // ==== \\ - public PostBuilderUrlIntf postBuilder() { - return new PostBuilder(httpClient); - } - - public interface PostBuilderUrlIntf { - PostBuilderUserAgentIntf url(String url); - } - - public interface PostBuilderUserAgentIntf extends PostBuilderDataMapIntf { - PostBuilderDataMapIntf userAgent(String userAgent); - } - - public interface PostBuilderDataMapIntf extends PostBuilderDataIntf { - PostBuilderPostIntf data(Map data); + public PostBuilder postBuilder(String url, @Nullable String userAgent=null) { + return new PostBuilder(httpClient, url, userAgent); } - public interface PostBuilderDataIntf extends PostBuilderPostIntf { - PostBuilderDataIntf addData(String key, String value); - } - - public interface PostBuilderPostIntf { - String post() throws ManagerException; + public static class PostBuilder { + @val HttpClient httpClient; + @val String url; + @val @Nullable String userAgent; + private Map data = new HashMap<>(); - org.jsoup.nodes.Document postAsJsoupDocument() throws ManagerException; - } + public PostBuilder(HttpClient httpClient, String url, @Nullable String userAgent=null) { + this.httpClient = httpClient; + this.url = url; + this.userAgent = userAgent; + } - @Setter - @Accessors(chain = true, fluent = true) - @RequiredArgsConstructor - public static class PostBuilder - implements PostBuilderUrlIntf, PostBuilderUserAgentIntf, PostBuilderDataMapIntf, PostBuilderDataIntf, PostBuilderPostIntf { - private final HttpClient httpClient; - private String url; - private String userAgent; - private Map data; - - @Override - public PostBuilderDataIntf addData(String key, String value) { - if (data == null) { - data = new HashMap<>(); - } + public PostBuilder addData(String key, String value) { data.put(key, value); return this; } - @Override public String post() throws ManagerException { try { - return httpClient.doPost(new URI(url).toURL(), userAgent, data == null ? new HashMap<>() : data); + return httpClient.doPost(new URI(url).toURL(), userAgent, data); } catch (MalformedURLException | URISyntaxException e) { throw new ManagerException("incorrect url", e); } catch (HttpClientException e) { @@ -125,7 +98,6 @@ public String post() throws ManagerException { } } - @Override public org.jsoup.nodes.Document postAsJsoupDocument() throws ManagerException { return Jsoup.parse(post()); } @@ -136,781 +108,321 @@ public org.jsoup.nodes.Document postAsJsoupDocument() throws ManagerException { // GET PAGE CONTENT \\ // ================ \\ - public PageContentBuilderUrlIntf getPageContentBuilder() { - return new PageContentBuilder(httpClient, inMemoryCache); - } - - public interface PageContentBuilderUrlIntf { - PageContentBuilderUserAgentIntf url(String url); + public String get(PageContentParams params) throws ManagerException { + return switch (params.cacheType) { + case NONE -> getContentWithoutCache(params); + case MEMORY -> inMemoryCache.getOrPut(params.url + params.cookieManager(), + () -> getContentWithoutCache(params)); + case DISK -> throw new IllegalArgumentException("Unexpected value: " + params.cacheType); + }; } - public interface PageContentBuilderUserAgentIntf extends PageContentBuilderCacheTypeIntf { - PageContentBuilderCacheTypeIntf userAgent(String userAgent); + public InputStream getAsInputStream(PageContentParams params) throws ManagerException { + return get(params).toInputStream(StandardCharsets.UTF_8); } - public interface PageContentBuilderCacheTypeIntf extends PageContentBuilderRetryIntf { - PageContentBuilderRetryIntf cacheType(CacheType cacheType); + public @Nullable Document getAsDocument(PageContentParams params, + @Nullable Predicate emptyResultPredicate=null) + throws ParserConfigurationException, ManagerException, IOException { + Optional asStringDocument = getAsStringDocument(params, emptyResultPredicate); + return asStringDocument.isPresent() ? XMLHelper.getDocument(asStringDocument.get()) : null; } - public interface PageContentBuilderRetryIntf extends PageContentBuilderGetIntf { - PageContentBuilderRetryConditionIntf retries(int retries); + public org.jsoup.nodes.Document getAsJsoupDocument(PageContentParams params, + @Nullable Predicate emptyResultPredicate=null) throws ManagerException { + return getAsStringDocument(params, emptyResultPredicate).map(Jsoup::parse).orElse(null); } - public interface PageContentBuilderRetryConditionIntf { - PageContentBuilderRetryWaitIntf retryPredicate(Predicate retryPredicate); - } - - public interface PageContentBuilderRetryWaitIntf { - PageContentBuilderGetIntf retryWait(int retryWait); - } - - public interface PageContentBuilderGetIntf { - String get() throws ManagerException; - - InputStream getAsInputStream() throws ManagerException; - - Optional getAsDocument() throws ParserConfigurationException, ManagerException; - - Optional getAsDocument(Predicate emptyResultPredicate) throws ParserConfigurationException, ManagerException; - - org.jsoup.nodes.Document getAsJsoupDocument() throws ManagerException; - - Optional getAsJsoupDocument(Predicate emptyResultPredicate) throws ManagerException; - - JSONObject getAsJsonObject() throws ManagerException; - - JSONArray getAsJsonArray() throws ManagerException; - } - - @Setter - @Accessors(chain = true, fluent = true) - @RequiredArgsConstructor - public static class PageContentBuilder implements PageContentBuilderGetIntf, PageContentBuilderCacheTypeIntf, - PageContentBuilderUserAgentIntf, PageContentBuilderUrlIntf, PageContentBuilderRetryIntf, PageContentBuilderRetryConditionIntf, - PageContentBuilderRetryWaitIntf { - private final HttpClient httpClient; - private final InMemoryCache inMemoryCache; - private String url; - private String userAgent = "Mozilla/5.25 Netscape/5.0 (Windows; I; Win95)"; - private CacheType cacheType; - private int retries; - private Predicate retryPredicate; - private int retryWait; - - @Override - public PageContentBuilder retries(int retries) { - if (retries < 0) { - throw new IllegalStateException("Number of retries cannot be less than 0"); - } - this.retries = retries; - return this; - } - - @Override - public String get() throws ManagerException { - if (cacheType == null) { - cacheType = CacheType.NONE; - } - return switch (cacheType) { - case NONE -> getContentWithoutCache(url, userAgent); - case MEMORY -> inMemoryCache.getOrPut(url, () -> getContentWithoutCache(url, userAgent)); - case DISK -> throw new IllegalArgumentException("Unexpected value: " + cacheType); - }; - } - - @Override - public InputStream getAsInputStream() throws ManagerException { - return IOUtils.toInputStream(get(), StandardCharsets.UTF_8); - } - - @Override - public Optional getAsDocument() throws ParserConfigurationException, ManagerException { - return XMLHelper.getDocument(get()); - } - - @Override - public Optional getAsDocument(Predicate emptyResultPredicate) throws ParserConfigurationException, ManagerException { - String html = get(); - return StringUtils.isBlank(html) || (emptyResultPredicate != null && emptyResultPredicate.test(html)) ? Optional.empty() - : XMLHelper.getDocument(html); - } - - @Override - public org.jsoup.nodes.Document getAsJsoupDocument() throws ManagerException { - return Jsoup.parse(get()); - } - - @Override - public Optional getAsJsoupDocument(Predicate emptyResultPredicate) throws ManagerException { - String html = get(); - return StringUtils.isBlank(html) || (emptyResultPredicate != null && emptyResultPredicate.test(html)) ? Optional.empty() - : Optional.of(Jsoup.parse(html)); - } - - @Override - public JSONObject getAsJsonObject() throws ManagerException { - try { - return new JSONObject(new String(getAsInputStream().readAllBytes(), StandardCharsets.UTF_8)); - } catch (JSONException | IOException | ManagerException e) { - throw new ManagerException(e); - } - } - - @Override - public JSONArray getAsJsonArray() throws ManagerException { - try { - return new JSONArray(new String(getAsInputStream().readAllBytes(), StandardCharsets.UTF_8)); - } catch (JSONException | IOException | ManagerException e) { - throw new ManagerException(e); - } - } - - private String getContentWithoutCache(String urlString, String userAgent) throws ManagerException { - try { - return httpClient.doGet(new URI(urlString).toURL(), userAgent); - } catch (HttpClientException e) { - if (retries-- > 0 && retryPredicate.test(e)) { - try { - Thread.sleep(retryWait * 1000L); - } catch (InterruptedException e1) { - // continue - } - return getContentWithoutCache(urlString, userAgent); - } - throw new ManagerException("Error occurred with httpclient response: %s %s".formatted(e.getResponseCode(), e.getResponseMessage()), - e); - } catch (IOException e) { - if (retries-- > 0 && retryPredicate.test(e)) { - return getContentWithoutCache(urlString, userAgent); - } - throw new ManagerException(e); - } catch (URISyntaxException e) { - throw new ManagerException("Invalid url [%s]".formatted(urlString), e); - } + private Optional getAsStringDocument(PageContentParams params, + @Nullable Predicate emptyResultPredicate) throws ManagerException { + if (emptyResultPredicate == null) { + return Optional.of(get(params)); } + String html = get(params); + return StringUtils.isBlank(html) || emptyResultPredicate.test(html) ? Optional.empty() : Optional.of(html); } - // =========== \\ - // CLEAR CACHE \\ - // =========== \\ - - public ClearExpiredCacheBuilderCacheTypeIntf clearExpiredCacheBuilder() { - return new ClearExpiredCacheBuilder<>(inMemoryCache, diskCache); + public JSONObject getAsJsonObject(PageContentParams params) throws ManagerException { + return new JSONObject(getAsJsonString(params)); } - public interface ClearExpiredCacheBuilderCacheTypeIntf { - - ClearExpiredCacheBuilderKeyFilterIntf cacheType(CacheType cacheType); + public JSONArray getAsJsonArray(PageContentParams params) throws ManagerException { + return new JSONArray(getAsJsonString(params)); } - public interface ClearExpiredCacheBuilderKeyFilterIntf extends ClearExpiredCacheBuilderClearIntf { - - ClearExpiredCacheBuilderClearIntf keyFilter(Predicate keyFilter); + private String getAsJsonString(PageContentParams params) throws ManagerException { + try { + return new String(getAsInputStream(params).readAllBytes(), StandardCharsets.UTF_8); + } catch (JSONException | IOException | ManagerException e) { + throw new ManagerException(e); + } } - public interface ClearExpiredCacheBuilderClearIntf { - - void clear(); + private String getContentWithoutCache(PageContentParams params) throws ManagerException { + return getContentWithoutCache(params.url, params.userAgent, params.retry, params.cookieManager); } - @Setter - @Accessors(chain = true, fluent = true) - @RequiredArgsConstructor - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static class ClearExpiredCacheBuilder - implements ClearExpiredCacheBuilderCacheTypeIntf, ClearExpiredCacheBuilderKeyFilterIntf, ClearExpiredCacheBuilderClearIntf { - private final InMemoryCache inMemoryCache; - private final DiskCache diskCache; - private CacheType cacheType; - private Predicate keyFilter; - - @Override - public ClearExpiredCacheBuilder keyFilter(Predicate keyFilter) { - this.keyFilter = (Predicate) keyFilter; - return (ClearExpiredCacheBuilder) this; - } - - @Override - public void clear() { - switch (cacheType) { - case MEMORY -> inMemoryCache.cleanup(keyFilter); - case DISK -> diskCache.cleanup(keyFilter); - default -> throw new IllegalArgumentException("Unexpected value: " + cacheType); + private String getContentWithoutCache(String url, String userAgent, Retry retry, + CookieManager cookieManager) throws ManagerException { + try { + return httpClient.doGet(new URI(url).toURL(), userAgent, cookieManager); + } catch (HttpClientException e) { + if (retry.canRetry() && retry.predicate.test(e)) { + return getContentWithoutCache(url, userAgent, retry.decreaseRetries().sleep(), cookieManager); } + throw new ManagerException( + "Error occurred with httpclient response: %s %s while accessing %s".formatted(e.responseCode, + e.responseMessage, url), e); + } catch (IOException e) { + if (retry.canRetry() && retry.predicate.test(e)) { + return getContentWithoutCache(url, userAgent, retry.decreaseRetries(), cookieManager); + } + throw new ManagerException(e); + } catch (URISyntaxException e) { + throw new ManagerException("Invalid url [%s]".formatted(url), e); } } - // ============= \\ - // VALUE BUILDER \\ - // ============= \\ - - public ValueBuilderCacheTypeIntf valueBuilder() { - return new ValueBuilder<>(inMemoryCache, diskCache); - } - - public interface ValueBuilderCacheTypeIntf { - - ValueBuilderKeyIntf cacheType(CacheType cacheType); - - ValueBuilderKeyIntf memoryCache(); - - ValueBuilderKeyIntf diskCache(); - } - - public interface ValueBuilderKeyIntf { - ValueBuilderIsPresentIntf key(String key); - - ValuesBuilderCacheTypeIntf keyFilter(Predicate keyFilter); - } - - public interface ValueBuilderIsPresentIntf extends ValuesBuilderCacheTypeIntf { - boolean isPresent(); - - boolean isExpiredTemporary(); - - boolean isTemporaryObject(); - - OptionalLong getTemporaryTimeToLive(); - } - - public interface ValuesBuilderCacheTypeIntf extends ValueBuilderRetryIntf { - ValueBuilderGetOptionalIntf returnType(Class returnType); - - , S extends T> ValueBuilderGetCollectionIntf returnType(Class collectionReturnType, - Class returnType); - - void remove(); - } - - public interface ValueBuilderRetryIntf extends ValueBuilderValueSupplierIntf { - ValueBuilderRetryConditionIntf retries(int retries); - - ValueBuilderGetValueStoreTempValueIntf value(S value); - - ValueBuilderGetOptionalStoreTempValueIntf optionalValue(Optional optionalValue); - - ValueBuilderGetOptionalIntStoreTempValueIntf optionalIntValue(OptionalInt optionalIntValue); - - , S extends T> ValueBuilderGetCollectionIntf collectionValue(C collectionValue); - } - - public interface ValueBuilderRetryConditionIntf { - ValueBuilderRetryWaitIntf retryPredicate(Predicate retryPredicate); - } - - public interface ValueBuilderRetryWaitIntf { - ValueBuilderValueSupplierIntf retryWait(int retryWait); - } - - public interface ValueBuilderValueSupplierIntf { - - ValueBuilderGetValueStoreTempValueIntf valueSupplier(ThrowingSupplier valueSupplier); - - , S extends T, X extends Exception> ValueBuilderGetCollectionIntf - collectionSupplier(Class collectionValueType, ThrowingSupplier valueSupplier); - - ValueBuilderGetOptionalStoreTempValueIntf - optionalSupplier(ThrowingSupplier, X> valueSupplier); - - ValueBuilderGetOptionalIntStoreTempValueIntf - optionalIntSupplier(ThrowingSupplier optionalIntSupplier); - } - - public interface ValueBuilderGetValueStoreTempValueIntf extends ValueBuilderGetValueIntf { - ValueBuilderGetValueStoreTempValueTtlIntf storeTempNullValue(); - } + public record Retry(int retries, Predicate predicate, Time waitTime) { - public interface ValueBuilderGetValueStoreTempValueTtlIntf - extends ValueBuilderGetValueIntf { - ValueBuilderGetValueIntf timeToLive(long seconds); + public static final Retry NONE = new Retry(0, null, 0 Second); - ValueBuilderGetValueIntf timeToLiveFunction(Function timeToLiveFunction); - } - - public interface ValueBuilderGetValueIntf extends ValueBuilderStoreIntf { - T get() throws X; - } - - public interface ValueBuilderGetOptionalStoreTempValueIntf - extends ValueBuilderGetOptionalIntf { - ValueBuilderGetOptionalStoreTempValueTtlIntf storeTempNullValue(); - } - - public interface ValueBuilderGetOptionalStoreTempValueTtlIntf - extends ValueBuilderGetOptionalIntf { - ValueBuilderGetOptionalIntf timeToLive(long seconds); - - ValueBuilderGetOptionalIntf timeToLiveFunction(Function timeToLiveFunction); - } - - public interface ValueBuilderGetOptionalIntf extends ValueBuilderStoreIntf { - List> getEntries(); - - Optional getOptional() throws X; - } - - public interface ValueBuilderGetOptionalIntStoreTempValueIntf extends ValueBuilderGetOptionalIntIntf { - ValueBuilderGetOptionalIntStoreTempValueTtlIntf storeTempNullValue(); - } - - public interface ValueBuilderGetOptionalIntStoreTempValueTtlIntf extends ValueBuilderGetOptionalIntIntf { - ValueBuilderGetOptionalIntIntf timeToLive(long seconds); - - ValueBuilderGetOptionalIntIntf timeToLiveFunction(Function timeToLiveFunction); - } - - public interface ValueBuilderGetOptionalIntIntf extends ValueBuilderStoreIntf { - OptionalInt getOptionalInt() throws X; - } - - // public interface ValueBuilderGetCollectionStoreTempValueIntf, T, X extends Exception> - // extends ValueBuilderGetCollectionIntf { - // ValueBuilderGetCollectionStoreTempValueTtlIntf storeTempValue(); - // } - // - // public interface ValueBuilderGetCollectionStoreTempValueTtlIntf, T, X extends Exception> - // extends ValueBuilderGetCollectionIntf { - // ValueBuilderGetCollectionIntf timeToLive(long seconds); - // - // ValueBuilderGetCollectionIntf timeToLiveFunction(Function timeToLiveFunction); - // } - - public interface ValueBuilderGetCollectionIntf, T, X extends Exception> - extends ValueBuilderStoreIntf { - C getCollection() throws X; - } - - public interface ValueBuilderStoreIntf { - void store() throws X; - - void storeAsTempValue() throws X; - } - - @Setter - @Accessors(chain = true, fluent = true) - @RequiredArgsConstructor - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static class ValueBuilder, T, X extends Exception> - implements ValueBuilderGetOptionalIntf, ValueBuilderCacheTypeIntf, - ValueBuilderValueSupplierIntf, ValueBuilderKeyIntf, ValueBuilderGetCollectionIntf, ValueBuilderGetOptionalIntIntf, - ValueBuilderRetryIntf, ValueBuilderRetryConditionIntf, ValueBuilderRetryWaitIntf, - ValueBuilderIsPresentIntf, ValuesBuilderCacheTypeIntf, - ValueBuilderStoreIntf, ValueBuilderGetOptionalIntStoreTempValueIntf, ValueBuilderGetOptionalStoreTempValueIntf, - ValueBuilderGetOptionalIntStoreTempValueTtlIntf, ValueBuilderGetOptionalStoreTempValueTtlIntf, - ValueBuilderGetValueStoreTempValueIntf, ValueBuilderGetValueStoreTempValueTtlIntf, ValueBuilderGetValueIntf { - private final InMemoryCache inMemoryCache; - private final DiskCache diskCache; - private String key; - private ThrowingSupplier valueSupplier; - private ThrowingSupplier collectionSupplier; - private ThrowingSupplier, X> optionalSupplier; - private ThrowingSupplier optionalIntSupplier; - private T value; - private Optional optionalValue; - private OptionalInt optionalIntValue; - private C collectionValue; - - private CacheType cacheType; - private Class returnType; - private int retries; - private Predicate retryPredicate; - private int retryWait; - private Predicate keyFilter; - @Setter(value = AccessLevel.NONE) - private Long timeToLive; - @Setter(value = AccessLevel.NONE) - private boolean storeTempNullValue; - private Function timeToLiveFunction; - - // - // @Override - // public ValueBuilder timeToLiveFunction(Function timeToLiveFunction) { - // this.timeToLiveFunction = timeToLiveFunction; - // return this; - // } - @Override - public ValueBuilder optionalIntValue(OptionalInt optionalIntValue) { - this.optionalIntValue = optionalIntValue; - return (ValueBuilder) this; - } - - @Override - public ValueBuilder retries(int retries) { + public Retry { if (retries < 0) { throw new IllegalStateException("Number of retries cannot be less than 0"); } - this.retries = retries; - return this; - } - - @Override - public ValueBuilder memoryCache() { - this.cacheType = CacheType.MEMORY; - return this; - } - - @Override - public ValueBuilder diskCache() { - this.cacheType = CacheType.DISK; - return (ValueBuilder) this; - } - - @Override - public ValueBuilder returnType(Class returnType) { - this.returnType = (Class) returnType; - return (ValueBuilder) this; - } - - @Override - public , S extends T> ValueBuilderGetCollectionIntf - returnType(Class collectionReturnType, Class returnType) { - this.returnType = (Class) returnType; - return (ValueBuilder) this; } - @Override - public ValueBuilder valueSupplier(ThrowingSupplier valueSupplier) { - this.valueSupplier = (ThrowingSupplier) valueSupplier; - return (ValueBuilder) this; + public Retry decreaseRetries() { + return new Retry(retries - 1, predicate, waitTime); } - @Override - public ValueBuilder - optionalSupplier(ThrowingSupplier, E> valueSupplier) { - this.optionalSupplier = (ThrowingSupplier) valueSupplier; - return (ValueBuilder) this; + public boolean canRetry() { + return retries > 0; } - @Override - public ValueBuilder optionalIntSupplier(ThrowingSupplier optionalIntSupplier) { - this.optionalIntSupplier = (ThrowingSupplier) optionalIntSupplier; - return (ValueBuilder) this; - } - - @Override - public , S extends T, E extends Exception> ValueBuilder - collectionSupplier(Class collectionValueType, ThrowingSupplier collectionSupplier) { - this.collectionSupplier = (ThrowingSupplier) collectionSupplier; - return (ValueBuilder) this; + public Retry sleep() { + Sleep.sleep(waitTime); + return this; } + } - @Override - public ValueBuilder value(S value) { - this.value = value; - return (ValueBuilder) this; - } + // ============= \\ + // CACHE METHODS \\ + // ============= \\ - @Override - public ValueBuilder optionalValue(Optional optionalValue) { - this.optionalValue = (Optional) optionalValue; - return (ValueBuilder) this; + public record CacheKey(Manager manager, CacheType cacheType, String key) { + public boolean isPresent() { + return manager.getOptionalCache(cacheType).map(cache -> cache.contains(key)).orElse(false); } - @Override - public , S extends T> ValueBuilder collectionValue(L collectionValue) { - this.collectionValue = (C) collectionValue; - return (ValueBuilder) this; + public boolean isExpiredTemporary() { + return manager.getOptionalCache(cacheType).map(cache -> cache.isTemporaryExpired(key)).orElse(false); } - @Override - public ValueBuilder storeTempNullValue() { - this.storeTempNullValue = true; - return this; + public boolean isTemporaryObject() { + return manager.getOptionalCache(cacheType).map(cache -> cache.isTemporaryObject(key)).orElse(false); } - @Override - public ValueBuilder timeToLive(long seconds) { - this.timeToLive = seconds * 1000; - return this; + public Optional