diff --git a/sass-embedded-host/src/main/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProvider.java b/sass-embedded-host/src/main/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProvider.java index 1266f77a..35beafcb 100644 --- a/sass-embedded-host/src/main/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProvider.java +++ b/sass-embedded-host/src/main/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProvider.java @@ -46,21 +46,35 @@ synchronized File extractPackage() throws IOException { } File execDir = targetPath.resolve("dart-sass").toFile(); + File result = findExecutable(execDir); - File[] execFile = execDir.listFiles(pathname -> pathname.isFile() && pathname.getName().startsWith("sass")); - - File result; + if (result == null && !IOUtils.isEmpty(targetPath)) { + log.info("No unique dart-sass executable found in {}, extracting {} again", execDir, dist); + try { + IOUtils.extract(dist, targetPath); + } catch (IOException e) { + throw new IOException(String.format("Failed to extract %s into %s", dist, targetPath), e); + } + result = findExecutable(execDir); + } - if (execFile == null || execFile.length != 1) { + if (result == null) { throw new IllegalStateException("No (unique) executable file found in " + execDir); - } else { - result = execFile[0]; } result.setExecutable(true, true); return result; } + private File findExecutable(File execDir) { + File[] execFile = execDir.listFiles(pathname -> pathname.isFile() && pathname.getName().startsWith("sass")); + if (execFile == null || execFile.length != 1) { + return null; + } + + return execFile[0]; + } + Path getTargetPath() throws IOException { String dartSassVersion = PropertyUtils.getDartSassVersion(); String hostVersion = PropertyUtils.getHostVersion(); diff --git a/sass-embedded-host/src/test/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProviderTest.java b/sass-embedded-host/src/test/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProviderTest.java index 18078f58..1531020e 100644 --- a/sass-embedded-host/src/test/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProviderTest.java +++ b/sass-embedded-host/src/test/java/de/larsgrefer/sass/embedded/connection/DartSassPackageProviderTest.java @@ -4,8 +4,17 @@ import de.larsgrefer.sass.embedded.SassCompilerFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import javax.annotation.Nullable; +import java.io.File; +import java.io.OutputStream; import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -33,4 +42,55 @@ void testConcurrentAccess() throws IOException { } } -} \ No newline at end of file + + @Test + void extractPackageRestoresMissingExecutable(@TempDir Path tempDir) throws IOException { + Path archive = tempDir.resolve("dart-sass.zip"); + createDartSassZip(archive); + + Path targetPath = tempDir.resolve("target"); + DartSassPackageProvider provider = new TestPackageProvider(archive.toUri().toURL(), targetPath); + + File executable = provider.extractPackage(); + assertThat(executable).isFile(); + + Files.delete(executable.toPath()); + Files.write(targetPath.resolve("dart-sass").resolve("kept-by-temp-cleaner"), new byte[0]); + + File restoredExecutable = provider.extractPackage(); + assertThat(restoredExecutable).isFile(); + } + + private void createDartSassZip(Path archive) throws IOException { + try (OutputStream outputStream = Files.newOutputStream(archive); + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { + zipOutputStream.putNextEntry(new ZipEntry("dart-sass/")); + zipOutputStream.closeEntry(); + zipOutputStream.putNextEntry(new ZipEntry("dart-sass/sass")); + zipOutputStream.write(new byte[]{1}); + zipOutputStream.closeEntry(); + } + } + + private static class TestPackageProvider extends DartSassPackageProvider { + + private final URL packageUrl; + private final Path targetPath; + + TestPackageProvider(URL packageUrl, Path targetPath) { + this.packageUrl = packageUrl; + this.targetPath = targetPath; + } + + @Override + Path getTargetPath() { + return targetPath; + } + + @Nullable + @Override + protected URL getPackageUrl() { + return packageUrl; + } + } +}