Rework to avoid using preferences object to retrieve options
This commit is contained in:
@@ -22,7 +22,7 @@ public class TarArchiver implements Archiver {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compress(final List<Path> files, final Path destination) throws IOException {
|
||||
public void compress(final List<Path> files, final Path destination) {
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public class XZArchiver implements Archiver {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compress(final List<Path> files, final Path destination) throws IOException {
|
||||
public void compress(final List<Path> files, final Path destination) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,15 @@ import com.github.gtache.autosubtitle.Audio;
|
||||
import com.github.gtache.autosubtitle.File;
|
||||
import com.github.gtache.autosubtitle.Video;
|
||||
import com.github.gtache.autosubtitle.VideoConverter;
|
||||
import com.github.gtache.autosubtitle.VideoInfo;
|
||||
import com.github.gtache.autosubtitle.impl.AudioInfoImpl;
|
||||
import com.github.gtache.autosubtitle.impl.FileAudioImpl;
|
||||
import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegBundledPath;
|
||||
import com.github.gtache.autosubtitle.modules.setup.ffmpeg.FFmpegSystemPath;
|
||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||
import com.github.gtache.autosubtitle.subtitle.ExportOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.FormatException;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -28,7 +29,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@@ -41,31 +41,28 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
private final Path bundledPath;
|
||||
private final Path systemPath;
|
||||
private final SubtitleConverterProvider converterProvider;
|
||||
private final Preferences preferences;
|
||||
private final ProcessRunner processRunner;
|
||||
|
||||
@Inject
|
||||
FFmpegVideoConverter(@FFmpegBundledPath final Path bundledPath, @FFmpegSystemPath final Path systemPath,
|
||||
final SubtitleConverterProvider converterProvider, final Preferences preferences,
|
||||
final ProcessRunner processRunner) {
|
||||
final SubtitleConverterProvider converterProvider, final ProcessRunner processRunner) {
|
||||
this.bundledPath = requireNonNull(bundledPath);
|
||||
this.systemPath = requireNonNull(systemPath);
|
||||
this.converterProvider = requireNonNull(converterProvider);
|
||||
this.preferences = requireNonNull(preferences);
|
||||
this.processRunner = requireNonNull(processRunner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Video addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles) throws IOException {
|
||||
public Video addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles, final ExportOptions options) throws IOException {
|
||||
final var out = getTempFile(video.info().format());
|
||||
addSoftSubtitles(video, subtitles, out);
|
||||
addSoftSubtitles(video, subtitles, options, out);
|
||||
return new FileVideoImpl(out, video.info());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles, final Path path) throws IOException {
|
||||
public void addSoftSubtitles(final Video video, final Collection<? extends SubtitleCollection<?>> subtitles, final ExportOptions options, final Path path) throws IOException {
|
||||
final var videoPath = getPath(video);
|
||||
final var collectionMap = dumpCollections(subtitles, video.info());
|
||||
final var collectionMap = dumpCollections(subtitles, options);
|
||||
final var args = new ArrayList<String>();
|
||||
args.add(getFFmpegPath());
|
||||
args.add("-y");
|
||||
@@ -108,18 +105,18 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Video addHardSubtitles(final Video video, final SubtitleCollection<?> subtitles) throws IOException {
|
||||
public Video addHardSubtitles(final Video video, final SubtitleCollection<?> subtitles, final ExportOptions options) throws IOException {
|
||||
final var out = getTempFile(video.info().format());
|
||||
addHardSubtitles(video, subtitles, out);
|
||||
addHardSubtitles(video, subtitles, options, out);
|
||||
return new FileVideoImpl(out, video.info());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHardSubtitles(final Video video, final SubtitleCollection<?> subtitles, final Path path) throws IOException {
|
||||
public void addHardSubtitles(final Video video, final SubtitleCollection<?> subtitles, final ExportOptions options, final Path path) throws IOException {
|
||||
final var videoPath = getPath(video);
|
||||
final var subtitlesPath = dumpSubtitles(subtitles, video.info());
|
||||
final var subtitlesPath = dumpSubtitles(subtitles, options);
|
||||
final var escapedPath = escapeVF(subtitlesPath.toString());
|
||||
final var subtitleArg = getSubtitleConverter().formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'";
|
||||
final var subtitleArg = getSubtitleConverter(options).formatName().equalsIgnoreCase("ass") ? "ass='" + escapedPath + "'" : "subtitles='" + escapedPath + "'";
|
||||
final var args = List.of(
|
||||
getFFmpegPath(),
|
||||
"-y",
|
||||
@@ -132,8 +129,8 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
runLog(args, Duration.ofHours(1));
|
||||
}
|
||||
|
||||
private SubtitleConverter<?> getSubtitleConverter() {
|
||||
return converterProvider.getConverter(preferences.get("outputFormat", "srt"));
|
||||
private SubtitleConverter<?> getSubtitleConverter(final ExportOptions options) {
|
||||
return converterProvider.getConverter(options.outputFormat());
|
||||
}
|
||||
|
||||
private static String escapeVF(final String path) {
|
||||
@@ -179,18 +176,22 @@ public class FFmpegVideoConverter implements VideoConverter {
|
||||
return path;
|
||||
}
|
||||
|
||||
private <T extends SubtitleCollection<?>> SequencedMap<T, Path> dumpCollections(final Collection<T> collections, final VideoInfo videoInfo) throws IOException {
|
||||
private <T extends SubtitleCollection<?>> SequencedMap<T, Path> dumpCollections(final Collection<? extends T> collections, final ExportOptions options) throws IOException {
|
||||
final var ret = LinkedHashMap.<T, Path>newLinkedHashMap(collections.size());
|
||||
for (final var subtitles : collections) {
|
||||
ret.put(subtitles, dumpSubtitles(subtitles, videoInfo));
|
||||
ret.put(subtitles, dumpSubtitles(subtitles, options));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Path dumpSubtitles(final SubtitleCollection<?> subtitles, final VideoInfo videoInfo) throws IOException {
|
||||
final var path = getTempFile(getSubtitleConverter().formatName().toLowerCase());
|
||||
Files.writeString(path, getSubtitleConverter().format(subtitles, videoInfo));
|
||||
return path;
|
||||
private Path dumpSubtitles(final SubtitleCollection<?> subtitles, final ExportOptions options) throws IOException {
|
||||
final var path = getTempFile(getSubtitleConverter(options).formatName().toLowerCase());
|
||||
try {
|
||||
Files.writeString(path, getSubtitleConverter(options).format(subtitles, options.formatOptions()));
|
||||
return path;
|
||||
} catch (final FormatException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getTempFile(final String extension) throws IOException {
|
||||
|
||||
@@ -9,7 +9,6 @@ module com.github.gtache.autosubtitle.ffmpeg {
|
||||
requires org.apache.logging.log4j;
|
||||
requires org.tukaani.xz;
|
||||
requires org.apache.commons.compress;
|
||||
requires java.prefs;
|
||||
|
||||
exports com.github.gtache.autosubtitle.ffmpeg;
|
||||
exports com.github.gtache.autosubtitle.setup.ffmpeg;
|
||||
|
||||
@@ -8,7 +8,11 @@ import com.github.gtache.autosubtitle.impl.FileVideoImpl;
|
||||
import com.github.gtache.autosubtitle.process.ProcessListener;
|
||||
import com.github.gtache.autosubtitle.process.ProcessResult;
|
||||
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
||||
import com.github.gtache.autosubtitle.subtitle.ExportOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.OutputFormat;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.FormatException;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.FormatOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverterProvider;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -28,7 +32,6 @@ import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@@ -40,29 +43,31 @@ class TestFFmpegVideoConverter {
|
||||
|
||||
private final ProcessRunner runner;
|
||||
private final ProcessListener listener;
|
||||
private final ProcessResult result;
|
||||
private final ProcessResult processResult;
|
||||
private final SubtitleConverter<?> subtitleConverter;
|
||||
private final SubtitleConverterProvider subtitleConverterProvider;
|
||||
private final Video video;
|
||||
private final InputStream in;
|
||||
private final VideoInfo videoInfo;
|
||||
private final InputStream in;
|
||||
private final ExportOptions options;
|
||||
private final FormatOptions formatOptions;
|
||||
private final SubtitleCollection<?> frCollection;
|
||||
private final SubtitleCollection<?> deCollection;
|
||||
private final Preferences preferences;
|
||||
private final Path systemPath;
|
||||
|
||||
TestFFmpegVideoConverter(@Mock final SubtitleConverter<?> subtitleConverter, @Mock final ProcessRunner runner,
|
||||
@Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video,
|
||||
@Mock final VideoInfo videoInfo, @Mock final SubtitleCollection<?> frCollection, @Mock final SubtitleCollection<?> deCollection,
|
||||
@Mock final Preferences preferences, @Mock final ProcessListener listener, @Mock final InputStream in,
|
||||
@Mock final ProcessResult result) {
|
||||
@Mock final SubtitleConverterProvider subtitleConverterProvider, @Mock final Video video, @Mock final VideoInfo videoInfo,
|
||||
@Mock final ExportOptions options, @Mock final SubtitleCollection<?> frCollection,
|
||||
@Mock final SubtitleCollection<?> deCollection, @Mock final ProcessListener listener, @Mock final InputStream in,
|
||||
@Mock final ProcessResult processResult, @Mock final FormatOptions formatOptions) {
|
||||
this.video = requireNonNull(video);
|
||||
this.runner = requireNonNull(runner);
|
||||
this.result = requireNonNull(result);
|
||||
this.videoInfo = requireNonNull(videoInfo);
|
||||
this.runner = requireNonNull(runner);
|
||||
this.options = requireNonNull(options);
|
||||
this.formatOptions = requireNonNull(formatOptions);
|
||||
this.processResult = requireNonNull(processResult);
|
||||
this.subtitleConverter = requireNonNull(subtitleConverter);
|
||||
this.subtitleConverterProvider = requireNonNull(subtitleConverterProvider);
|
||||
this.preferences = requireNonNull(preferences);
|
||||
this.listener = requireNonNull(listener);
|
||||
this.in = requireNonNull(in);
|
||||
this.systemPath = Paths.get("system");
|
||||
@@ -71,25 +76,26 @@ class TestFFmpegVideoConverter {
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws IOException {
|
||||
void beforeEach() throws IOException, FormatException {
|
||||
when(video.info()).thenReturn(videoInfo);
|
||||
when(subtitleConverterProvider.getConverter("srt")).thenReturn((SubtitleConverter) subtitleConverter);
|
||||
when(subtitleConverterProvider.getConverter(OutputFormat.SRT)).thenReturn((SubtitleConverter) subtitleConverter);
|
||||
when(subtitleConverter.formatName()).thenReturn("srt");
|
||||
when(subtitleConverter.format(any(), any())).thenReturn("");
|
||||
when(preferences.get("outputFormat", "srt")).thenReturn("srt");
|
||||
when(options.outputFormat()).thenReturn(OutputFormat.SRT);
|
||||
when(options.formatOptions()).thenReturn(formatOptions);
|
||||
when(video.getInputStream()).thenReturn(in);
|
||||
when(frCollection.language()).thenReturn(Language.FR);
|
||||
when(deCollection.language()).thenReturn(Language.DE);
|
||||
when(runner.startListen(anyList())).thenReturn(listener);
|
||||
when(listener.join(Duration.ofHours(1))).thenReturn(result);
|
||||
when(result.exitCode()).thenReturn(0);
|
||||
when(listener.join(Duration.ofHours(1))).thenReturn(processResult);
|
||||
when(processResult.exitCode()).thenReturn(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddSoftSubtitlesMp4() throws IOException {
|
||||
void testAddSoftSubtitlesMp4() throws IOException, FormatException {
|
||||
when(videoInfo.format()).thenReturn("mp4");
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||
final var result = converter.addSoftSubtitles(video, List.of(frCollection, deCollection));
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, runner);
|
||||
final var result = converter.addSoftSubtitles(video, List.of(frCollection, deCollection), options);
|
||||
|
||||
assertEquals(videoInfo, result.info());
|
||||
|
||||
@@ -125,8 +131,8 @@ class TestFFmpegVideoConverter {
|
||||
assertTrue(args.get(5).endsWith(".srt"));
|
||||
assertTrue(args.get(7).endsWith(".srt"));
|
||||
assertTrue(args.get(26).endsWith(".mp4"));
|
||||
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||
verify(subtitleConverter).format(deCollection, videoInfo);
|
||||
verify(subtitleConverter).format(frCollection, formatOptions);
|
||||
verify(subtitleConverter).format(deCollection, formatOptions);
|
||||
verify(in).transferTo(any());
|
||||
}
|
||||
|
||||
@@ -135,19 +141,18 @@ class TestFFmpegVideoConverter {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddSoftSubtitlesMkvPathFile() throws IOException {
|
||||
void testAddSoftSubtitlesMkvPathFile() throws IOException, FormatException {
|
||||
when(videoInfo.format()).thenReturn("mkv");
|
||||
doReturn(null).when(subtitleConverterProvider).getConverter("srt");
|
||||
when(subtitleConverterProvider.getConverter("ass")).thenReturn((SubtitleConverter) subtitleConverter);
|
||||
doReturn(null).when(subtitleConverterProvider).getConverter(OutputFormat.SRT);
|
||||
when(subtitleConverterProvider.getConverter(OutputFormat.ASS)).thenReturn((SubtitleConverter) subtitleConverter);
|
||||
when(subtitleConverter.formatName()).thenReturn("ass");
|
||||
when(preferences.get("outputFormat", "srt")).thenReturn("ass");
|
||||
when(options.outputFormat()).thenReturn(OutputFormat.ASS);
|
||||
final var fileVideo = mock(FileVideoImpl.class);
|
||||
when(fileVideo.info()).thenReturn(videoInfo);
|
||||
final var path = Paths.get("path.mkv");
|
||||
when(fileVideo.path()).thenReturn(path);
|
||||
final var out = Paths.get("out.mkv");
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||
converter.addSoftSubtitles(fileVideo, List.of(frCollection, deCollection), out);
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, runner);
|
||||
converter.addSoftSubtitles(fileVideo, List.of(frCollection, deCollection), options, out);
|
||||
|
||||
final var args = getArgs();
|
||||
assertEquals(27, args.size());
|
||||
@@ -181,16 +186,16 @@ class TestFFmpegVideoConverter {
|
||||
checkEquals(argMap, args);
|
||||
assertTrue(args.get(5).endsWith(".ass"));
|
||||
assertTrue(args.get(7).endsWith(".ass"));
|
||||
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||
verify(subtitleConverter).format(deCollection, videoInfo);
|
||||
verify(subtitleConverter).format(frCollection, formatOptions);
|
||||
verify(subtitleConverter).format(deCollection, formatOptions);
|
||||
verifyNoInteractions(in);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddHardSubtitles() throws IOException {
|
||||
void testAddHardSubtitles() throws IOException, FormatException {
|
||||
when(videoInfo.format()).thenReturn("mp4");
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||
final var result = converter.addHardSubtitles(video, frCollection);
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, runner);
|
||||
final var result = converter.addHardSubtitles(video, frCollection, options);
|
||||
|
||||
assertEquals(videoInfo, result.info());
|
||||
|
||||
@@ -206,19 +211,19 @@ class TestFFmpegVideoConverter {
|
||||
assertTrue(args.get(3).endsWith(".mp4"));
|
||||
assertTrue(args.get(5).startsWith("subtitles="));
|
||||
assertTrue(args.get(6).endsWith(".mp4"));
|
||||
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||
verify(subtitleConverter).format(frCollection, formatOptions);
|
||||
verify(in).transferTo(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddHardAssSubtitles() throws IOException {
|
||||
doReturn(null).when(subtitleConverterProvider).getConverter("srt");
|
||||
when(subtitleConverterProvider.getConverter("ass")).thenReturn((SubtitleConverter) subtitleConverter);
|
||||
void testAddHardAssSubtitles() throws IOException, FormatException {
|
||||
doReturn(null).when(subtitleConverterProvider).getConverter(OutputFormat.SRT);
|
||||
when(subtitleConverterProvider.getConverter(OutputFormat.ASS)).thenReturn((SubtitleConverter) subtitleConverter);
|
||||
when(subtitleConverter.formatName()).thenReturn("ass");
|
||||
when(preferences.get("outputFormat", "srt")).thenReturn("ass");
|
||||
when(options.outputFormat()).thenReturn(OutputFormat.ASS);
|
||||
when(videoInfo.format()).thenReturn("mp4");
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||
final var result = converter.addHardSubtitles(video, frCollection);
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, runner);
|
||||
final var result = converter.addHardSubtitles(video, frCollection, options);
|
||||
|
||||
assertEquals(videoInfo, result.info());
|
||||
|
||||
@@ -234,7 +239,7 @@ class TestFFmpegVideoConverter {
|
||||
assertTrue(args.get(3).endsWith(".mp4"));
|
||||
assertTrue(args.get(5).startsWith("ass="));
|
||||
assertTrue(args.get(6).endsWith(".mp4"));
|
||||
verify(subtitleConverter).format(frCollection, videoInfo);
|
||||
verify(subtitleConverter).format(frCollection, formatOptions);
|
||||
verify(in).transferTo(any());
|
||||
}
|
||||
|
||||
@@ -242,7 +247,7 @@ class TestFFmpegVideoConverter {
|
||||
void testGetAudio() throws IOException {
|
||||
when(videoInfo.format()).thenReturn("mp4");
|
||||
when(videoInfo.duration()).thenReturn(25000L);
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, runner);
|
||||
final var result = converter.getAudio(video);
|
||||
|
||||
assertEquals(new AudioInfoImpl("wav", 25000L), result.info());
|
||||
@@ -267,10 +272,10 @@ class TestFFmpegVideoConverter {
|
||||
|
||||
@Test
|
||||
void testGetAudioException() {
|
||||
when(result.exitCode()).thenReturn(1);
|
||||
when(processResult.exitCode()).thenReturn(1);
|
||||
when(videoInfo.format()).thenReturn("mp4");
|
||||
when(videoInfo.duration()).thenReturn(25000L);
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, preferences, runner);
|
||||
final var converter = new FFmpegVideoConverter(Paths.get("bundled"), systemPath, subtitleConverterProvider, runner);
|
||||
assertThrows(IOException.class, () -> converter.getAudio(video));
|
||||
}
|
||||
|
||||
@@ -280,7 +285,7 @@ class TestFFmpegVideoConverter {
|
||||
Files.createFile(bundled);
|
||||
when(videoInfo.format()).thenReturn("mp4");
|
||||
when(videoInfo.duration()).thenReturn(25000L);
|
||||
final var converter = new FFmpegVideoConverter(bundled, systemPath, subtitleConverterProvider, preferences, runner);
|
||||
final var converter = new FFmpegVideoConverter(bundled, systemPath, subtitleConverterProvider, runner);
|
||||
final var result = converter.getAudio(video);
|
||||
|
||||
assertEquals(new AudioInfoImpl("wav", 25000L), result.info());
|
||||
|
||||
@@ -95,7 +95,7 @@ class TestFFmpegSetupManager {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetStatusSystemException() throws IOException, SetupException {
|
||||
void testGetStatusSystemException() throws IOException {
|
||||
final var ffmpegPath = Paths.get("path");
|
||||
when(configuration.bundledFFmpegPath()).thenReturn(ffmpegPath);
|
||||
when(processRunner.run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5))).thenThrow(IOException.class);
|
||||
|
||||
Reference in New Issue
Block a user