package com.github.sbugat.ec2tools.service.update; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.egit.github.core.Repository; import org.eclipse.egit.github.core.RepositoryTag; import org.eclipse.egit.github.core.service.RepositoryService; import org.slf4j.ext.XLogger; import org.slf4j.ext.XLoggerFactory; /** * Simple generic version checker on GitHub, inpect target jar and local jar build date to determinated if an update is available. * * The checker use an independant thread to check and download the file. * * @author Sylvain Bugat * */ @Singleton public final class UpdateService implements Runnable { /** SLF4J XLogger. */ private static final XLogger LOG = XLoggerFactory.getXLogger(UpdateService.class); /** Jar extension. */ private static final String JAR_EXTENSION = ".jar"; //$NON-NLS-1$ /** Tmp extension. */ private static final String TMP_EXTENSION = ".tmp"; //$NON-NLS-1$ /** Target directory in zipball releases. */ private static final String TARGET_DIRECTORY = "target"; //$NON-NLS-1$ /** Root URL of the GitHub project to update. */ public static final String gitHubUser = "Sylvain-Bugat"; //$NON-NLS-1$ /** GitHub repository. */ public static final String gitHubRepository = "aws-ec2-start-stop-tools"; //$NON-NLS-1$ /** Maven artifact identifier. */ private static final String mavenArtifactId = gitHubRepository; /** * GitHub repository service. */ @Inject private RepositoryService repositoryService; /** * Background thread launched to check the version on GitHub. */ @Override public void run() { LOG.entry(); final String currentJar = currentJar(); if (null == currentJar) { LOG.exit(); return; } try { final Repository repository = repositoryService.getRepository(gitHubUser, gitHubRepository); final String currentVersion = 'v' + currentJar.replaceFirst("^" + mavenArtifactId + '-', "").replaceFirst(JAR_EXTENSION + '$', ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ RepositoryTag recentRelease = null; for (final RepositoryTag tag : repositoryService.getTags(repository)) { if (null != recentRelease && tag.getName().compareTo(recentRelease.getName()) > 0) { recentRelease = tag; } else if (tag.getName().compareTo(currentVersion) > 0) { recentRelease = tag; } } if (null == recentRelease) { LOG.exit(); return; } findAndDownloadReleaseJar(recentRelease); LOG.exit(); } catch (final Exception e) { // Ignore any error during update process // Just delete the temporary file cleanOldAndTemporaryJar(); LOG.exit(e); } } /** * Find a jar in release and if it is a newer version, ask to download it. * * @param release GitHub last release to use * @return true if a new release jar has been found * @throws IOException in case of reading error */ private boolean findAndDownloadReleaseJar(final RepositoryTag release) throws IOException { LOG.entry(release); try (final InputStream remoteJarInputStream = new URL(release.getZipballUrl()).openStream()) { final ZipInputStream zipInputStream = new ZipInputStream(remoteJarInputStream); ZipEntry entry = zipInputStream.getNextEntry(); while (null != entry) { if (entry.getName().matches(".*/" + TARGET_DIRECTORY + '/' + mavenArtifactId + "-[0-9\\.]*" + JAR_EXTENSION)) { //$NON-NLS-1$ //$NON-NLS-2$ final String jarFileBaseName = entry.getName().replaceFirst("^.*/", ""); //$NON-NLS-1$ //$NON-NLS-2$ downloadFile(zipInputStream, jarFileBaseName + TMP_EXTENSION); Files.move(Paths.get(jarFileBaseName + TMP_EXTENSION), Paths.get(jarFileBaseName)); LOG.exit(true); return true; } entry = zipInputStream.getNextEntry(); } } LOG.exit(false); return false; } /** * Clean the old jar and any existing temporary file. */ private void cleanOldAndTemporaryJar() { LOG.entry(); try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get("."))) { //$NON-NLS-1$ for (final Path path : directoryStream) { final String fileName = path.getFileName().toString(); if (fileName.startsWith(mavenArtifactId) && fileName.endsWith(JAR_EXTENSION + TMP_EXTENSION)) { deleteJar(path); } } LOG.exit(); } catch (final IOException e) { // Ignore any error during the delete process LOG.exit(e); } } /** * Delete a jar file. * * @param jarFileToDelete file to delete */ private void deleteJar(final Path jarFileToDelete) { LOG.entry(); if (Files.exists(jarFileToDelete)) { try { Files.delete(jarFileToDelete); LOG.exit(); } catch (final IOException e) { // Ignore any error during the delete process LOG.exit(e); } } } /** * Return the current executed jar. * * @return the name of the current executed jar */ private String currentJar() { LOG.entry(); String currentJar = null; try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get("."))) { //$NON-NLS-1$ for (final Path path : directoryStream) { final String fileName = path.getFileName().toString(); if (fileName.startsWith(mavenArtifactId) && fileName.endsWith(JAR_EXTENSION) && (null == currentJar || currentJar.compareTo(fileName) < 0)) { currentJar = fileName; } } } catch (final IOException e) { // Ignore any error during the process currentJar = null; } LOG.exit(currentJar); return currentJar; } /** * Download a file/URL and write it to a destination file. * * @param inputStream source stream * @param destinationFile destination file * @throws IOException in case of copy error */ private void downloadFile(final InputStream inputStream, final String destinationFile) throws IOException { Files.copy(inputStream, Paths.get(destinationFile)); } }