Rewrite
This commit is contained in:
parent
e25ad84ac2
commit
91694960e4
18
pom.xml
18
pom.xml
@ -34,6 +34,19 @@
|
||||
<maven.compiler.>11</maven.compiler.>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>mchv-release</id>
|
||||
<name>MCHV Release Apache Maven Packages</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>mchv-snapshot</id>
|
||||
<name>MCHV Snapshot Apache Maven Packages</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@ -67,6 +80,11 @@
|
||||
<version>3.4.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.cavallium</groupId>
|
||||
<artifactId>dbengine</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.warp.filesponge.api.FileSource;
|
||||
import org.warp.filesponge.value.FileType;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import org.warp.filesponge.value.MirrorURI;
|
||||
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public abstract class BaseMirrorFileSource<FURI extends FileURI, FTYPE extends FileType> implements
|
||||
FileSource<FURI, FTYPE> {
|
||||
|
||||
protected final MirrorAvailabilityManager receiverAvailabilityManager;
|
||||
protected final MirrorURI mirrorURI;
|
||||
|
||||
}
|
35
src/main/lombok/org/warp/filesponge/DataBlock.java
Normal file
35
src/main/lombok/org/warp/filesponge/DataBlock.java
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class DataBlock {
|
||||
|
||||
int offset;
|
||||
int length;
|
||||
ByteBuffer data;
|
||||
|
||||
|
||||
public int getId() {
|
||||
return offset / Web.BLOCK_SIZE;
|
||||
}
|
||||
}
|
182
src/main/lombok/org/warp/filesponge/DiskCache.java
Normal file
182
src/main/lombok/org/warp/filesponge/DiskCache.java
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import static org.warp.filesponge.Web.BLOCK_SIZE;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import it.cavallium.dbengine.database.Column;
|
||||
import it.cavallium.dbengine.database.LLDatabaseConnection;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.LLDictionaryResultType;
|
||||
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
||||
import it.cavallium.dbengine.database.UpdateMode;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.warp.filesponge.DiskMetadata.DiskMetadataSerializer;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class DiskCache implements URLsDiskHandler, URLsWriter {
|
||||
|
||||
private static final DiskMetadataSerializer diskMetadataSerializer = new DiskMetadataSerializer();
|
||||
|
||||
private final LLKeyValueDatabase db;
|
||||
private final LLDictionary fileContent;
|
||||
private final LLDictionary fileMetadata;
|
||||
|
||||
public static Mono<DiskCache> open(LLDatabaseConnection databaseConnection, String dbName, boolean lowMemory) {
|
||||
return databaseConnection
|
||||
.getDatabase(dbName, List.of(Column.dictionary("file-content"), Column.dictionary("file-metadata")), lowMemory)
|
||||
.flatMap(db -> Mono.zip(
|
||||
Mono.just(db).single(),
|
||||
db.getDictionary("file-content", UpdateMode.ALLOW).single(),
|
||||
db.getDictionary("file-metadata", UpdateMode.ALLOW).single()
|
||||
))
|
||||
.map(tuple -> new DiskCache(tuple.getT1(), tuple.getT2(), tuple.getT3()))
|
||||
.single();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeMetadata(URL url, Metadata metadata) {
|
||||
return fileMetadata
|
||||
.update(url.getSerializer().serialize(url), oldValue -> {
|
||||
if (oldValue.isPresent()) {
|
||||
return oldValue;
|
||||
} else {
|
||||
return Optional
|
||||
.of(new DiskMetadata(
|
||||
metadata.getSize(),
|
||||
new BooleanArrayList(DiskMetadata.getBlocksCount(metadata.getSize(), BLOCK_SIZE))
|
||||
))
|
||||
.map(diskMetadataSerializer::serialize);
|
||||
}
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeContentBlock(URL url, DataBlock dataBlock) {
|
||||
return Mono
|
||||
.fromCallable(() -> {
|
||||
byte[] bytes = new byte[dataBlock.getLength()];
|
||||
dataBlock.getData().get(bytes);
|
||||
return bytes;
|
||||
}).subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(bytes -> fileContent.put(getBlockKey(url, dataBlock.getId()), bytes, LLDictionaryResultType.VOID))
|
||||
.then(fileMetadata
|
||||
.update(url.getSerializer().serialize(url), prevBytes -> prevBytes
|
||||
.map(diskMetadataSerializer::deserialize)
|
||||
.map(prevMeta -> {
|
||||
if (!prevMeta.getDownloadedBlocks().getBoolean(dataBlock.getId())) {
|
||||
BooleanArrayList bal = prevMeta.getDownloadedBlocks().clone();
|
||||
bal.add(dataBlock.getId(), true);
|
||||
return new DiskMetadata(prevMeta.getSize(), bal);
|
||||
} else {
|
||||
return prevMeta;
|
||||
}
|
||||
})
|
||||
.map(diskMetadataSerializer::serialize)
|
||||
)
|
||||
)
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBlock> requestContent(URL url) {
|
||||
return requestDiskMetadata(url)
|
||||
.filter(DiskMetadata::isDownloadedFully)
|
||||
.flatMapMany(meta -> Flux.fromIterable(meta.getDownloadedBlocks()))
|
||||
.index()
|
||||
// Get only downloaded blocks
|
||||
.filter(Tuple2::getT2)
|
||||
.flatMapSequential(blockMeta -> {
|
||||
int blockId = Math.toIntExact(blockMeta.getT1());
|
||||
boolean downloaded = blockMeta.getT2();
|
||||
if (!downloaded) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return fileContent.get(null, getBlockKey(url, blockId)).map(data -> {
|
||||
int blockOffset = getBlockOffset(blockId);
|
||||
int blockLength = data.length;
|
||||
if (blockOffset + blockLength >= blockMeta.size()) {
|
||||
if (blockOffset + blockLength > blockMeta.size()) {
|
||||
throw new IllegalStateException("Overflowed data size");
|
||||
}
|
||||
} else if (data.length != BLOCK_SIZE) {
|
||||
throw new IllegalStateException("Block data length != block length");
|
||||
}
|
||||
return new DataBlock(blockOffset, blockLength, ByteBuffer.wrap(data, 0, blockLength));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private byte[] getBlockKey(URL url, int blockId) {
|
||||
byte[] urlBytes = url.getSerializer().serialize(url);
|
||||
byte[] blockIdBytes = Ints.toByteArray(blockId);
|
||||
byte[] resultBytes = Arrays.copyOf(urlBytes, urlBytes.length);
|
||||
System.arraycopy(blockIdBytes, 0, resultBytes, urlBytes.length, blockIdBytes.length);
|
||||
return resultBytes;
|
||||
}
|
||||
|
||||
private static int getBlockOffset(int blockId) {
|
||||
return blockId * BLOCK_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DiskMetadata> requestDiskMetadata(URL url) {
|
||||
return fileMetadata
|
||||
.get(null, url.getSerializer().serialize(url))
|
||||
.map(diskMetadataSerializer::deserialize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Metadata> requestMetadata(URL url) {
|
||||
return requestDiskMetadata(url)
|
||||
.map(DiskMetadata::asMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Tuple2<Metadata, Flux<DataBlock>>> request(URL url) {
|
||||
return fileMetadata
|
||||
.get(null, url.getSerializer().serialize(url))
|
||||
.map(diskMetadataSerializer::deserialize)
|
||||
.map(diskMeta -> {
|
||||
var meta = diskMeta.asMetadata();
|
||||
if (diskMeta.isDownloadedFully()) {
|
||||
return Tuples.of(meta, this.requestContent(url));
|
||||
} else {
|
||||
return Tuples.of(meta, Flux.empty());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<Void> close() {
|
||||
return db.close();
|
||||
}
|
||||
}
|
103
src/main/lombok/org/warp/filesponge/DiskMetadata.java
Normal file
103
src/main/lombok/org/warp/filesponge/DiskMetadata.java
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import lombok.Data;
|
||||
import lombok.SneakyThrows;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Data
|
||||
public class DiskMetadata {
|
||||
|
||||
/**
|
||||
* -1 = unknown size
|
||||
*/
|
||||
private final int size;
|
||||
|
||||
private final BooleanArrayList downloadedBlocks;
|
||||
|
||||
private Boolean downloadedFully;
|
||||
|
||||
public boolean isDownloadedFully() {
|
||||
if (downloadedFully == null) {
|
||||
// Ensure blocks count is valid by calling getBlocksCount()
|
||||
getBlocksCount();
|
||||
// It's fully downloaded if every block is true
|
||||
downloadedFully = !this.getDownloadedBlocks().contains(false);
|
||||
}
|
||||
return downloadedFully;
|
||||
}
|
||||
|
||||
private int getBlocksCount() {
|
||||
var expectedBlocksCount = getBlocksCount(size, Web.BLOCK_SIZE);
|
||||
if (this.getDownloadedBlocks().size() != expectedBlocksCount) {
|
||||
throw new IllegalStateException("Blocks array length != expected blocks count");
|
||||
}
|
||||
return expectedBlocksCount;
|
||||
}
|
||||
|
||||
public static int getBlocksCount(int size, int blockSize) {
|
||||
return (size + (blockSize - size % blockSize)) / blockSize;
|
||||
}
|
||||
|
||||
public Metadata asMetadata() {
|
||||
return new Metadata(size);
|
||||
}
|
||||
|
||||
public static class DiskMetadataSerializer implements Serializer<DiskMetadata, byte[]> {
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public @NotNull DiskMetadata deserialize(byte @NotNull [] serialized) {
|
||||
var bais = new ByteArrayInputStream(serialized);
|
||||
var dis = new DataInputStream(bais);
|
||||
int size = dis.readInt();
|
||||
int blocksCount = getBlocksCount(size, Web.BLOCK_SIZE);
|
||||
boolean[] downloadedBlocks = new boolean[blocksCount];
|
||||
for (int i = 0; i < blocksCount; i++) {
|
||||
downloadedBlocks[i] = dis.readBoolean();
|
||||
}
|
||||
return new DiskMetadata(size, BooleanArrayList.wrap(downloadedBlocks, blocksCount));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public byte @NotNull [] serialize(@NotNull DiskMetadata deserialized) {
|
||||
try (var bos = new ByteArrayOutputStream(Integer.BYTES * 2)) {
|
||||
try (var dos = new DataOutputStream(bos)) {
|
||||
dos.writeInt(deserialized.getSize());
|
||||
if (deserialized.getDownloadedBlocks().size() != deserialized.getBlocksCount()) {
|
||||
throw new IllegalStateException("Blocks array length != expected blocks count");
|
||||
}
|
||||
for (boolean downloadedBlock : deserialized.getDownloadedBlocks()) {
|
||||
dos.writeBoolean(downloadedBlock);
|
||||
}
|
||||
}
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.warp.filesponge.reactor.AsyncMultiAssociation;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import org.warp.filesponge.value.MirrorURI;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
@AllArgsConstructor()
|
||||
public class FileMirrorsManager {
|
||||
|
||||
private final Scheduler fileMirrorsManagerScheduler = Schedulers.single();
|
||||
|
||||
private final MirrorAvailabilityManager mirrorAvailabilityManager;
|
||||
|
||||
/**
|
||||
* This map must be persistent
|
||||
*/
|
||||
private final AsyncMultiAssociation<FileURI, MirrorURI> fileMirrors;
|
||||
|
||||
public Mono<Set<MirrorURI>> getAvailableMirrors(FileURI fileURI) {
|
||||
return fileMirrors
|
||||
.getLinks(fileURI)
|
||||
.filterWhen(mirrorAvailabilityManager::isMirrorAvailable)
|
||||
.collect(Collectors.toUnmodifiableSet())
|
||||
.subscribeOn(fileMirrorsManagerScheduler);
|
||||
}
|
||||
|
||||
public Mono<Boolean> hasAnyAvailableMirror(FileURI uri) {
|
||||
return fileMirrors
|
||||
.getLinks(uri)
|
||||
.filterWhen(mirrorAvailabilityManager::isMirrorAvailable)
|
||||
.hasElements()
|
||||
.subscribeOn(fileMirrorsManagerScheduler);
|
||||
}
|
||||
|
||||
public Mono<Void> addMirror(FileURI uri, MirrorURI mirrorURI) {
|
||||
return fileMirrors
|
||||
.link(uri, mirrorURI)
|
||||
.then()
|
||||
.subscribeOn(fileMirrorsManagerScheduler);
|
||||
}
|
||||
|
||||
public Mono<Void> removeMirror(FileURI uri, MirrorURI mirrorURI) {
|
||||
return fileMirrors
|
||||
.unlink(uri, mirrorURI)
|
||||
.then()
|
||||
.subscribeOn(fileMirrorsManagerScheduler);
|
||||
}
|
||||
|
||||
public Mono<Void> unsetAllFiles() {
|
||||
return fileMirrors.clear()
|
||||
.subscribeOn(fileMirrorsManagerScheduler);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,12 +16,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
package org.warp.filesponge;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class Metadata {
|
||||
|
||||
/**
|
||||
* -1 = unknown size
|
||||
*/
|
||||
int size;
|
||||
|
||||
public enum FileAvailability {
|
||||
UNKNOWN,
|
||||
DOWNLOADABLE,
|
||||
DOWNLOADING,
|
||||
DOWNLOADED,
|
||||
FAILED
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.warp.filesponge.value.MirrorURI;
|
||||
import org.warp.filesponge.reactor.ConcurrentAsyncSet;
|
||||
import org.warp.filesponge.reactor.AsyncSet;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@AllArgsConstructor(access = AccessLevel.PUBLIC)
|
||||
public class MirrorAvailabilityManager {
|
||||
|
||||
private final AsyncSet<MirrorURI> availableMirrors = new ConcurrentAsyncSet<>();
|
||||
|
||||
public Mono<Void> setAllMirrorsAsUnavailable() {
|
||||
return availableMirrors.clear();
|
||||
}
|
||||
|
||||
public Mono<Void> setMirrorAvailability(MirrorURI mirrorURI, boolean available) {
|
||||
if (available) {
|
||||
return availableMirrors.add(mirrorURI).then();
|
||||
} else {
|
||||
return availableMirrors.remove(mirrorURI).then();
|
||||
}
|
||||
}
|
||||
|
||||
public Mono<Boolean> isMirrorAvailable(MirrorURI mirrorURI) {
|
||||
return this.availableMirrors.contains(mirrorURI);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.warp.filesponge.api.FileAccessor;
|
||||
import org.warp.filesponge.value.FileStatus;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Prevent access to other methods via casting
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class SecureFileAccessor<FURI extends FileURI> implements FileAccessor<FURI> {
|
||||
|
||||
private final FileAccessor<FURI> fileAccessor;
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(@NotNull FURI fileURI) {
|
||||
return fileAccessor.delete(fileURI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ByteBuffer> getContent(@NotNull FURI fileURI, boolean offlineOnly) {
|
||||
return fileAccessor.getContent(fileURI, offlineOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Mono<FileStatus> getStatus(@NotNull FURI fileURI) {
|
||||
return fileAccessor.getStatus(fileURI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fileAccessor.toString();
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,10 +16,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
package org.warp.filesponge;
|
||||
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
|
||||
public interface URL {
|
||||
|
||||
Serializer<URL, byte[]> getSerializer();
|
||||
|
||||
public enum FileDataAvailability {
|
||||
UNAVAILABLE,
|
||||
PARTIAL,
|
||||
FULL
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,11 +16,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
package org.warp.filesponge;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface URLDiskHandler extends URLHandler {
|
||||
|
||||
Mono<DiskMetadata> requestDiskMetadata();
|
||||
|
||||
public enum FileSourceAvailability {
|
||||
DOWNLOADABLE,
|
||||
DOWNLOADING,
|
||||
DOWNLOADED,
|
||||
FAILED
|
||||
}
|
@ -16,23 +16,21 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
package org.warp.filesponge;
|
||||
|
||||
public class AlreadyAssignedException extends Exception {
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
public AlreadyAssignedException() {
|
||||
super();
|
||||
public interface URLHandler {
|
||||
|
||||
Flux<DataBlock> requestContent();
|
||||
|
||||
Mono<Metadata> requestMetadata();
|
||||
|
||||
default Mono<Tuple2<Metadata, Flux<DataBlock>>> request() {
|
||||
return requestMetadata().map(metadata -> Tuples.of(metadata, requestContent()));
|
||||
}
|
||||
|
||||
public AlreadyAssignedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AlreadyAssignedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AlreadyAssignedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,6 +16,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
package org.warp.filesponge;
|
||||
|
||||
public interface FileType {}
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface URLWriter {
|
||||
|
||||
Mono<Void> writeMetadata(Metadata metadata);
|
||||
|
||||
Mono<Void> writeContentBlock(DataBlock dataBlock);
|
||||
|
||||
}
|
@ -16,40 +16,32 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.reactor;
|
||||
package org.warp.filesponge;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface AsyncMultiAssociation<T, U> {
|
||||
public interface URLsDiskHandler extends URLsHandler {
|
||||
|
||||
Mono<Boolean> link(T var1, U var2);
|
||||
Mono<DiskMetadata> requestDiskMetadata(URL url);
|
||||
|
||||
Mono<Boolean> unlink(T var1, U var2);
|
||||
|
||||
Flux<U> unlink(T var1);
|
||||
|
||||
Flux<T> unlinkFromSource(U var1);
|
||||
|
||||
default Mono<Boolean> hasAnyLink(T src) {
|
||||
return this.getLinks(src).hasElements();
|
||||
default URLDiskHandler asURLDiskHandler(URL url) {
|
||||
return new URLDiskHandler() {
|
||||
@Override
|
||||
public Mono<DiskMetadata> requestDiskMetadata() {
|
||||
return URLsDiskHandler.this.requestDiskMetadata(url);
|
||||
}
|
||||
|
||||
default Mono<Boolean> hasAnyLinkSource(U dest) {
|
||||
return this.getLinkSources(dest).hasElements();
|
||||
@Override
|
||||
public Flux<DataBlock> requestContent() {
|
||||
return URLsDiskHandler.this.requestContent(url);
|
||||
}
|
||||
|
||||
Mono<Boolean> hasLink(T var1, U var2);
|
||||
@Override
|
||||
public Mono<Metadata> requestMetadata() {
|
||||
return URLsDiskHandler.this.requestMetadata(url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Flux<U> getLinks(T var1);
|
||||
|
||||
Flux<T> getLinkSources(U var1);
|
||||
|
||||
Mono<Void> clear();
|
||||
|
||||
Mono<Integer> size();
|
||||
|
||||
Flux<T> getSources();
|
||||
|
||||
Flux<U> getDestinations();
|
||||
}
|
50
src/main/lombok/org/warp/filesponge/URLsHandler.java
Normal file
50
src/main/lombok/org/warp/filesponge/URLsHandler.java
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
public interface URLsHandler {
|
||||
|
||||
Flux<DataBlock> requestContent(URL url);
|
||||
|
||||
Mono<Metadata> requestMetadata(URL url);
|
||||
|
||||
default Mono<Tuple2<Metadata, Flux<DataBlock>>> request(URL url) {
|
||||
return requestMetadata(url).map(metadata -> Tuples.of(metadata, requestContent(url)));
|
||||
}
|
||||
|
||||
default URLHandler asURLHandler(URL url) {
|
||||
return new URLHandler() {
|
||||
@Override
|
||||
public Flux<DataBlock> requestContent() {
|
||||
return URLsHandler.this.requestContent(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Metadata> requestMetadata() {
|
||||
return URLsHandler.this.requestMetadata(url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,25 +16,28 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.api;
|
||||
package org.warp.filesponge;
|
||||
|
||||
import org.warp.filesponge.SecureFileAccessor;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* FileAccessor can be used to manage FileSponge and access files from the client side
|
||||
*/
|
||||
public interface FileSpongeClient<FURI extends FileURI> extends FileAccessor<FURI> {
|
||||
public interface URLsWriter {
|
||||
|
||||
Mono<Void> optimizeStorage();
|
||||
Mono<Void> writeMetadata(URL url, Metadata metadata);
|
||||
|
||||
/**
|
||||
* Get this instance but without special methods
|
||||
*
|
||||
* @return limited instance of itself
|
||||
*/
|
||||
default FileAccessor<FURI> asFileAccessor() {
|
||||
return new SecureFileAccessor<>(this);
|
||||
Mono<Void> writeContentBlock(URL url, DataBlock dataBlock);
|
||||
|
||||
default URLWriter getUrlWriter(URL url) {
|
||||
return new URLWriter() {
|
||||
@Override
|
||||
public Mono<Void> writeMetadata(Metadata metadata) {
|
||||
return URLsWriter.this.writeMetadata(url, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeContentBlock(DataBlock dataBlock) {
|
||||
return URLsWriter.this.writeContentBlock(url, dataBlock);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
87
src/main/lombok/org/warp/filesponge/Web.java
Normal file
87
src/main/lombok/org/warp/filesponge/Web.java
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class Web implements URLsHandler {
|
||||
|
||||
public static final int BLOCK_SIZE = 8 * 1024 * 1024; // 8 MiB
|
||||
public static final int MAX_BLOCKS = 256; // 2 GiB
|
||||
|
||||
private final Set<URLsHandler> urlsHandlers = new ConcurrentHashMap<URLsHandler, Object>().keySet(new Object());
|
||||
|
||||
private final Set<URLsDiskHandler> cacheAccess = new ConcurrentHashMap<URLsDiskHandler, Object>().keySet(new Object());
|
||||
private final Set<URLsWriter> cacheWrite = new ConcurrentHashMap<URLsWriter, Object>().keySet(new Object());
|
||||
|
||||
public Web() {
|
||||
|
||||
}
|
||||
|
||||
public Mono<Void> registerSource(URLsHandler urLsHandler) {
|
||||
return Mono.fromRunnable(() -> urlsHandlers.add(urLsHandler));
|
||||
}
|
||||
|
||||
public <T extends URLsDiskHandler & URLsWriter> Mono<Void> registerCache(T urlsCache) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
cacheAccess.add(urlsCache);
|
||||
cacheWrite.add(urlsCache);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBlock> requestContent(URL url) {
|
||||
return Flux
|
||||
.fromIterable(cacheAccess)
|
||||
.flatMap(urlsHandler -> urlsHandler.requestContent(url))
|
||||
.switchIfEmpty(Flux
|
||||
.fromIterable(urlsHandlers)
|
||||
.flatMap(urlsHandler -> urlsHandler
|
||||
.requestContent(url)
|
||||
.flatMapSequential(dataBlock -> Flux
|
||||
.fromIterable(cacheWrite)
|
||||
.flatMapSequential(cw -> cw.writeContentBlock(url, dataBlock))
|
||||
.then(Mono.just(dataBlock))
|
||||
)
|
||||
)
|
||||
)
|
||||
.distinct(DataBlock::getId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Metadata> requestMetadata(URL url) {
|
||||
return Mono.from(Flux
|
||||
.fromIterable(cacheAccess)
|
||||
.flatMap(urlsHandler -> urlsHandler.requestMetadata(url))
|
||||
.switchIfEmpty(Flux
|
||||
.fromIterable(urlsHandlers)
|
||||
.flatMap(urlsHandler -> urlsHandler
|
||||
.requestMetadata(url)
|
||||
.flatMap(dataBlock -> Flux
|
||||
.fromIterable(cacheWrite)
|
||||
.flatMapSequential(cw -> cw.writeMetadata(url, dataBlock))
|
||||
.then(Mono.just(dataBlock))
|
||||
)
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.warp.filesponge.value.FileStatus;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* FileAccessor can be used to access files from the client side
|
||||
*/
|
||||
public interface FileAccessor<FURI extends FileURI> {
|
||||
|
||||
/**
|
||||
* Request file deletion
|
||||
*
|
||||
* @param fileURI File URI
|
||||
* @return Empty.
|
||||
*/
|
||||
Mono<Void> delete(@NotNull FURI fileURI);
|
||||
|
||||
/**
|
||||
* Get file content
|
||||
*
|
||||
* @param fileURI File URI
|
||||
* @param offlineOnly true to get the file from cache
|
||||
* @return content if found. If the request is offline the future will complete instantly.
|
||||
* Can be empty
|
||||
*/
|
||||
Mono<ByteBuffer> getContent(@NotNull FURI fileURI, boolean offlineOnly);
|
||||
|
||||
/**
|
||||
* Get file status
|
||||
*
|
||||
* @param fileURI File URI
|
||||
* @return status of this file. Cannot be empty.
|
||||
*/
|
||||
Mono<FileStatus> getStatus(@NotNull FURI fileURI);
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.api;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* FileActor sends signals to a mirror
|
||||
*/
|
||||
public interface FileActor<FURI extends FileURI> {
|
||||
|
||||
/**
|
||||
* Send a "delete file" signal
|
||||
*
|
||||
* @param fileURI File URI
|
||||
* @return true if the signal can be sent. Cannot be empty.
|
||||
*/
|
||||
Mono<Boolean> deleteFile(FURI fileURI);
|
||||
|
||||
/**
|
||||
* Send a "download file" signal
|
||||
*
|
||||
* @param fileURI File URI
|
||||
* @return true if the signal can be sent. Cannot be empty.
|
||||
*/
|
||||
Mono<Boolean> downloadFile(FURI fileURI);
|
||||
|
||||
/**
|
||||
* Check if this actor can handle signals for this file
|
||||
*
|
||||
* @param fileURI File URI
|
||||
* @return true if the actor can send signals related to this file. Cannot be empty.
|
||||
*/
|
||||
Mono<Boolean> canHandleFile(FURI fileURI);
|
||||
|
||||
/**
|
||||
* Send a "download file" signal
|
||||
*
|
||||
* @param timeout if it's 0 the method will return immediately, if it's set the method will wait until a file
|
||||
* <b>download request</b> has been found, or the timeout time elapsed
|
||||
* @return empty if no pending <b>download requests</b> has been found, true if the signal can be sent, false
|
||||
* otherwise
|
||||
*/
|
||||
Mono<Boolean> downloadNextFile(Duration timeout);
|
||||
|
||||
/**
|
||||
* Send a "delete file" signal
|
||||
*
|
||||
* @param timeout if it's 0 the method will return immediately, if it's set the method will wait until a file
|
||||
* <b>delete request</b> has been found, or the timeout time elapsed
|
||||
* @return empty if no pending <b>delete requests</b> has been found, true if the signal can be sent, false otherwise
|
||||
*/
|
||||
Mono<Boolean> deleteNextFile(Duration timeout);
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.warp.filesponge.value.FileSourceAvailability;
|
||||
import org.warp.filesponge.value.FileType;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* FileSource receives responses from a mirror
|
||||
*/
|
||||
public interface FileSource<FURI extends FileURI, FTYPE extends FileType> {
|
||||
|
||||
/**
|
||||
* Called when the mirror is online
|
||||
*/
|
||||
Mono<Void> onAvailable();
|
||||
|
||||
/**
|
||||
* Called when the mirror is unreachable
|
||||
*/
|
||||
Mono<Void> onUnavailable();
|
||||
|
||||
/**
|
||||
* Called when the mirror notifies you that a new file exists. Cannot be empty.
|
||||
*/
|
||||
Mono<Boolean> onNewFile(@NotNull FURI fileURI, @NotNull FTYPE fileType);
|
||||
|
||||
/**
|
||||
* Called when the mirror notifies you details about a file.
|
||||
* <p>
|
||||
* {@link FileSource#onNewFile(FURI, FTYPE)} must have been already called
|
||||
*/
|
||||
Mono<Void> onFile(@NotNull FURI fileURI, @NotNull FileSourceAvailability fileAvailability, long totalSize);
|
||||
|
||||
/**
|
||||
* Called when the mirror notifies you the bytes of a part of a file.
|
||||
* <p>
|
||||
* {@link FileSource#onNewFile(FURI, FTYPE)} and {@link FileSource#onFile(FURI, FileSourceAvailability, long)} must
|
||||
* have been already called
|
||||
*/
|
||||
Mono<Void> onFilePiece(@NotNull FURI fileURI, long offset, long size, @NotNull ByteBuffer piece);
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.api;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.warp.filesponge.value.FileType;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
import org.warp.filesponge.value.MirrorURI;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface FileStorage<FURI extends FileURI, FTYPE extends FileType, MURI extends MirrorURI> {
|
||||
|
||||
Mono<Void> newFile(@NotNull FURI fileURI, @NotNull FTYPE fileType);
|
||||
|
||||
/**
|
||||
* Read file data.
|
||||
* Fails if not all the file data is available.
|
||||
* @param fileURI File URI
|
||||
* @return read-only file data
|
||||
* @throws java.util.NoSuchElementException if file is not existing, or some requested data is missing
|
||||
*/
|
||||
Mono<ByteBuffer> readFileData(@NotNull FURI fileURI);
|
||||
|
||||
/**
|
||||
* Read a part of file data.
|
||||
* Fails if not all the requested file data is available.
|
||||
* @param fileURI File URI
|
||||
* @param offset offset of the current data segment
|
||||
* @param size current data segment size
|
||||
* @return read-only file data part
|
||||
* @throws java.util.NoSuchElementException if file is not existing, or some requested data is missing
|
||||
* @throws org.warp.commonutils.error.IndexOutOfBoundsException if requested offset or size is not valid
|
||||
*/
|
||||
Mono<ByteBuffer> readFileDataPart(@NotNull FURI fileURI, long offset, long size);
|
||||
|
||||
/**
|
||||
* Set a part of file data.
|
||||
* If file size is 0, the file will be deleted.
|
||||
* @param fileURI File URI
|
||||
* @param offset offset of the current data segment
|
||||
* @param size current data segment size
|
||||
* @param bytes data segment, can be null if totalSize is 0
|
||||
* @param totalSize total file size
|
||||
* @return nothing
|
||||
*/
|
||||
Mono<Void> setFileData(@NotNull FURI fileURI, long offset, long size, @Nullable ByteBuffer bytes, long totalSize);
|
||||
|
||||
Mono<Boolean> hasAllData(@NotNull FURI fileURI);
|
||||
|
||||
/**
|
||||
* Delete a file
|
||||
* @param fileURI File URI
|
||||
* @return nothing
|
||||
*/
|
||||
default Mono<Void> deleteFile(@NotNull FURI fileURI) {
|
||||
return setFileData(fileURI, 0, 0, null, 0);
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.extra.api;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.warp.filesponge.value.AlreadyAssignedException;
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
|
||||
/**
|
||||
* Translate File URIs to "fileId" and back
|
||||
*/
|
||||
public interface FileURITranslator<FURI extends FileURI, FID> {
|
||||
|
||||
Optional<FURI> getURI(FID fileId);
|
||||
|
||||
Optional<FID> getFileId(FURI fileURI);
|
||||
|
||||
/**
|
||||
* @throws AlreadyAssignedException Throw if the uri has another fileId assigned
|
||||
*/
|
||||
void setFileId(FURI fileURI, FID fileId) throws AlreadyAssignedException;
|
||||
|
||||
Optional<FURI> delete(FID fileId);
|
||||
|
||||
Optional<FID> delete(FURI fileURI);
|
||||
|
||||
void clear();
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.extra.api;
|
||||
|
||||
import org.warp.filesponge.value.FileURI;
|
||||
|
||||
/**
|
||||
* Create an unique "fileId" for each File URI
|
||||
*/
|
||||
public interface URIObfuscator<FURI extends FileURI, FID> {
|
||||
|
||||
FURI deobfuscateFileId(FID fileId);
|
||||
|
||||
void getFileId(FURI fileURI);
|
||||
|
||||
void setURIValue(FURI fileURI, FID fileId);
|
||||
|
||||
FURI delete(FID fileId);
|
||||
|
||||
FID delete(FURI fileURI);
|
||||
|
||||
void clear();
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extra functionalities and utilities for FileSponge
|
||||
*/
|
||||
package org.warp.filesponge.extra;
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.reactor;
|
||||
|
||||
import java.util.Set;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Asynchronous set
|
||||
* @param <T> value type
|
||||
*/
|
||||
public interface AsyncSet<T> {
|
||||
|
||||
/**
|
||||
* Clear the set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
Mono<Void> clear();
|
||||
|
||||
/**
|
||||
* Add element to the set
|
||||
*
|
||||
* @param value value to add
|
||||
* @return true if added, false if it's already present. Can't be empty.
|
||||
*/
|
||||
Mono<Boolean> add(T value);
|
||||
|
||||
/**
|
||||
* Remove element from the set
|
||||
*
|
||||
* @param value value to remove
|
||||
* @return true if removed, false if it's not present. Can't be empty.
|
||||
*/
|
||||
Mono<Boolean> remove(T value);
|
||||
|
||||
/**
|
||||
* Find if element is present in the set
|
||||
*
|
||||
* @param value value to find
|
||||
* @return true if found, false if not. Can't be empty.
|
||||
*/
|
||||
Mono<Boolean> contains(T value);
|
||||
|
||||
/**
|
||||
* Get the set size
|
||||
*
|
||||
* @return set size, from 0 to {@value Integer#MAX_VALUE}. Can't be empty.
|
||||
*/
|
||||
Mono<Integer> size();
|
||||
|
||||
/**
|
||||
* Get all values
|
||||
* @return values, in a flux. Can be empty.
|
||||
*/
|
||||
Flux<T> toFlux();
|
||||
|
||||
/**
|
||||
* Get all values
|
||||
* @return values, in a set. Can't be empty.
|
||||
*/
|
||||
Mono<Set<T>> toSet();
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.reactor;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap.KeySetView;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.warp.commonutils.concurrency.atomicity.Atomic;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Atomic
|
||||
@EqualsAndHashCode
|
||||
public class ConcurrentAsyncSet<T> implements AsyncSet<T> {
|
||||
|
||||
private final KeySetView<T, Boolean> set;
|
||||
|
||||
public ConcurrentAsyncSet() {
|
||||
this.set = ConcurrentHashMap.newKeySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> clear() {
|
||||
return Mono.fromCallable(() -> {
|
||||
set.clear();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> add(T value) {
|
||||
return Mono.fromCallable(() -> set.add(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> remove(T value) {
|
||||
return Mono.fromCallable(() -> set.remove(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> contains(T value) {
|
||||
return Mono.fromCallable(() -> set.contains(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Integer> size() {
|
||||
return Mono.fromCallable(set::size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> toFlux() {
|
||||
return Flux.fromStream(set::stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Set<T>> toSet() {
|
||||
return Mono.fromCallable(() -> Set.copyOf(set));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return set.toString();
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.reactor;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.warp.commonutils.concurrency.atomicity.NotAtomic;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@NotAtomic
|
||||
@EqualsAndHashCode
|
||||
public class HashAsyncSet<T> implements AsyncSet<T> {
|
||||
|
||||
private final HashSet<T> set;
|
||||
|
||||
public HashAsyncSet() {
|
||||
this.set = new HashSet<>();
|
||||
}
|
||||
|
||||
public HashAsyncSet(HashSet<T> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> clear() {
|
||||
return Mono.fromCallable(() -> {
|
||||
set.clear();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> add(T value) {
|
||||
return Mono.fromCallable(() -> set.add(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> remove(T value) {
|
||||
return Mono.fromCallable(() -> set.remove(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> contains(T value) {
|
||||
return Mono.fromCallable(() -> set.contains(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Integer> size() {
|
||||
return Mono.fromCallable(set::size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> toFlux() {
|
||||
return Flux.fromStream(set::stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Set<T>> toSet() {
|
||||
return Mono.fromCallable(() -> Set.copyOf(set));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return set.toString();
|
||||
}
|
||||
|
||||
public SynchronizedHashAsyncSet<T> synchronize() {
|
||||
return new SynchronizedHashAsyncSet<>(this);
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2021 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.reactor;
|
||||
|
||||
import java.util.Set;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.warp.commonutils.concurrency.atomicity.NotAtomic;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@NotAtomic
|
||||
public class SynchronizedHashAsyncSet<T> implements AsyncSet<T> {
|
||||
|
||||
private transient final Scheduler scheduler = Schedulers.single();
|
||||
private final HashAsyncSet<T> set;
|
||||
|
||||
public SynchronizedHashAsyncSet() {
|
||||
this.set = new HashAsyncSet<>();
|
||||
}
|
||||
|
||||
public SynchronizedHashAsyncSet(HashAsyncSet<T> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> clear() {
|
||||
return set.clear().subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> add(T value) {
|
||||
return set.add(value).subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> remove(T value) {
|
||||
return set.remove(value).subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> contains(T value) {
|
||||
return set.contains(value).subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Integer> size() {
|
||||
return set.size().subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> toFlux() {
|
||||
return set.toFlux().subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Set<T>> toSet() {
|
||||
return set.toSet().subscribeOn(scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return set.toString();
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
|
||||
import java.util.Optional;
|
||||
import lombok.Value;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Value
|
||||
public class FileStatus {
|
||||
@NotNull FileAvailability availability;
|
||||
@NotNull FileDataAvailability dataAvailability;
|
||||
@Nullable Integer totalSize;
|
||||
@Nullable Integer downloadedSize;
|
||||
|
||||
public Optional<Integer> getTotalSize() {
|
||||
return Optional.ofNullable(totalSize);
|
||||
}
|
||||
|
||||
public Optional<Integer> getDownloadedSize() {
|
||||
return Optional.ofNullable(downloadedSize);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
|
||||
public interface FileURI {}
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* FileSponge
|
||||
* Copyright (C) 2020 Andrea Cavalli
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.warp.filesponge.value;
|
||||
|
||||
public interface MirrorURI {}
|
Loading…
Reference in New Issue
Block a user