refactory
This commit is contained in:
16
build.gradle
16
build.gradle
@@ -7,9 +7,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
// https://plugins.gradle.org/plugin/org.springframework.boot
|
// https://plugins.gradle.org/plugin/org.springframework.boot
|
||||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:${pluginSpringBootVerson}"
|
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVerson}"
|
||||||
// https://plugins.gradle.org/plugin/io.spring.dependency-management
|
|
||||||
classpath "io.spring.gradle:dependency-management-plugin:${pluginSpringBomVerson}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +27,15 @@ subprojects {
|
|||||||
dependencies {
|
dependencies {
|
||||||
annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
|
annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
|
||||||
compileOnly("org.projectlombok:lombok:${lombokVersion}")
|
compileOnly("org.projectlombok:lombok:${lombokVersion}")
|
||||||
|
|
||||||
|
testAnnotationProcessor("org.projectlombok:lombok:${lombokVersion}")
|
||||||
|
testCompileOnly("org.projectlombok:lombok:${lombokVersion}")
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
|
||||||
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
|
||||||
|
testImplementation("org.mockito:mockito-core:${mockitoCoreVersion}")
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.implementation.resolutionStrategy {
|
test {
|
||||||
failOnVersionConflict()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
apply plugin: 'java-library'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api("com.squareup.okhttp3:okhttp:${okhttpVersion}")
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package ru.di9.mirror.app.domain;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public record FileRecord(String name, long size, InputStream inputStream) {
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package ru.di9.mirror.app.domain;
|
|
||||||
|
|
||||||
public record PathRecord(String name, PathType type) {
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package ru.di9.mirror.app.domain;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record WalkerResult(PathType pathType, FileRecord fileRecord, List<PathRecord> filesList) {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package ru.di9.mirror.app.service;
|
|
||||||
|
|
||||||
import ru.di9.mirror.app.Utils;
|
|
||||||
import ru.di9.mirror.app.domain.FileRecord;
|
|
||||||
import ru.di9.mirror.app.domain.PathRecord;
|
|
||||||
import ru.di9.mirror.app.domain.PathType;
|
|
||||||
import ru.di9.mirror.app.domain.WalkerResult;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class FileStorageService implements StorageService {
|
|
||||||
|
|
||||||
private final Path storagePath;
|
|
||||||
|
|
||||||
public FileStorageService(String storagePath) {
|
|
||||||
this.storagePath = Paths.get(storagePath).resolve("local");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("java:S112")
|
|
||||||
@Override
|
|
||||||
public Optional<FileRecord> getFile(String path) {
|
|
||||||
Path fullPath = storageResolve(path);
|
|
||||||
|
|
||||||
if (Files.notExists(fullPath)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return Optional.of(new FileRecord(
|
|
||||||
fullPath.toFile().getName(),
|
|
||||||
Files.size(fullPath),
|
|
||||||
Files.newInputStream(fullPath)
|
|
||||||
));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("java:S112")
|
|
||||||
@Override
|
|
||||||
public void putFile(String path, InputStream inputStream) {
|
|
||||||
Path fullPath = storageResolve(path);
|
|
||||||
try {
|
|
||||||
Files.createDirectories(fullPath.getParent());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (OutputStream outputStream = Files.newOutputStream(fullPath)) {
|
|
||||||
byte[] buff = new byte[8 * 1024];
|
|
||||||
int len;
|
|
||||||
while ((len = inputStream.read(buff)) > 0) {
|
|
||||||
outputStream.write(buff, 0, len);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("java:S112")
|
|
||||||
@Override
|
|
||||||
public Optional<WalkerResult> walker(String path) {
|
|
||||||
Path fullPath = storageResolve(path);
|
|
||||||
|
|
||||||
if (Files.notExists(fullPath)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Files.isDirectory(fullPath)) {
|
|
||||||
final List<PathRecord> list = new ArrayList<>();
|
|
||||||
|
|
||||||
try (Stream<Path> walk = Files.walk(fullPath, 1)) {
|
|
||||||
walk.skip(1).forEach(path1 -> list.add(new PathRecord(
|
|
||||||
path1.getFileName().toString(),
|
|
||||||
Files.isDirectory(path1) ? PathType.DIRECTORY : PathType.FILE)));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.of(new WalkerResult(PathType.DIRECTORY, null, list));
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
var fileRecord = new FileRecord(
|
|
||||||
fullPath.getFileName().toString(),
|
|
||||||
Files.size(fullPath),
|
|
||||||
Files.newInputStream(fullPath)
|
|
||||||
);
|
|
||||||
|
|
||||||
return Optional.of(new WalkerResult(PathType.FILE, fileRecord, null));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path storageResolve(String httpPath) {
|
|
||||||
if (Utils.isEmptyString(httpPath)) {
|
|
||||||
return storagePath;
|
|
||||||
} else {
|
|
||||||
return storagePath.resolve(httpPath.substring(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package ru.di9.mirror.app.service;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import ru.di9.mirror.app.domain.FileRecord;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class MavenCentralStorageService extends MirrorService {
|
|
||||||
|
|
||||||
private static final String MAVEN_CENTRAL_URL = "https://repo1.maven.org/maven2";
|
|
||||||
private final OkHttpClient okHttpClient;
|
|
||||||
|
|
||||||
public MavenCentralStorageService(OkHttpClient okHttpClient) {
|
|
||||||
this.okHttpClient = okHttpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<FileRecord> getFile(String path) {
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(MAVEN_CENTRAL_URL + path)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
if (response.code() == 404) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IOException("Unexpected code " + response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.of(new FileRecord(
|
|
||||||
path,
|
|
||||||
Long.parseLong(Objects.requireNonNull(response.header("Content-Length"))),
|
|
||||||
Objects.requireNonNull(response.body()).byteStream()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package ru.di9.mirror.app.service;
|
|
||||||
|
|
||||||
import ru.di9.mirror.app.domain.WalkerResult;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public abstract class MirrorService implements StorageService{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putFile(String path, InputStream inputStream) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<WalkerResult> walker(String path) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package ru.di9.mirror.app.service;
|
|
||||||
|
|
||||||
import ru.di9.mirror.app.domain.FileRecord;
|
|
||||||
import ru.di9.mirror.app.domain.WalkerResult;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@SuppressWarnings("ClassCanBeRecord")
|
|
||||||
public class MirroredStorageService implements StorageService {
|
|
||||||
|
|
||||||
private final FileStorageService fileStorageService;
|
|
||||||
private final List<MirrorService> mirrors;
|
|
||||||
|
|
||||||
public MirroredStorageService(FileStorageService fileStorageService,
|
|
||||||
List<MirrorService> mirrors) {
|
|
||||||
this.fileStorageService = fileStorageService;
|
|
||||||
this.mirrors = mirrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<FileRecord> getFile(String path) {
|
|
||||||
Optional<FileRecord> optional;
|
|
||||||
|
|
||||||
optional = fileStorageService.getFile(path);
|
|
||||||
if (optional.isPresent()) {
|
|
||||||
return optional;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (StorageService mirror : mirrors) {
|
|
||||||
optional = mirror.getFile(path);
|
|
||||||
if (optional.isPresent()) {
|
|
||||||
FileRecord fileRecord = optional.get();
|
|
||||||
fileStorageService.putFile(fileRecord.name(), fileRecord.inputStream());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileStorageService.getFile(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putFile(String path, InputStream inputStream) {
|
|
||||||
fileStorageService.putFile(path, inputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<WalkerResult> walker(String path) {
|
|
||||||
return fileStorageService.walker(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package ru.di9.mirror.app.service;
|
|
||||||
|
|
||||||
import ru.di9.mirror.app.domain.FileRecord;
|
|
||||||
import ru.di9.mirror.app.domain.WalkerResult;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface StorageService {
|
|
||||||
|
|
||||||
Optional<FileRecord> getFile(String path);
|
|
||||||
|
|
||||||
void putFile(String path, InputStream inputStream);
|
|
||||||
|
|
||||||
Optional<WalkerResult> walker(String path);
|
|
||||||
}
|
|
||||||
33
mirror-core/build.gradle
Normal file
33
mirror-core/build.gradle
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
integrationTestImplementation {
|
||||||
|
extendsFrom(testImplementation)
|
||||||
|
}
|
||||||
|
integrationTestRuntimeOnly {
|
||||||
|
extendsFrom(testRuntimeOnly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
integrationTest {
|
||||||
|
java {
|
||||||
|
compileClasspath += main.output + test.output
|
||||||
|
runtimeClasspath += main.output + test.output
|
||||||
|
srcDir file('src/integrationTest/java')
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDir file('src/integrationTest/resources')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idea.module {
|
||||||
|
testSourceDirs += sourceSets.integrationTest.java.srcDirs
|
||||||
|
testResourceDirs += sourceSets.integrationTest.resources.srcDirs
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api("io.minio:minio:${minioVersion}")
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package ru.di9.mirror.core.service;
|
||||||
|
|
||||||
|
import io.minio.MinioClient;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.di9.mirror.core.domain.ItemRecord2;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class MinioServiceIntTest {
|
||||||
|
static final String url = "http://dev.di9.ru:9000";
|
||||||
|
static final String accessKey = "mirror";
|
||||||
|
static final String secretKey = "mirror123";
|
||||||
|
static final String bucket = "mirror";
|
||||||
|
static MinioClient minioClient;
|
||||||
|
MinioService minioService;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
minioClient = MinioClient.builder()
|
||||||
|
.endpoint(url)
|
||||||
|
.credentials(accessKey, secretKey)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
minioService = new MinioService(minioClient, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void list() {
|
||||||
|
List<ItemRecord2> list = minioService.list("/local/ghast/ghast-tools/");
|
||||||
|
|
||||||
|
assertNotNull(list);
|
||||||
|
assertFalse(list.isEmpty());
|
||||||
|
|
||||||
|
assertTrue(list.stream()
|
||||||
|
.anyMatch(itemRecord2 -> itemRecord2.name().equals("maven-metadata.xml")
|
||||||
|
&& !itemRecord2.isDir()
|
||||||
|
&& itemRecord2.size() == 320));
|
||||||
|
assertTrue(list.stream()
|
||||||
|
.anyMatch(itemRecord2 -> itemRecord2.name().equals("1.13")
|
||||||
|
&& itemRecord2.isDir()
|
||||||
|
&& itemRecord2.size() == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void notExists() {
|
||||||
|
Optional<InputStream> optional = minioService.get("/not/exists/path/to/object");
|
||||||
|
|
||||||
|
assertNotNull(optional);
|
||||||
|
assertTrue(optional.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exists() {
|
||||||
|
Optional<InputStream> optional = minioService.get("/local/ghast/ghast-tools/maven-metadata.xml");
|
||||||
|
|
||||||
|
assertNotNull(optional);
|
||||||
|
assertTrue(optional.isPresent());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ru.di9.mirror.app;
|
package ru.di9.mirror.core;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -9,4 +9,8 @@ public final class Utils {
|
|||||||
public static boolean isEmptyString(String string) {
|
public static boolean isEmptyString(String string) {
|
||||||
return string == null || string.isEmpty() || string.isBlank();
|
return string == null || string.isEmpty() || string.isBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isNotEmptyString(String string) {
|
||||||
|
return !isEmptyString(string);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package ru.di9.mirror.core.domain;
|
||||||
|
|
||||||
|
import io.minio.Result;
|
||||||
|
import io.minio.messages.Item;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
public record ItemRecord2(Result<Item> itemResult, String prefix) {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public boolean isDir() {
|
||||||
|
return itemResult.get().isDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public String name() {
|
||||||
|
String objectName = itemResult.get().objectName();
|
||||||
|
if (isDir()) {
|
||||||
|
return objectName.substring(prefix.length() - 1, objectName.length() - 1);
|
||||||
|
} else {
|
||||||
|
return objectName.substring(prefix.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public long size() {
|
||||||
|
return itemResult.get().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.di9.mirror.core.domain;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public record MavenHandlerGetFileResponse(String name, InputStream inputStream) {
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ru.di9.mirror.app.domain;
|
package ru.di9.mirror.core.domain;
|
||||||
|
|
||||||
public enum PathType {
|
public enum PathType {
|
||||||
FILE, DIRECTORY
|
FILE, DIRECTORY
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package ru.di9.mirror.core.domain;
|
||||||
|
|
||||||
|
public record WalkerResult(PathType type) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.di9.mirror.core.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import ru.di9.mirror.core.domain.ItemRecord2;
|
||||||
|
import ru.di9.mirror.core.service.MinioService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IndexOfHandler {
|
||||||
|
|
||||||
|
private final MinioService minioService;
|
||||||
|
|
||||||
|
public List<ItemRecord2> walker(String path) {
|
||||||
|
return minioService.list("/local/" + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package ru.di9.mirror.core.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import ru.di9.mirror.core.domain.MavenHandlerGetFileResponse;
|
||||||
|
import ru.di9.mirror.core.service.ExternalMavenService;
|
||||||
|
import ru.di9.mirror.core.service.MinioService;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MavenHandler {
|
||||||
|
|
||||||
|
private final MinioService minioService;
|
||||||
|
private final List<ExternalMavenService> externalMavenServices;
|
||||||
|
|
||||||
|
public Optional<MavenHandlerGetFileResponse> getFile(String path) {
|
||||||
|
final String fileName = path.substring(path.lastIndexOf("/") + 1);
|
||||||
|
|
||||||
|
Optional<InputStream> optionalInputStream = minioService.get("/local/" + path);
|
||||||
|
if (optionalInputStream.isPresent()) {
|
||||||
|
return optionalInputStream
|
||||||
|
.map(inputStream -> new MavenHandlerGetFileResponse(fileName, inputStream));
|
||||||
|
} else {
|
||||||
|
for (ExternalMavenService externalMavenService : externalMavenServices) {
|
||||||
|
final String nameForStore = "/" + externalMavenService.getId() + "/" + path;
|
||||||
|
|
||||||
|
optionalInputStream = minioService.get(nameForStore);
|
||||||
|
if (optionalInputStream.isPresent()) {
|
||||||
|
return optionalInputStream
|
||||||
|
.map(inputStream -> new MavenHandlerGetFileResponse(fileName, inputStream));
|
||||||
|
} else {
|
||||||
|
optionalInputStream = externalMavenService.getFile(path);
|
||||||
|
if (optionalInputStream.isPresent()) {
|
||||||
|
minioService.put(nameForStore, optionalInputStream.get());
|
||||||
|
return minioService.get(nameForStore)
|
||||||
|
.map(inputStream -> new MavenHandlerGetFileResponse(fileName, inputStream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putFile(String path, InputStream inputStream) {
|
||||||
|
minioService.put("/local/" + path, inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package ru.di9.mirror.core.service;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.ToString;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@SuppressWarnings("ClassCanBeRecord")
|
||||||
|
@ToString(of = {"id", "url"})
|
||||||
|
public class ExternalMavenService {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String id;
|
||||||
|
private final String url;
|
||||||
|
private final OkHttpClient okHttpClient;
|
||||||
|
|
||||||
|
public ExternalMavenService(String id, String url, OkHttpClient okHttpClient) {
|
||||||
|
this.id = id;
|
||||||
|
this.okHttpClient = okHttpClient;
|
||||||
|
|
||||||
|
if (!url.endsWith("/")) {
|
||||||
|
this.url = url + "/";
|
||||||
|
} else {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public Optional<InputStream> getFile(String path) {
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url + path)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
if (response.code() == 404) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.ofNullable(response.body()).map(ResponseBody::byteStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package ru.di9.mirror.core.service;
|
||||||
|
|
||||||
|
import io.minio.*;
|
||||||
|
import io.minio.errors.*;
|
||||||
|
import io.minio.messages.Item;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import ru.di9.mirror.core.domain.ItemRecord2;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MinioService {
|
||||||
|
private static final long UNKNOWN_SIZE = -1L;
|
||||||
|
private static final long PART_SIZE = (5 * 1024 * 1024); // 5 MB
|
||||||
|
|
||||||
|
private final MinioClient minioClient;
|
||||||
|
private final String bucket;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public void put(String name, InputStream inputStream) {
|
||||||
|
minioClient.putObject(PutObjectArgs.builder()
|
||||||
|
.bucket(bucket)
|
||||||
|
.object(name)
|
||||||
|
.stream(inputStream, UNKNOWN_SIZE, PART_SIZE)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public Optional<InputStream> get(String name) {
|
||||||
|
try {
|
||||||
|
return Optional.of(minioClient.getObject(GetObjectArgs.builder()
|
||||||
|
.bucket(bucket)
|
||||||
|
.object(name)
|
||||||
|
.build()));
|
||||||
|
} catch (ErrorResponseException e) {
|
||||||
|
if (e.errorResponse().code().equalsIgnoreCase("NoSuchKey")) {
|
||||||
|
return Optional.empty();
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ItemRecord2> list(String prefix) {
|
||||||
|
if (!prefix.endsWith("/")) {
|
||||||
|
prefix = prefix + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
|
||||||
|
.bucket(bucket)
|
||||||
|
.prefix(prefix)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<ItemRecord2> list = new ArrayList<>();
|
||||||
|
for (Result<Item> result : results) {
|
||||||
|
list.add(new ItemRecord2(result, prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package ru.di9.mirror.core.service;
|
||||||
|
|
||||||
|
import io.minio.ListObjectsArgs;
|
||||||
|
import io.minio.MinioClient;
|
||||||
|
import io.minio.Result;
|
||||||
|
import io.minio.messages.Item;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.di9.mirror.core.domain.ItemRecord2;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
class MinioServiceTest {
|
||||||
|
static final String bucket = "mirror";
|
||||||
|
static MinioClient mockMinioClient;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
mockMinioClient = mock(MinioClient.class);
|
||||||
|
|
||||||
|
Iterable<Result<Item>> listObjects = List.of(
|
||||||
|
mockResult("ghast/ghast-tools/maven-metadata.xml", 320, false),
|
||||||
|
mockResult("ghast/ghast-tools/1.13/", 0, true)
|
||||||
|
);
|
||||||
|
when(mockMinioClient.listObjects(any(ListObjectsArgs.class))).thenReturn(listObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void list() {
|
||||||
|
MinioService minioService = new MinioService(mockMinioClient, bucket);
|
||||||
|
List<ItemRecord2> list = minioService.list("/ghast/ghast-tools/");
|
||||||
|
|
||||||
|
assertNotNull(list);
|
||||||
|
assertFalse(list.isEmpty());
|
||||||
|
|
||||||
|
assertTrue(list.stream()
|
||||||
|
.anyMatch(itemRecord2 -> itemRecord2.name().equals("maven-metadata.xml")
|
||||||
|
&& !itemRecord2.isDir()
|
||||||
|
&& itemRecord2.size() == 320));
|
||||||
|
assertTrue(list.stream()
|
||||||
|
.anyMatch(itemRecord2 -> itemRecord2.name().equals("1.13")
|
||||||
|
&& itemRecord2.isDir()
|
||||||
|
&& itemRecord2.size() == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@SneakyThrows
|
||||||
|
private static Result<Item> mockResult(String name, long size, boolean isDir) {
|
||||||
|
Item item = mock(Item.class);
|
||||||
|
when(item.objectName()).thenReturn(name);
|
||||||
|
when(item.size()).thenReturn(size);
|
||||||
|
when(item.isDir()).thenReturn(isDir);
|
||||||
|
|
||||||
|
Result<Item> result = mock(Result.class);
|
||||||
|
when(result.get()).thenReturn(item);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
|
//file:noinspection GrUnresolvedAccess
|
||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(':mirror-app'))
|
implementation(project(':mirror-core'))
|
||||||
|
|
||||||
annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
|
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:${springBootVerson}")
|
||||||
implementation('org.springframework.boot:spring-boot-starter-web')
|
implementation("org.springframework.boot:spring-boot-starter-web:${springBootVerson}")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-freemarker:${springBootVerson}")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('compileJava') {
|
tasks.named('compileJava') {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package ru.di9.mirror.web.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "maven-mirrors")
|
||||||
|
public class MavenMirrorsProperties {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final List<MirrorData> list = new ArrayList<>();
|
||||||
|
|
||||||
|
public record MirrorData(String id, String url) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package ru.di9.mirror.web.config;
|
package ru.di9.mirror.web.config;
|
||||||
|
|
||||||
|
import io.minio.MinioClient;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import ru.di9.mirror.app.service.*;
|
import ru.di9.mirror.core.handler.IndexOfHandler;
|
||||||
|
import ru.di9.mirror.core.handler.MavenHandler;
|
||||||
|
import ru.di9.mirror.core.service.ExternalMavenService;
|
||||||
|
import ru.di9.mirror.core.service.MinioService;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -12,20 +16,42 @@ import java.util.List;
|
|||||||
public class WebConfig {
|
public class WebConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public StorageService mirroredStorageService(FileStorageService fileStorageService,
|
public MavenHandler mavenHandler(MinioService minioService,
|
||||||
List<MirrorService> mirrors) {
|
List<ExternalMavenService> externalMavenServices) {
|
||||||
return new MirroredStorageService(fileStorageService, mirrors);
|
return new MavenHandler(minioService, externalMavenServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public FileStorageService fileStorageService(@Value("${maven.storage}") String storagePath) {
|
public IndexOfHandler indexOfHandler(MinioService minioService) {
|
||||||
return new FileStorageService(storagePath);
|
return new IndexOfHandler(minioService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public List<MirrorService> mirrors() {
|
public MinioService minioService(MinioClient minioClient, @Value("${minio.bucket}") String bucket) {
|
||||||
return List.of(
|
return new MinioService(minioClient, bucket);
|
||||||
new MavenCentralStorageService(new OkHttpClient())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MinioClient minioClient(@Value("${minio.url}") String url,
|
||||||
|
@Value("${minio.accessKey}") String accessKey,
|
||||||
|
@Value("${minio.secretKey}") String secretKey) {
|
||||||
|
return MinioClient.builder()
|
||||||
|
.endpoint(url)
|
||||||
|
.credentials(accessKey, secretKey)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public List<ExternalMavenService> externalMavenServices(OkHttpClient okHttpClient,
|
||||||
|
MavenMirrorsProperties mavenMirrorsProperties) {
|
||||||
|
return mavenMirrorsProperties.getList().stream()
|
||||||
|
.map(mirrorData -> new ExternalMavenService(mirrorData.id(), mirrorData.url(), okHttpClient))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OkHttpClient okHttpClient() {
|
||||||
|
return new OkHttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,79 @@
|
|||||||
package ru.di9.mirror.web.controller;
|
package ru.di9.mirror.web.controller;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.core.io.InputStreamResource;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.ModelMap;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import ru.di9.mirror.app.Utils;
|
import ru.di9.mirror.core.Utils;
|
||||||
import ru.di9.mirror.app.domain.FileRecord;
|
import ru.di9.mirror.core.domain.ItemRecord2;
|
||||||
import ru.di9.mirror.app.domain.PathRecord;
|
import ru.di9.mirror.core.handler.IndexOfHandler;
|
||||||
import ru.di9.mirror.app.domain.PathType;
|
|
||||||
import ru.di9.mirror.app.domain.WalkerResult;
|
|
||||||
import ru.di9.mirror.app.service.StorageService;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping(path = "/maven")
|
@RequestMapping(path = "/maven")
|
||||||
public class IndexOfMavenController {
|
public class IndexOfMavenController {
|
||||||
private final StorageService storageService;
|
|
||||||
|
|
||||||
public IndexOfMavenController(@Qualifier("mirroredStorageService") StorageService storageService) {
|
private final IndexOfHandler indexOfHandler;
|
||||||
this.storageService = storageService;
|
|
||||||
|
public IndexOfMavenController(IndexOfHandler indexOfHandler) {
|
||||||
|
this.indexOfHandler = indexOfHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/{*path}", headers = "accept=text/html")
|
@GetMapping(path = "/{*path}", headers = "accept=text/html")
|
||||||
public ResponseEntity<Object> walker(@PathVariable("path") String httpPath) {
|
public String walker(@PathVariable("path") String httpPath,
|
||||||
ResponseEntity<Object> responseEntity;
|
@ModelAttribute("model") ModelMap model) {
|
||||||
|
String path = correctingHttpPath(httpPath);
|
||||||
|
|
||||||
Optional<WalkerResult> walkerResultOptional = storageService.walker(httpPath);
|
List<ItemRecord2> walker = indexOfHandler.walker(path);
|
||||||
if (walkerResultOptional.isEmpty()) {
|
List<ModelLink> links = new ArrayList<>();
|
||||||
responseEntity = ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
||||||
.body("<html><body><h1>404 - Not Found</h1><hr><p style=\"text-align:right\">Project-Mirror</p></body></html>");
|
StringBuilder sb = new StringBuilder("/maven");
|
||||||
} else {
|
int resetLength = sb.length();
|
||||||
WalkerResult walkerResult = walkerResultOptional.get();
|
|
||||||
if (walkerResult.pathType() == PathType.DIRECTORY) {
|
if (Utils.isNotEmptyString(path)) {
|
||||||
responseEntity = generateIndexOf(httpPath, walkerResult.filesList());
|
int idx = path.lastIndexOf("/");
|
||||||
} else {
|
if (idx > -1) {
|
||||||
responseEntity = downloadFile(walkerResult.fileRecord());
|
sb.append("/").append(path, 0, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
links.add(new ModelLink("..", sb.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return responseEntity;
|
sb.setLength(resetLength);
|
||||||
}
|
sb.append("/");
|
||||||
|
resetLength = sb.length();
|
||||||
|
for (ItemRecord2 itemRecord2 : walker) {
|
||||||
|
if (Utils.isNotEmptyString(path)) {
|
||||||
|
sb.append(path).append("/");
|
||||||
|
}
|
||||||
|
sb.append(itemRecord2.name());
|
||||||
|
|
||||||
private ResponseEntity<Object> downloadFile(FileRecord fileRecord) {
|
links.add(new ModelLink(itemRecord2.name() + (itemRecord2.isDir() ? "/" : ""), sb.toString()));
|
||||||
return ResponseEntity.ok()
|
sb.setLength(resetLength);
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
|
||||||
"attachment; filename=\"" + fileRecord.name() + "\"")
|
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
|
||||||
.contentLength(fileRecord.size())
|
|
||||||
.body(new InputStreamResource(fileRecord.inputStream()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResponseEntity<Object> generateIndexOf(String httpPath, List<PathRecord> filesList) {
|
|
||||||
ResponseEntity<Object> responseEntity;
|
|
||||||
StringBuilder sb = new StringBuilder("<html><body><h1>Index of</h1><hr><ul>");
|
|
||||||
|
|
||||||
if (!Utils.isEmptyString(httpPath)) {
|
|
||||||
String levelUpPath = httpPath.substring(0, httpPath.lastIndexOf("/"));
|
|
||||||
sb.append("<li><a href=\"/maven").append(levelUpPath).append("\">..</a></li>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filesList.forEach(pathRecord -> {
|
model.put("links", links);
|
||||||
sb.append("<li><a href=\"/maven")
|
return "list";
|
||||||
.append(httpPath)
|
}
|
||||||
.append("/")
|
|
||||||
.append(pathRecord.name())
|
|
||||||
.append("\">")
|
|
||||||
.append(pathRecord.name());
|
|
||||||
if (pathRecord.type() == PathType.DIRECTORY) {
|
|
||||||
sb.append("/");
|
|
||||||
}
|
|
||||||
sb.append("</a></li>");
|
|
||||||
});
|
|
||||||
|
|
||||||
responseEntity = ResponseEntity.ok(sb.append("</ul><hr><p style=\"text-align:right\">Project-Mirror</p></body></html>").toString());
|
private String correctingHttpPath(String httpPath) {
|
||||||
return responseEntity;
|
String path = httpPath;
|
||||||
|
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.endsWith("/")) {
|
||||||
|
path = path.substring(0, path.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ModelLink(String name, String link) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package ru.di9.mirror.web.controller;
|
package ru.di9.mirror.web.controller;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@@ -12,43 +11,31 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import ru.di9.mirror.app.domain.FileRecord;
|
import ru.di9.mirror.core.handler.MavenHandler;
|
||||||
import ru.di9.mirror.app.service.StorageService;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping(path = "/maven")
|
@RequestMapping(path = "/maven")
|
||||||
public class MavenController {
|
public class MavenController {
|
||||||
private final StorageService storageService;
|
private final MavenHandler mavenHandler;
|
||||||
|
|
||||||
public MavenController(@Qualifier("mirroredStorageService") StorageService storageService) {
|
public MavenController(MavenHandler mavenHandler) {
|
||||||
this.storageService = storageService;
|
this.mavenHandler = mavenHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/{*path}")
|
@GetMapping(path = "/{*path}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||||
public ResponseEntity<Resource> mavenGetFile(@PathVariable("path") String httpPath) {
|
public ResponseEntity<Resource> mavenGetFile(@PathVariable("path") String httpPath) {
|
||||||
ResponseEntity<Resource> responseEntity;
|
return mavenHandler.getFile(httpPath.substring(1))
|
||||||
|
.<ResponseEntity<Resource>>map(response -> ResponseEntity.ok()
|
||||||
Optional<FileRecord> fileRecordOptional = storageService.getFile(httpPath);
|
|
||||||
if (fileRecordOptional.isEmpty()) {
|
|
||||||
responseEntity = ResponseEntity.notFound().build();
|
|
||||||
} else {
|
|
||||||
FileRecord fileRecord = fileRecordOptional.get();
|
|
||||||
|
|
||||||
responseEntity = ResponseEntity.ok()
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
"attachment; filename=\"" + fileRecord.name() + "\"")
|
"attachment; filename=\"" + response.name() + "\"")
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
.contentLength(fileRecord.size())
|
.body(new InputStreamResource(response.inputStream())))
|
||||||
.body(new InputStreamResource(fileRecord.inputStream()));
|
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||||
}
|
|
||||||
|
|
||||||
return responseEntity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("java:S1452")
|
@SuppressWarnings("java:S1452")
|
||||||
@@ -57,7 +44,7 @@ public class MavenController {
|
|||||||
HttpServletRequest httpRequest) throws IOException {
|
HttpServletRequest httpRequest) throws IOException {
|
||||||
|
|
||||||
try (InputStream inputStream = httpRequest.getInputStream()) {
|
try (InputStream inputStream = httpRequest.getInputStream()) {
|
||||||
storageService.putFile(httpPath, inputStream);
|
mavenHandler.putFile(httpPath.substring(1), inputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
{
|
{
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "maven.storage",
|
"name": "minio.url",
|
||||||
"type": "java.lang.String"
|
"type": "java.lang.String",
|
||||||
|
"description": "MinIO console url."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "minio.accessKey",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "MinIO Access key (login)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "minio.secretKey",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "MinIO Secret key (password)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "minio.bucket",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "MinIO Bucket name."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maven-mirrors.list",
|
||||||
|
"type": "java.util.List<ru.di9.mirror.web.config.MavenMirrorsProperties.MirrorData>",
|
||||||
|
"sourceType": "java.util.List<ru.di9.mirror.web.config.MavenMirrorsProperties.MirrorData>"
|
||||||
}
|
}
|
||||||
]
|
] }
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,22 @@ server:
|
|||||||
address: 127.0.0.1
|
address: 127.0.0.1
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|
||||||
|
spring:
|
||||||
|
freemarker:
|
||||||
|
template-loader-path: classpath:/templates
|
||||||
|
suffix: .ftl
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
|
|
||||||
maven:
|
minio:
|
||||||
storage: './storage'
|
url: 'http://dev.di9.ru:9000'
|
||||||
|
accessKey: 'mirror'
|
||||||
|
secretKey: 'mirror123'
|
||||||
|
bucket: 'mirror'
|
||||||
|
|
||||||
|
maven-mirrors:
|
||||||
|
list:
|
||||||
|
- id: 'maven_central'
|
||||||
|
url: 'https://repo1.maven.org/maven2'
|
||||||
|
- id: 'spigot'
|
||||||
|
url: 'https://hub.spigotmc.org/nexus/content/groups/public'
|
||||||
|
|||||||
19
mirror-web/src/main/resources/templates/list.ftl
Normal file
19
mirror-web/src/main/resources/templates/list.ftl
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<header>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Project-Mirror</title>
|
||||||
|
</header>
|
||||||
|
<body>
|
||||||
|
<h1>Index of</h1>
|
||||||
|
<hr>
|
||||||
|
<ul>
|
||||||
|
<#--noinspection FtlTypesInspection-->
|
||||||
|
<#list model["links"] as item>
|
||||||
|
<li><a href="${item.link()}">${item.name()}</a></li>
|
||||||
|
</#list>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<p style="text-align:right">Project-Mirror</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
rootProject.name = 'project-mirror'
|
rootProject.name = 'project-mirror'
|
||||||
|
|
||||||
include('mirror-app')
|
include('mirror-core')
|
||||||
include('mirror-web')
|
include('mirror-web')
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
ext {
|
ext {
|
||||||
lombokVersion = '1.18.20'
|
lombokVersion = '1.18.20'
|
||||||
okhttpVersion = '4.9.3'
|
minioVersion = '8.3.9'
|
||||||
|
springBootVerson = '2.6.6'
|
||||||
|
|
||||||
pluginSpringBootVerson = '2.6.6'
|
// for tests only
|
||||||
pluginSpringBomVerson = '1.0.11.RELEASE'
|
junitJupiterVersion = '5.5.2'
|
||||||
|
mockitoCoreVersion = '4.5.1'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user