From 7d324f8dec477b4ec61898b04e432b1042a5fa90 Mon Sep 17 00:00:00 2001 From: amvanbaren Date: Mon, 4 Apr 2022 21:39:50 +0200 Subject: [PATCH] Added publisher displayName Added browse endpoint to VSCodeAdapter Removed WEB_RESOURCE, instead use RESOURCE FileResource java migration Updated web resource tests --- .../eclipse/openvsx/ExtensionProcessor.java | 25 +-- .../openvsx/adapter/VSCodeAdapter.java | 149 +++++++++++-- ...V1_23__FileResource_Extract_Resources.java | 196 ++++++++++++++++++ .../eclipse/openvsx/dto/FileResourceDTO.java | 22 ++ .../openvsx/entities/FileResource.java | 3 +- .../FileResourceDTORepository.java | 18 +- .../repositories/RepositoryService.java | 6 +- .../storage/AzureBlobStorageService.java | 22 +- .../storage/GoogleCloudStorageService.java | 20 +- .../eclipse/openvsx/storage/StorageUtil.java | 38 ++++ .../openvsx/storage/StorageUtilService.java | 40 +--- .../openvsx/adapter/VSCodeAdapterTest.java | 140 ++++++++++++- .../adapter/findid-yaml-response-alpine.json | 10 +- .../openvsx/adapter/findid-yaml-response.json | 10 +- .../adapter/findname-yaml-response-linux.json | 10 +- .../adapter/findname-yaml-response.json | 10 +- .../adapter/search-yaml-response-darwin.json | 10 +- .../adapter/search-yaml-response-targets.json | 26 ++- .../openvsx/adapter/search-yaml-response.json | 10 +- 19 files changed, 645 insertions(+), 120 deletions(-) create mode 100644 server/src/main/java/org/eclipse/openvsx/db/migration/V1_23__FileResource_Extract_Resources.java create mode 100644 server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java b/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java index cbfaf6a24..4b72caaab 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java @@ -285,12 +285,8 @@ private List getEngines(JsonNode node) { return null; } - private boolean isWebExtensionKind(ExtensionVersion extension) { - return extension.getTags().contains(WEB_EXTENSION_TAG); - } - public List getResources(ExtensionVersion extension) { - var resources = new ArrayList(); + var resources = new ArrayList<>(getAllResources(extension)); var binary = getBinary(extension); if (binary != null) resources.add(binary); @@ -309,27 +305,24 @@ public List getResources(ExtensionVersion extension) { var icon = getIcon(extension); if (icon != null) resources.add(icon); - if (isWebExtensionKind(extension)) - resources.addAll(getWebResources(extension)); + return resources; } - protected List getWebResources(ExtensionVersion extension) { + protected List getAllResources(ExtensionVersion extension) { readInputStream(); - var namePrefix = "extension/"; return zipFile.stream() - .filter(zipEntry -> zipEntry.getName().startsWith(namePrefix)) .map(zipEntry -> { var bytes = ArchiveUtil.readEntry(zipFile, zipEntry); if (bytes == null) { return null; } - var webResource = new FileResource(); - webResource.setExtension(extension); - webResource.setName(zipEntry.getName()); - webResource.setType(FileResource.WEB_RESOURCE); - webResource.setContent(bytes); - return webResource; + var resource = new FileResource(); + resource.setExtension(extension); + resource.setName(zipEntry.getName()); + resource.setType(FileResource.RESOURCE); + resource.setContent(bytes); + return resource; }) .filter(Objects::nonNull) .collect(Collectors.toList()); diff --git a/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java b/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java index c956c65a9..f96c7e49a 100644 --- a/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java +++ b/server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAdapter.java @@ -9,10 +9,14 @@ ********************************************************************************/ package org.eclipse.openvsx.adapter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import org.eclipse.openvsx.dto.*; +import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.FileResource; +import org.eclipse.openvsx.entities.Namespace; import org.eclipse.openvsx.repositories.RepositoryService; import org.eclipse.openvsx.search.SearchUtilService; import org.eclipse.openvsx.storage.GoogleCloudStorageService; @@ -30,7 +34,9 @@ import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.ModelAndView; +import javax.persistence.EntityManager; import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -51,6 +57,9 @@ public class VSCodeAdapter { @Autowired RepositoryService repositories; + @Autowired + EntityManager entityManager; + @Autowired VSCodeIdService idService; @@ -152,7 +161,6 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para totalCount = (long) extensions.size(); } - var flags = param.flags; var extensionsMap = extensions.stream().collect(Collectors.toMap(e -> e.getId(), e -> e)); List allActiveExtensionVersions = repositories.findAllActiveExtensionVersionDTOs(extensionsMap.keySet(), targetPlatform); @@ -185,21 +193,21 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para .sorted(comparator) .collect(Collectors.groupingBy(ExtensionVersionDTO::getExtension)); - Map> resources; + Map> fileResources; if (test(flags, FLAG_INCLUDE_FILES) && !extensionVersionsMap.isEmpty()) { - var types = List.of(MANIFEST, README, LICENSE, ICON, DOWNLOAD, CHANGELOG, WEB_RESOURCE); + var types = List.of(MANIFEST, README, LICENSE, ICON, DOWNLOAD, CHANGELOG, RESOURCE); var idsMap = extensionVersionsMap.values().stream() .flatMap(Collection::stream) .collect(Collectors.toMap(ev -> ev.getId(), ev -> ev)); - resources = repositories.findAllFileResourceDTOsByExtensionVersionIdAndType(idsMap.keySet(), types).stream() + fileResources = repositories.findAllFileResourceDTOsByExtensionVersionIdAndType(idsMap.keySet(), types).stream() .map(r -> { r.setExtensionVersion(idsMap.get(r.getExtensionVersionId())); return r; }) .collect(Collectors.groupingBy(FileResourceDTO::getExtensionVersion)); } else { - resources = Collections.emptyMap(); + fileResources = Collections.emptyMap(); } Map activeReviewCounts; @@ -222,7 +230,7 @@ public ExtensionQueryResult extensionQuery(@RequestBody ExtensionQueryParam para var latest = latestVersions.get(extension.getId()); var queryExt = toQueryExtension(extension, latest, activeReviewCounts, flags); queryExt.versions = extensionVersionsMap.getOrDefault(extension, Collections.emptyList()).stream() - .map(extVer -> toQueryVersion(extVer, resources, flags)) + .map(extVer -> toQueryVersion(extVer, fileResources, flags)) .collect(Collectors.toList()); extensionQueryResults.add(queryExt); @@ -326,12 +334,13 @@ private FileResource getFileFromDB(ExtensionVersion extVersion, String assetType case FILE_ICON: return repositories.findFileByType(extVersion, FileResource.ICON); default: { - if (assetType.startsWith(FILE_WEB_RESOURCES)) { - var name = assetType.substring((FILE_WEB_RESOURCES.length())); - return repositories.findFileByTypeAndName(extVersion, FileResource.WEB_RESOURCE, name); - } else { - return null; - } + var name = assetType.startsWith(FILE_WEB_RESOURCES) + ? assetType.substring((FILE_WEB_RESOURCES.length())) + : null; + + return name != null && name.startsWith("extension/") // is web resource + ? repositories.findFileByTypeAndName(extVersion, FileResource.RESOURCE, name) + : null; } } } @@ -373,6 +382,101 @@ public ModelAndView download(@PathVariable String namespace, @PathVariable Strin return new ModelAndView("redirect:" + apiUrl, model); } + @GetMapping("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/**") + @CrossOrigin + public ResponseEntity browse( + HttpServletRequest request, + @PathVariable String namespaceName, + @PathVariable String extensionName, + @PathVariable String version + ) { + var path = UrlUtil.extractWildcardPath(request); + var resources = repositories.findAllResourceFileResourceDTOs(namespaceName, extensionName, version, path); + if(resources.isEmpty()) { + return ResponseEntity.notFound().build(); + } else if(resources.size() == 1 && resources.get(0).getName().equals(path)) { + return browseFile(resources.get(0), namespaceName, extensionName, version); + } else { + return browseDirectory(resources, namespaceName, extensionName, version, path); + } + } + + private ResponseEntity browseFile( + FileResourceDTO resource, + String namespaceName, + String extensionName, + String version + ) { + if (resource.getStorageType().equals(FileResource.STORAGE_DB)) { + var headers = storageUtil.getFileResponseHeaders(resource.getName()); + return new ResponseEntity<>(resource.getContent(), headers, HttpStatus.OK); + } else { + var namespace = new Namespace(); + namespace.setName(namespaceName); + + var extension = new Extension(); + extension.setName(extensionName); + extension.setNamespace(namespace); + + var extVersion = new ExtensionVersion(); + extVersion.setVersion(version); + extVersion.setExtension(extension); + + var fileResource = new FileResource(); + fileResource.setName(resource.getName()); + fileResource.setExtension(extVersion); + + return ResponseEntity.status(HttpStatus.FOUND) + .location(storageUtil.getLocation(fileResource)) + .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS).cachePublic()) + .build(); + } + } + + private ResponseEntity browseDirectory( + List resources, + String namespaceName, + String extensionName, + String version, + String path + ) { + if(!path.isEmpty() && !path.endsWith("/")) { + path += "/"; + } + + var urls = new HashSet(); + var baseUrl = UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "vscode", "unpkg", namespaceName, extensionName, version); + for(var resource : resources) { + var name = resource.getName(); + if(name.startsWith(path)) { + var index = name.indexOf('/', path.length()); + var isDirectory = index != -1; + if(isDirectory) { + name = name.substring(0, index); + } + + var url = UrlUtil.createApiUrl(baseUrl, name.split("/")); + if(isDirectory) { + url += '/'; + } + + urls.add(url); + } + } + + String json; + try { + json = new ObjectMapper().writeValueAsString(urls); + } catch (JsonProcessingException e) { + throw new ErrorResultException("Failed to generate JSON: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS).cachePublic()) + .body(json.getBytes(StandardCharsets.UTF_8)); + } + private ExtensionQueryResult.Extension toQueryExtension(ExtensionDTO extension, ExtensionVersionDTO latest, Map activeReviewCounts, int flags) { var namespace = extension.getNamespace(); @@ -384,6 +488,7 @@ private ExtensionQueryResult.Extension toQueryExtension(ExtensionDTO extension, queryExt.publisher = new ExtensionQueryResult.Publisher(); queryExt.publisher.publisherId = namespace.getPublicId(); queryExt.publisher.publisherName = namespace.getName(); + queryExt.publisher.displayName = namespace.getName(); queryExt.tags = latest.getTags(); // TODO: add these // queryExt.releaseDate @@ -415,7 +520,7 @@ private ExtensionQueryResult.Extension toQueryExtension(ExtensionDTO extension, private ExtensionQueryResult.ExtensionVersion toQueryVersion( ExtensionVersionDTO extVer, - Map> resources, + Map> fileResources, int flags ) { var queryVer = new ExtensionQueryResult.ExtensionVersion(); @@ -451,11 +556,11 @@ private ExtensionQueryResult.ExtensionVersion toQueryVersion( } } - if(resources.containsKey(extVer)) { - var resourcesByType = resources.get(extVer).stream() + if(fileResources.containsKey(extVer)) { + var resourcesByType = fileResources.get(extVer).stream() .collect(Collectors.groupingBy(FileResourceDTO::getType)); - var webResources = resourcesByType.remove(WEB_RESOURCE); + var resources = resourcesByType.remove(RESOURCE); var fileBaseUrl = UrlUtil.createApiFileBaseUrl(serverUrl, namespaceName, extensionName, extVer.getTargetPlatform(), extVer.getVersion()); queryVer.files = Lists.newArrayList(); @@ -466,11 +571,13 @@ private ExtensionQueryResult.ExtensionVersion toQueryVersion( queryVer.addFile(FILE_VSIX, createFileUrl(resourcesByType.get(DOWNLOAD), fileBaseUrl)); queryVer.addFile(FILE_CHANGELOG, createFileUrl(resourcesByType.get(CHANGELOG), fileBaseUrl)); - if (webResources != null) { - for (var webResource : webResources) { - var name = webResource.getName(); - var url = createFileUrl(webResource, fileBaseUrl); - queryVer.addFile(FILE_WEB_RESOURCES + name, url); + if (resources != null) { + for (var resource : resources) { + if(resource.isWebResource()) { + var name = resource.getName(); + var url = createFileUrl(resource, fileBaseUrl); + queryVer.addFile(FILE_WEB_RESOURCES + name, url); + } } } } diff --git a/server/src/main/java/org/eclipse/openvsx/db/migration/V1_23__FileResource_Extract_Resources.java b/server/src/main/java/org/eclipse/openvsx/db/migration/V1_23__FileResource_Extract_Resources.java new file mode 100644 index 000000000..633a4e534 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/db/migration/V1_23__FileResource_Extract_Resources.java @@ -0,0 +1,196 @@ +/** ****************************************************************************** + * Copyright (c) 2022 Precies. Software and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ + +package org.eclipse.openvsx.db.migration; + +import org.eclipse.openvsx.ExtensionProcessor; +import org.eclipse.openvsx.entities.Extension; +import org.eclipse.openvsx.entities.ExtensionVersion; +import org.eclipse.openvsx.entities.FileResource; +import org.eclipse.openvsx.entities.Namespace; +import org.eclipse.openvsx.storage.AzureBlobStorageService; +import org.eclipse.openvsx.storage.GoogleCloudStorageService; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.io.ByteArrayInputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class V1_23__FileResource_Extract_Resources extends BaseJavaMigration { + + private static final String COL_FR_ID = "fr_id"; + private static final String COL_FR_NAME = "fr_name"; + private static final String COL_FR_TYPE = "fr_type"; + private static final String COL_FR_CONTENT = "fr_content"; + private static final String COL_FR_STORAGE_TYPE = "fr_storage_type"; + private static final String COL_EV_ID = "ev_id"; + private static final String COL_EV_VERSION = "ev_version"; + private static final String COL_E_NAME = "e_name"; + private static final String COL_N_NAME = "n_name"; + + @Autowired + RestTemplate restTemplate; + + @Autowired + GoogleCloudStorageService googleStorage; + + @Autowired + AzureBlobStorageService azureStorage; + + @Override + public void migrate(Context context) throws Exception { + var connection = context.getConnection(); + var downloads = getAllDownloads(connection); + var resources = extractResources(downloads); + uploadResources(resources); + deleteResources(connection); + insertResources(resources, connection); + deleteWebResources(connection); + } + + private List getAllDownloads(Connection connection) throws SQLException { + var query = "SELECT " + + "fr.id " + COL_FR_ID + ", " + + "fr.name " + COL_FR_NAME + ", " + + "fr.type " + COL_FR_TYPE + ", " + + "fr.content " + COL_FR_CONTENT + ", " + + "fr.storage_type " + COL_FR_STORAGE_TYPE + ", " + + "ev.id " + COL_EV_ID + ", " + + "ev.version " + COL_EV_VERSION + ", " + + "e.name " + COL_E_NAME + ", " + + "n.name " + COL_N_NAME + " " + + "FROM file_resource fr " + + "JOIN extension_version ev ON ev.id = fr.extension_id " + + "JOIN extension e ON e.id = ev.extension_id " + + "JOIN namespace n ON n.id = e.namespace_id " + + "WHERE fr.type = 'download'"; + + var downloads = new ArrayList(); + try(var statement = connection.prepareStatement(query)) { + try(var result = statement.executeQuery()) { + while(result.next()) { + downloads.add(toFileResource(result)); + } + } + } + + return downloads; + } + + private FileResource toFileResource(ResultSet result) throws SQLException { + var namespace = new Namespace(); + namespace.setName(result.getString(COL_N_NAME)); + + var extension = new Extension(); + extension.setName(result.getString(COL_E_NAME)); + extension.setNamespace(namespace); + + var extVersion = new ExtensionVersion(); + extVersion.setId(result.getLong(COL_EV_ID)); + extVersion.setVersion(result.getString(COL_EV_VERSION)); + extVersion.setExtension(extension); + + var resource = new FileResource(); + resource.setId(result.getLong(COL_FR_ID)); + resource.setName(result.getString(COL_FR_NAME)); + resource.setType(result.getString(COL_FR_TYPE)); + resource.setContent(result.getBytes(COL_FR_CONTENT)); + resource.setStorageType(result.getString(COL_FR_STORAGE_TYPE)); + resource.setExtension(extVersion); + + return resource; + } + + private List extractResources(List downloads) { + var storages = Map.of( + FileResource.STORAGE_GOOGLE, googleStorage, + FileResource.STORAGE_AZURE, azureStorage + ); + + var resources = new ArrayList(); + for(var download : downloads) { + byte[] content; + if(download.getStorageType().equals(FileResource.STORAGE_DB)) { + content = download.getContent(); + } else { + var storage = storages.get(download.getStorageType()); + var uri = storage.getLocation(download); + content = restTemplate.getForObject(uri, byte[].class); + } + + try(var processor = new ExtensionProcessor(new ByteArrayInputStream(content))) { + var processedResources = processor.getResources(download.getExtension()).stream() + .filter(resource -> resource.getType().equals(FileResource.RESOURCE)) + .map(resource -> { + resource.setStorageType(download.getStorageType()); + return resource; + }) + .collect(Collectors.toList()); + + resources.addAll(processedResources); + } + } + + return resources; + } + + private void uploadResources(List resources) { + var storages = Map.of( + FileResource.STORAGE_GOOGLE, googleStorage, + FileResource.STORAGE_AZURE, azureStorage + ); + + for(var resource : resources) { + if(!resource.getStorageType().equals(FileResource.STORAGE_DB)) { + var storage = storages.get(resource.getStorageType()); + storage.uploadFile(resource); + resource.setContent(null); + } + } + } + + private void insertResources(List resources, Connection connection) throws SQLException { + var query = "INSERT INTO file_resource(id, type, content, extension_id, name, storage_type) VALUES(nextval('hibernate_sequence'), ?, ?, ?, ?, ?)"; + try(var statement = connection.prepareStatement(query)) { + for(var resource : resources) { + statement.setString(1, resource.getType()); + statement.setBytes(2, resource.getContent()); + statement.setLong(3, resource.getExtension().getId()); + statement.setString(4, resource.getName()); + statement.setString(5, resource.getStorageType()); + statement.executeUpdate(); + } + } + } + + private void deleteResources(Connection connection) throws SQLException { + var query = "DELETE FROM file_resource WHERE type = 'resource'"; + try(var statement = connection.prepareStatement(query)) { + statement.executeUpdate(); + } + } + + private void deleteWebResources(Connection connection) throws SQLException { + var query = "DELETE FROM file_resource WHERE type = 'web-resource'"; + try(var statement = connection.prepareStatement(query)) { + statement.executeUpdate(); + } + } +} diff --git a/server/src/main/java/org/eclipse/openvsx/dto/FileResourceDTO.java b/server/src/main/java/org/eclipse/openvsx/dto/FileResourceDTO.java index 2b87f5286..c545be3b6 100644 --- a/server/src/main/java/org/eclipse/openvsx/dto/FileResourceDTO.java +++ b/server/src/main/java/org/eclipse/openvsx/dto/FileResourceDTO.java @@ -9,6 +9,8 @@ * ****************************************************************************** */ package org.eclipse.openvsx.dto; +import org.eclipse.openvsx.entities.FileResource; + import java.util.Objects; public class FileResourceDTO { @@ -19,6 +21,14 @@ public class FileResourceDTO { private final long id; private final String name; private final String type; + private String storageType; + private byte[] content; + + public FileResourceDTO(long id, long extensionVersionId, String name, String type, String storageType, byte[] content) { + this(id, extensionVersionId, name, type); + this.storageType = storageType; + this.content = content; + } public FileResourceDTO(long id, long extensionVersionId, String name, String type) { this.id = id; @@ -54,4 +64,16 @@ public String getName() { public String getType() { return type; } + + public String getStorageType() { + return storageType; + } + + public byte[] getContent() { + return content; + } + + public boolean isWebResource() { + return type.equals(FileResource.RESOURCE) && name.startsWith("extension/"); + } } diff --git a/server/src/main/java/org/eclipse/openvsx/entities/FileResource.java b/server/src/main/java/org/eclipse/openvsx/entities/FileResource.java index 533241ba6..8c7ab388c 100644 --- a/server/src/main/java/org/eclipse/openvsx/entities/FileResource.java +++ b/server/src/main/java/org/eclipse/openvsx/entities/FileResource.java @@ -27,7 +27,7 @@ public class FileResource { public static final String README = "readme"; public static final String LICENSE = "license"; public static final String CHANGELOG = "changelog"; - public static final String WEB_RESOURCE = "web-resource"; + public static final String RESOURCE = "resource"; // Storage types public static final String STORAGE_DB = "database"; @@ -100,5 +100,4 @@ public String getStorageType() { public void setStorageType(String storageType) { this.storageType = storageType; } - } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceDTORepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceDTORepository.java index 8a4dc8a5e..9e545b98a 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceDTORepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/FileResourceDTORepository.java @@ -21,7 +21,7 @@ import java.util.Collection; import java.util.List; -import static org.eclipse.openvsx.jooq.Tables.FILE_RESOURCE; +import static org.eclipse.openvsx.jooq.Tables.*; @Component public class FileResourceDTORepository { @@ -29,11 +29,25 @@ public class FileResourceDTORepository { @Autowired DSLContext dsl; - public List findAllByExtensionIdAndType(Collection extensionIds, Collection types) { + public List findAll(Collection extensionIds, Collection types) { return dsl.select(FILE_RESOURCE.ID, FILE_RESOURCE.EXTENSION_ID, FILE_RESOURCE.NAME, FILE_RESOURCE.TYPE) .from(FILE_RESOURCE) .where(FILE_RESOURCE.EXTENSION_ID.in(extensionIds)) .and(FILE_RESOURCE.TYPE.in(types)) .fetchInto(FileResourceDTO.class); } + + public List findAllResources(String namespaceName, String extensionName, String version, String prefix) { + return dsl.select(FILE_RESOURCE.ID, FILE_RESOURCE.EXTENSION_ID, FILE_RESOURCE.NAME, FILE_RESOURCE.TYPE, FILE_RESOURCE.STORAGE_TYPE, FILE_RESOURCE.CONTENT) + .from(FILE_RESOURCE) + .join(EXTENSION_VERSION).on(EXTENSION_VERSION.ID.eq(FILE_RESOURCE.EXTENSION_ID)) + .join(EXTENSION).on(EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID)) + .join(NAMESPACE).on(NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID)) + .where(NAMESPACE.NAME.eq(namespaceName)) + .and(EXTENSION.NAME.eq(extensionName)) + .and(EXTENSION_VERSION.VERSION.eq(version)) + .and(FILE_RESOURCE.TYPE.eq(FileResource.RESOURCE)) + .and(FILE_RESOURCE.NAME.startsWith(prefix)) + .fetchInto(FileResourceDTO.class); + } } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java index 0b953bcea..703d99887 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java @@ -286,7 +286,11 @@ public List findActiveExtensionVersionDTOsByExtensionName(S } public List findAllFileResourceDTOsByExtensionVersionIdAndType(Collection extensionVersionIds, Collection types) { - return fileResourceDTORepo.findAllByExtensionIdAndType(extensionVersionIds, types); + return fileResourceDTORepo.findAll(extensionVersionIds, types); + } + + public List findAllResourceFileResourceDTOs(String namespaceName, String extensionName, String version, String prefix) { + return fileResourceDTORepo.findAllResources(namespaceName, extensionName, version, prefix); } public Map findAllActiveReviewCountsByExtensionId(Collection extensionIds) { 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 84291a59c..78c169193 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java @@ -9,34 +9,26 @@ ********************************************************************************/ package org.eclipse.openvsx.storage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URI; - -import javax.transaction.Transactional; -import javax.transaction.Transactional.TxType; - import com.azure.storage.blob.BlobContainerClient; import com.azure.storage.blob.BlobContainerClientBuilder; import com.azure.storage.blob.models.BlobHttpHeaders; import com.azure.storage.blob.models.BlobStorageException; import com.google.common.base.Strings; - import org.apache.commons.lang3.ArrayUtils; import org.eclipse.openvsx.entities.FileResource; import org.eclipse.openvsx.util.TargetPlatform; import org.eclipse.openvsx.util.UrlUtil; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; + @Component public class AzureBlobStorageService implements IStorageService { - @Autowired - StorageUtilService storageUtil; - @Value("${ovsx.storage.azure.service-endpoint:}") String serviceEndpoint; @@ -65,7 +57,6 @@ protected BlobContainerClient getContainerClient() { } @Override - @Transactional(TxType.MANDATORY) public void uploadFile(FileResource resource) { var blobName = getBlobName(resource); if (Strings.isNullOrEmpty(serviceEndpoint)) { @@ -74,17 +65,16 @@ public void uploadFile(FileResource resource) { } uploadFile(resource.getContent(), resource.getName(), blobName); - resource.setStorageType(FileResource.STORAGE_AZURE); } protected void uploadFile(byte[] content, String fileName, String blobName) { var blobClient = getContainerClient().getBlobClient(blobName); var headers = new BlobHttpHeaders(); - headers.setContentType(storageUtil.getFileType(fileName).toString()); + headers.setContentType(StorageUtil.getFileType(fileName).toString()); if (fileName.endsWith(".vsix")) { headers.setContentDisposition("attachment; filename=\"" + fileName + "\""); } else { - var cacheControl = storageUtil.getCacheControl(fileName); + var cacheControl = StorageUtil.getCacheControl(fileName); headers.setCacheControl(cacheControl.getHeaderValue()); } try (var dataStream = new ByteArrayInputStream(content)) { 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 86a86137a..979ea8ceb 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java @@ -9,35 +9,25 @@ ********************************************************************************/ package org.eclipse.openvsx.storage; -import java.net.URI; - -import javax.transaction.Transactional; -import javax.transaction.Transactional.TxType; - import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; -import com.google.common.base.Preconditions; import com.google.common.base.Strings; - import org.apache.commons.lang3.ArrayUtils; -import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.FileResource; import org.eclipse.openvsx.util.TargetPlatform; import org.eclipse.openvsx.util.UrlUtil; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import java.net.URI; + @Component public class GoogleCloudStorageService implements IStorageService { private static final String BASE_URL = "https://storage.googleapis.com/"; - @Autowired - StorageUtilService storageUtil; - @Value("${ovsx.storage.gcp.project-id:}") String projectId; @@ -67,7 +57,6 @@ protected Storage getStorage() { } @Override - @Transactional(TxType.MANDATORY) public void uploadFile(FileResource resource) { var objectId = getObjectId(resource); if (Strings.isNullOrEmpty(bucketId)) { @@ -76,16 +65,15 @@ public void uploadFile(FileResource resource) { } uploadFile(resource.getContent(), resource.getName(), objectId); - resource.setStorageType(FileResource.STORAGE_GOOGLE); } protected void uploadFile(byte[] content, String fileName, String objectId) { var blobInfoBuilder = BlobInfo.newBuilder(BlobId.of(bucketId, objectId)) - .setContentType(storageUtil.getFileType(fileName).toString()); + .setContentType(StorageUtil.getFileType(fileName).toString()); if (fileName.endsWith(".vsix")) { blobInfoBuilder.setContentDisposition("attachment; filename=\"" + fileName + "\""); } else { - var cacheControl = storageUtil.getCacheControl(fileName); + var cacheControl = StorageUtil.getCacheControl(fileName); blobInfoBuilder.setCacheControl(cacheControl.getHeaderValue()); } getStorage().create(blobInfoBuilder.build(), content); diff --git a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java new file mode 100644 index 000000000..63e897fc4 --- /dev/null +++ b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java @@ -0,0 +1,38 @@ +/** ****************************************************************************** + * Copyright (c) 2022 Precies. Software and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * ****************************************************************************** */ + +package org.eclipse.openvsx.storage; + +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; + +import java.net.URLConnection; +import java.util.concurrent.TimeUnit; + +class StorageUtil { + + private StorageUtil(){} + + static MediaType getFileType(String fileName) { + if (fileName.endsWith(".vsix")) + return MediaType.APPLICATION_OCTET_STREAM; + if (fileName.endsWith(".json")) + return MediaType.APPLICATION_JSON; + var contentType = URLConnection.guessContentTypeFromName(fileName); + if (contentType != null) + return MediaType.parseMediaType(contentType); + return MediaType.TEXT_PLAIN; + } + + static CacheControl getCacheControl(String fileName) { + // Files are requested with a version string in the URL, so their content cannot change + return CacheControl.maxAge(30, TimeUnit.DAYS).cachePublic(); + } +} 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 fc2e57acf..9f2d2c8b4 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java @@ -104,8 +104,10 @@ public String getActiveStorageType() { } @Override + @Transactional(Transactional.TxType.MANDATORY) public void uploadFile(FileResource resource) { - switch (getActiveStorageType()) { + var storageType = getActiveStorageType(); + switch (storageType) { case STORAGE_GOOGLE: googleStorage.uploadFile(resource); break; @@ -115,6 +117,8 @@ public void uploadFile(FileResource resource) { default: throw new RuntimeException("External storage is not available."); } + + resource.setStorageType(storageType); } @Override @@ -159,19 +163,6 @@ public void addFileUrls(ExtensionVersion extVersion, String serverUrl, Map getWebResourceUrls(ExtensionVersion extVersion, String serverUrl) { - var name2Url = new HashMap(); - var fileBaseUrl = UrlUtil.createApiFileBaseUrl(serverUrl, extVersion); - var resources = repositories.findFilesByType(extVersion, Arrays.asList(WEB_RESOURCE)); - if (resources != null) { - for (var resource : resources) { - var fileUrl = UrlUtil.createApiFileUrl(fileBaseUrl, resource.getName()); - name2Url.put(resource.getName(), fileUrl); - } - } - return name2Url; - } - @Transactional public void increaseDownloadCount(ExtensionVersion extVersion, FileResource resource) { if(azureDownloadCountService.isEnabled()) { @@ -201,29 +192,12 @@ public void increaseDownloadCount(ExtensionVersion extVersion, FileResource reso public HttpHeaders getFileResponseHeaders(String fileName) { var headers = new HttpHeaders(); - headers.setContentType(getFileType(fileName)); + headers.setContentType(StorageUtil.getFileType(fileName)); if (fileName.endsWith(".vsix")) { headers.add("Content-Disposition", "attachment; filename=\"" + fileName + "\""); } else { - headers.setCacheControl(getCacheControl(fileName)); + headers.setCacheControl(StorageUtil.getCacheControl(fileName)); } return headers; } - - public MediaType getFileType(String fileName) { - if (fileName.endsWith(".vsix")) - return MediaType.APPLICATION_OCTET_STREAM; - if (fileName.endsWith(".json")) - return MediaType.APPLICATION_JSON; - var contentType = URLConnection.guessContentTypeFromName(fileName); - if (contentType != null) - return MediaType.parseMediaType(contentType); - return MediaType.TEXT_PLAIN; - } - - public CacheControl getCacheControl(String fileName) { - // Files are requested with a version string in the URL, so their content cannot change - return CacheControl.maxAge(30, TimeUnit.DAYS).cachePublic(); - } - } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java index f9ead69c8..b07a041a0 100644 --- a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java +++ b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAdapterTest.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @@ -48,6 +49,7 @@ import org.eclipse.openvsx.storage.GoogleCloudStorageService; import org.eclipse.openvsx.storage.StorageUtilService; import org.eclipse.openvsx.util.TargetPlatform; +import org.eclipse.openvsx.util.VersionUtil; import org.elasticsearch.search.aggregations.Aggregations; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -96,7 +98,7 @@ public class VSCodeAdapterTest { @Test public void testSearch() throws Exception { var extension = mockSearch(true); - mockExtensionVersionDTOs(extension, null, ""); + mockExtensionVersionDTOs(extension, null, "universal"); mockMvc.perform(post("/vscode/gallery/extensionquery") .content(file("search-yaml-query.json")) @@ -133,7 +135,7 @@ public void testSearchMultipleTargetsResponse() throws Exception { @Test public void testFindById() throws Exception { var extension = mockSearch(true); - mockExtensionVersionDTOs(extension, null, ""); + mockExtensionVersionDTOs(extension, null, "universal"); mockMvc.perform(post("/vscode/gallery/extensionquery") .content(file("findid-yaml-query.json")) @@ -158,7 +160,7 @@ public void testFindByIdAlpineTarget() throws Exception { @Test public void testFindByIdDuplicate() throws Exception { var extension = mockSearch(true); - mockExtensionVersionDTOs(extension, null, ""); + mockExtensionVersionDTOs(extension, null, "universal"); mockMvc.perform(post("/vscode/gallery/extensionquery") .content(file("findid-yaml-duplicate-query.json")) @@ -180,7 +182,7 @@ public void testFindByIdInactive() throws Exception { @Test public void testFindByName() throws Exception { var extension = mockSearch(true); - mockExtensionVersionDTOs(extension, null, ""); + mockExtensionVersionDTOs(extension, null, "universal"); mockMvc.perform(post("/vscode/gallery/extensionquery") .content(file("findname-yaml-query.json")) @@ -205,7 +207,7 @@ public void testFindByNameLinuxTarget() throws Exception { @Test public void testFindByNameDuplicate() throws Exception { var extension = mockSearch(true); - mockExtensionVersionDTOs(extension, null,""); + mockExtensionVersionDTOs(extension, null,"universal"); mockMvc.perform(post("/vscode/gallery/extensionquery") .content(file("findname-yaml-duplicate-query.json")) @@ -250,6 +252,121 @@ public void testGetItem() throws Exception { .andExpect(header().string("Location", "/extension/redhat/vscode-yaml")); } + @Test + public void testWebResourceAsset() throws Exception { + mockExtension(); + mockMvc.perform(get("/vscode/asset/{namespace}/{extensionName}/{version}/{assetType}", + "redhat", "vscode-yaml", "0.5.2", "Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png")) + .andExpect(status().isOk()) + .andExpect(content().string("logo.png")); + } + + @Test + public void testNotWebResourceAsset() throws Exception { + mockExtension(); + mockMvc.perform(get("/vscode/asset/{namespace}/{extensionName}/{version}/{assetType}", + "redhat", "vscode-yaml", "0.5.2", "Microsoft.VisualStudio.Code.WebResources/img/logo.png")) + .andExpect(status().isNotFound()); + } + + @Test + public void testBrowseNotFound() throws Exception { + Mockito.when(repositories.findAllResourceFileResourceDTOs("foo", "bar", "1.3.4", "extension/img")) + .thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", "foo", "bar", "1.3.4", "extension/img")) + .andExpect(status().isNotFound()); + } + + @Test + public void testBrowseTopDir() throws Exception { + var vsixResource = new FileResourceDTO(15, 1, "extension.vsixmanifest", RESOURCE, STORAGE_DB, "".getBytes(StandardCharsets.UTF_8)); + var manifestResource = new FileResourceDTO(16, 1, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8)); + var readmeResource = new FileResourceDTO(17, 1, "extension/README.md", RESOURCE, STORAGE_DB, "README".getBytes(StandardCharsets.UTF_8)); + var changelogResource = new FileResourceDTO(18, 1, "extension/CHANGELOG.md", RESOURCE, STORAGE_DB, "CHANGELOG".getBytes(StandardCharsets.UTF_8)); + var licenseResource = new FileResourceDTO(19, 1, "extension/LICENSE.txt", RESOURCE, STORAGE_DB, "LICENSE".getBytes(StandardCharsets.UTF_8)); + var iconResource = new FileResourceDTO(20, 1, "extension/images/icon128.png", RESOURCE, STORAGE_DB, "ICON128".getBytes(StandardCharsets.UTF_8)); + Mockito.when(repositories.findAllResourceFileResourceDTOs("foo", "bar", "1.3.4", "")) + .thenReturn(List.of(vsixResource, manifestResource, readmeResource, changelogResource, licenseResource, iconResource)); + + mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}", "foo", "bar", "1.3.4")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension.vsixmanifest\",\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/\"]")); + } + + @Test + public void testBrowseVsixManifest() throws Exception { + var content = "".getBytes(StandardCharsets.UTF_8); + var vsixResource = new FileResourceDTO(15, 1, "extension.vsixmanifest", RESOURCE, STORAGE_DB, content); + Mockito.when(repositories.findAllResourceFileResourceDTOs("foo", "bar", "1.3.4", "extension.vsixmanifest")) + .thenReturn(List.of(vsixResource)); + + mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", "foo", "bar", "1.3.4", "extension.vsixmanifest")) + .andExpect(status().isOk()) + .andExpect(content().bytes(content)); + } + + @Test + public void testBrowseExtensionDir() throws Exception { + var manifestResource = new FileResourceDTO(16, 1, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8)); + var readmeResource = new FileResourceDTO(17, 1, "extension/README.md", RESOURCE, STORAGE_DB, "README".getBytes(StandardCharsets.UTF_8)); + var changelogResource = new FileResourceDTO(18, 1, "extension/CHANGELOG.md", RESOURCE, STORAGE_DB, "CHANGELOG".getBytes(StandardCharsets.UTF_8)); + var licenseResource = new FileResourceDTO(19, 1, "extension/LICENSE.txt", RESOURCE, STORAGE_DB, "LICENSE".getBytes(StandardCharsets.UTF_8)); + var iconResource = new FileResourceDTO(20, 1, "extension/images/icon128.png", RESOURCE, STORAGE_DB, "ICON128".getBytes(StandardCharsets.UTF_8)); + + Mockito.when(repositories.findAllResourceFileResourceDTOs("foo", "bar", "1.3.4", "extension")) + .thenReturn(List.of(manifestResource, readmeResource, changelogResource, licenseResource, iconResource)); + + mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", "foo", "bar", "1.3.4", "extension/")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[" + + "\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/package.json\"," + + "\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/README.md\"," + + "\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/CHANGELOG.md\"," + + "\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/LICENSE.txt\"," + + "\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/images/\"" + + "]")); + } + + @Test + public void testBrowsePackageJson() throws Exception { + var content = "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8); + var manifestResource = new FileResourceDTO(16, 1, "extension/package.json", RESOURCE, STORAGE_DB, content); + Mockito.when(repositories.findAllResourceFileResourceDTOs("foo", "bar", "1.3.4", "extension/package.json")) + .thenReturn(List.of(manifestResource)); + + mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", "foo", "bar", "1.3.4", "extension/package.json")) + .andExpect(status().isOk()) + .andExpect(content().bytes(content)); + } + + @Test + public void testBrowseImagesDir() throws Exception { + var iconResource = new FileResourceDTO(20, 1, "extension/images/icon128.png", RESOURCE, STORAGE_DB, "ICON128".getBytes(StandardCharsets.UTF_8)); + Mockito.when(repositories.findAllResourceFileResourceDTOs("foo", "bar", "1.3.4", "extension/images")) + .thenReturn(List.of(iconResource)); + + mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", "foo", "bar", "1.3.4", "extension/images/")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/images/icon128.png\"]")); + } + + @Test + public void testBrowseIcon() throws Exception { + var content = "ICON128".getBytes(StandardCharsets.UTF_8); + var iconResource = new FileResourceDTO(20, 1, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content); + Mockito.when(repositories.findAllResourceFileResourceDTOs("foo", "bar", "1.3.4", "extension/images/icon128.png")) + .thenReturn(List.of(iconResource)); + + mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", "foo", "bar", "1.3.4", "extension/images/icon128.png")) + .andExpect(status().isOk()) + .andExpect(content().bytes(content)); + } + + @Test public void testGetItemBadRequest() throws Exception { mockMvc.perform(get("/vscode/item?itemName={itemName}", "vscode-yaml")) @@ -307,6 +424,7 @@ private ExtensionDTO mockSearch(String targetPlatform, boolean active) { Mockito.when(repositories.findActiveExtensionDTO(name, namespaceName)) .thenReturn(extension); + mockExtensionVersionDTOs(extension, targetPlatform, targetPlatform); return extension; } @@ -346,7 +464,7 @@ private ExtensionVersionDTO mockExtensionVersionDTO(ExtensionDTO extension, long private void mockFileResourceDTOs(List extensionVersions) { var ids = extensionVersions.stream().map(ExtensionVersionDTO::getId).collect(Collectors.toSet()); - var types = List.of(MANIFEST, README, LICENSE, ICON, DOWNLOAD, CHANGELOG, WEB_RESOURCE); + var types = List.of(MANIFEST, README, LICENSE, ICON, DOWNLOAD, CHANGELOG, RESOURCE); var files = new ArrayList(); for(var id : ids) { @@ -356,6 +474,8 @@ private void mockFileResourceDTOs(List extensionVersions) { files.add(new FileResourceDTO(id * 100 + 8, id, "CHANGELOG.md", CHANGELOG)); files.add(new FileResourceDTO(id * 100 + 9, id, "LICENSE.txt", LICENSE)); files.add(new FileResourceDTO(id * 100 + 10, id, "icon128.png", ICON)); + files.add(new FileResourceDTO(id * 100 + 11, id, "extension/themes/dark.json", RESOURCE)); + files.add(new FileResourceDTO(id * 100 + 12, id, "extension/img/logo.png", RESOURCE)); } Mockito.when(repositories.findAllFileResourceDTOsByExtensionVersionIdAndType(ids, types)) @@ -451,6 +571,14 @@ private ExtensionVersion mockExtension(String targetPlatform) throws JsonProcess iconFile.setStorageType(FileResource.STORAGE_DB); Mockito.when(repositories.findFileByType(extVersion, FileResource.ICON)) .thenReturn(iconFile); + var webResourceFile = new FileResource(); + webResourceFile.setExtension(extVersion); + webResourceFile.setName("extension/img/logo.png"); + webResourceFile.setType(FileResource.RESOURCE); + webResourceFile.setStorageType(STORAGE_DB); + webResourceFile.setContent("logo.png".getBytes()); + Mockito.when(repositories.findFileByTypeAndName(extVersion, FileResource.RESOURCE, "extension/img/logo.png")) + .thenReturn(webResourceFile); Mockito.when(repositories.findFilesByType(extVersion, Arrays.asList(FileResource.MANIFEST, FileResource.README, FileResource.LICENSE, FileResource.ICON, FileResource.DOWNLOAD, FileResource.CHANGELOG))) .thenReturn(Streamable.of(manifestFile, readmeFile, licenseFile, iconFile, extensionFile, changelogFile)); return extVersion; diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json index b9c72cebd..33e3a1d69 100644 --- a/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response-alpine.json @@ -6,7 +6,7 @@ "publisher": { "publisherId": "test-2", "publisherName": "redhat", - "displayName": null + "displayName": "redhat" }, "extensionId": "test-1", "extensionName": "vscode-yaml", @@ -42,6 +42,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/alpine-arm64/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/alpine-arm64/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/alpine-arm64/0.5.2/file/extension/img/logo.png" } ], "properties": [ diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response.json index 0395aa48a..3cea19ced 100644 --- a/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response.json +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findid-yaml-response.json @@ -6,7 +6,7 @@ "publisher": { "publisherId": "test-2", "publisherName": "redhat", - "displayName": null + "displayName": "redhat" }, "extensionId": "test-1", "extensionName": "vscode-yaml", @@ -41,6 +41,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/0.5.2/file/extension/img/logo.png" } ], "properties": [ diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json index acaf54756..8c23e8e31 100644 --- a/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response-linux.json @@ -6,7 +6,7 @@ "publisher": { "publisherId": "test-2", "publisherName": "redhat", - "displayName": null + "displayName": "redhat" }, "extensionId": "test-1", "extensionName": "vscode-yaml", @@ -42,6 +42,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/linux-x64/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/linux-x64/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/linux-x64/0.5.2/file/extension/img/logo.png" } ], "properties": [ diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response.json b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response.json index 0395aa48a..3cea19ced 100644 --- a/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response.json +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/findname-yaml-response.json @@ -6,7 +6,7 @@ "publisher": { "publisherId": "test-2", "publisherName": "redhat", - "displayName": null + "displayName": "redhat" }, "extensionId": "test-1", "extensionName": "vscode-yaml", @@ -41,6 +41,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/0.5.2/file/extension/img/logo.png" } ], "properties": [ diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json index a8cf37bfd..27f1b531d 100644 --- a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-darwin.json @@ -6,7 +6,7 @@ "publisher": { "publisherId": "test-2", "publisherName": "redhat", - "displayName": null + "displayName": "redhat" }, "extensionId": "test-1", "extensionName": "vscode-yaml", @@ -42,6 +42,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/darwin-x64/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/darwin-x64/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/darwin-x64/0.5.2/file/extension/img/logo.png" } ], "properties": [ diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-targets.json b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-targets.json index 09f00d17c..5b23aa39c 100644 --- a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-targets.json +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response-targets.json @@ -6,7 +6,7 @@ "publisher": { "publisherId": "test-2", "publisherName": "redhat", - "displayName": null + "displayName": "redhat" }, "extensionId": "test-1", "extensionName": "vscode-yaml", @@ -42,6 +42,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/darwin-x64/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/darwin-x64/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/darwin-x64/0.5.2/file/extension/img/logo.png" } ], "properties": [ @@ -97,6 +105,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/linux-x64/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/linux-x64/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/linux-x64/0.5.2/file/extension/img/logo.png" } ], "properties": [ @@ -152,6 +168,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/alpine-arm64/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/alpine-arm64/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/alpine-arm64/0.5.2/file/extension/img/logo.png" } ], "properties": [ diff --git a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response.json b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response.json index 0395aa48a..3cea19ced 100644 --- a/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response.json +++ b/server/src/test/resources/org/eclipse/openvsx/adapter/search-yaml-response.json @@ -6,7 +6,7 @@ "publisher": { "publisherId": "test-2", "publisherName": "redhat", - "displayName": null + "displayName": "redhat" }, "extensionId": "test-1", "extensionName": "vscode-yaml", @@ -41,6 +41,14 @@ { "assetType": "Microsoft.VisualStudio.Services.Content.Changelog", "source": "http://localhost/api/redhat/vscode-yaml/0.5.2/file/CHANGELOG.md" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/themes/dark.json", + "source":"http://localhost/api/redhat/vscode-yaml/0.5.2/file/extension/themes/dark.json" + }, + { + "assetType":"Microsoft.VisualStudio.Code.WebResources/extension/img/logo.png", + "source":"http://localhost/api/redhat/vscode-yaml/0.5.2/file/extension/img/logo.png" } ], "properties": [