diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java b/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java index 16b2f338a..4d707824a 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java @@ -27,6 +27,7 @@ import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.json.NamespaceDetailsJson; import org.eclipse.openvsx.util.TargetPlatform; +import org.eclipse.openvsx.util.TimeUtil; import org.eclipse.openvsx.util.VersionAlias; import org.springframework.stereotype.Component; @@ -88,7 +89,7 @@ public List validateNamespaceDetails(NamespaceDetailsJson json) { var detectedType = tika.detect(in, json.logo); var logoType = MimeTypes.getDefaultMimeTypes().getRegisteredMimeType(detectedType); if(logoType != null) { - json.logo = "logo-" + json.name + logoType.getExtension(); + json.logo = "logo-" + json.name + "-" + System.currentTimeMillis() + logoType.getExtension(); if(!logoType.getType().equals(MediaType.image("png")) && !logoType.getType().equals(MediaType.image("jpg"))) { issues.add(new Issue("Namespace logo should be of png or jpg type")); } diff --git a/server/src/main/java/org/eclipse/openvsx/UserService.java b/server/src/main/java/org/eclipse/openvsx/UserService.java index 77c281992..7c3a0136a 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserService.java +++ b/server/src/main/java/org/eclipse/openvsx/UserService.java @@ -9,18 +9,12 @@ ********************************************************************************/ package org.eclipse.openvsx; -import java.util.Arrays; -import java.util.Objects; -import java.util.UUID; - -import javax.persistence.EntityManager; -import javax.transaction.Transactional; - import com.google.common.base.Joiner; +import org.apache.commons.io.IOUtils; import org.eclipse.openvsx.cache.CacheService; import org.eclipse.openvsx.entities.*; -import org.eclipse.openvsx.json.NamespaceDetailsJson; import org.eclipse.openvsx.json.AccessTokenJson; +import org.eclipse.openvsx.json.NamespaceDetailsJson; import org.eclipse.openvsx.json.ResultJson; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.security.IdPrincipal; @@ -35,6 +29,14 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Component; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Objects; +import java.util.UUID; + import static org.eclipse.openvsx.cache.CacheService.CACHE_NAMESPACE_DETAILS_JSON; import static org.eclipse.openvsx.util.UrlUtil.createApiUrl; @@ -234,16 +236,31 @@ public ResultJson updateNamespaceDetails(NamespaceDetailsJson details) { if(!Objects.equals(details.socialLinks, namespace.getSocialLinks())) { namespace.setSocialLinks(details.socialLinks); } - if(!Arrays.equals(details.logoBytes, namespace.getLogoBytes())) { - if (details.logoBytes != null) { - if (namespace.getLogoBytes() != null) { + if(details.logoBytes == null) { + details.logoBytes = new byte[0]; + } + + boolean contentEquals; + var oldLogo = storageUtil.downloadNamespaceLogo(namespace); + try ( + var newLogoInput = new ByteArrayInputStream(details.logoBytes); + var oldLogoInput = Files.newInputStream(oldLogo) + ) { + contentEquals = IOUtils.contentEquals(newLogoInput, oldLogoInput); + } catch (IOException e) { + throw new RuntimeException(e); + } + + if(!contentEquals) { + if (details.logoBytes.length > 0) { + if (namespace.getLogoStorageType() != null) { storageUtil.removeNamespaceLogo(namespace); } namespace.setLogoName(details.logo); namespace.setLogoBytes(details.logoBytes); storeNamespaceLogo(namespace); - } else if (namespace.getLogoBytes() != null) { + } else if (namespace.getLogoStorageType() != null) { storageUtil.removeNamespaceLogo(namespace); namespace.setLogoName(null); namespace.setLogoBytes(null); @@ -251,6 +268,12 @@ public ResultJson updateNamespaceDetails(NamespaceDetailsJson details) { } } + try { + Files.delete(oldLogo); + } catch (IOException e) { + throw new RuntimeException(e); + } + return ResultJson.success("Updated details for namespace " + details.name); } diff --git a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java index dca0cd965..b66ccbbce 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java @@ -26,6 +26,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; @Component @@ -189,4 +190,15 @@ public URI getNamespaceLogoLocation(Namespace namespace) { protected String getBlobName(Namespace namespace) { return UrlUtil.createApiUrl("", namespace.getName(), "logo", namespace.getLogoName()).substring(1); // remove first '/' } + + @Override + public Path downloadNamespaceLogo(Namespace namespace) { + try { + var logoFile = Files.createTempFile("namespace-logo", ".png"); + getContainerClient().getBlobClient(getBlobName(namespace)).downloadToFile(logoFile.toString(), true); + return logoFile; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java index d5fc46856..7f78b22a0 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; @@ -185,4 +186,24 @@ protected String getObjectId(Namespace namespace) { return UrlUtil.createApiUrl("", namespace.getName(), "logo", namespace.getLogoName()).substring(1); // remove first '/' } + @Override + public Path downloadNamespaceLogo(Namespace namespace) { + Path logoFile; + try { + logoFile = Files.createTempFile("namespace-logo", ".png"); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try ( + var reader = getStorage().reader(BlobId.of(bucketId, getObjectId(namespace))); + var output = new FileOutputStream(logoFile.toFile()); + ) { + output.getChannel().transferFrom(reader, 0, Long.MAX_VALUE); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return logoFile; + } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java index 469671b18..991da607f 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/IStorageService.java @@ -56,4 +56,6 @@ public interface IStorageService { * Returns the public access location of a namespace logo. */ URI getNamespaceLogoLocation(Namespace namespace); + + Path downloadNamespaceLogo(Namespace namespace); } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java index c55faa326..7988afba9 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java @@ -30,7 +30,9 @@ import javax.persistence.EntityManager; import javax.transaction.Transactional; +import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.TimeUnit; @@ -221,6 +223,37 @@ public URI getNamespaceLogoLocation(Namespace namespace) { } } + public Path downloadNamespaceLogo(Namespace namespace) { + if(namespace.getLogoStorageType() == null) { + return createNamespaceLogoFile(); + } + + switch (namespace.getLogoStorageType()) { + case STORAGE_GOOGLE: + return googleStorage.downloadNamespaceLogo(namespace); + case STORAGE_AZURE: + return azureStorage.downloadNamespaceLogo(namespace); + case STORAGE_DB: + try { + var logoFile = createNamespaceLogoFile(); + Files.write(logoFile, namespace.getLogoBytes()); + return logoFile; + } catch (IOException e) { + throw new RuntimeException(e); + } + default: + return createNamespaceLogoFile(); + } + } + + private Path createNamespaceLogoFile() { + try { + return Files.createTempFile("namespace-logo", ".png"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private String getFileUrl(String name, ExtensionVersion extVersion, String serverUrl) { return UrlUtil.createApiFileUrl(serverUrl, extVersion, name); }