Skip to content

Commit

Permalink
Mail capability - Initial work
Browse files Browse the repository at this point in the history
  • Loading branch information
NickImpact committed Oct 16, 2023
1 parent e15a4d5 commit 8ad7acc
Show file tree
Hide file tree
Showing 17 changed files with 513 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ version = rootProject.version

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
languageVersion.set(JavaLanguageVersion.of(17))
}
}

Expand Down
1 change: 1 addition & 0 deletions impactor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
api(project(":api:core"))
api(project(":api:config"))
api(project(":api:economy"))
api(project(":api:mail"))
api(project(":api:players"))
api(project(":api:plugins"))
api(project(":api:storage"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import net.impactdev.impactor.api.utility.ExceptionPrinter;
import net.impactdev.impactor.core.economy.currency.ImpactorCurrencyProvider;
import net.impactdev.impactor.core.economy.storage.EconomyStorage;
import net.impactdev.impactor.core.economy.storage.StorageFactory;
import net.impactdev.impactor.core.economy.storage.EconomyStorageFactory;
import net.impactdev.impactor.core.plugin.BaseImpactorPlugin;

import java.util.List;
Expand All @@ -61,7 +61,7 @@ public ImpactorEconomyService() {
}

this.provider = new ImpactorCurrencyProvider(currencies);
this.storage = StorageFactory.instance(BaseImpactorPlugin.instance(), this.config.get(EconomyConfig.STORAGE_TYPE), StorageType.JSON);
this.storage = EconomyStorageFactory.instance(BaseImpactorPlugin.instance(), this.config.get(EconomyConfig.STORAGE_TYPE), StorageType.JSON);

try {
this.storage.init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@
import net.impactdev.impactor.api.storage.connection.configurate.loaders.HoconLoader;
import net.impactdev.impactor.api.storage.connection.configurate.loaders.JsonLoader;
import net.impactdev.impactor.api.storage.connection.configurate.loaders.YamlLoader;
import net.impactdev.impactor.core.economy.storage.implementations.ConfigurateProvider;
import net.impactdev.impactor.core.economy.storage.implementations.EconomyConfigurateProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;

public final class StorageFactory {
public final class EconomyStorageFactory {

public static EconomyStorage instance(ImpactorPlugin plugin, @Nullable StorageType type, @NotNull StorageType fallback) {
StorageType use = Optional.ofNullable(type).orElse(fallback);
Expand All @@ -47,11 +47,11 @@ public static EconomyStorage instance(ImpactorPlugin plugin, @Nullable StorageTy
private static EconomyStorageImplementation createNewImplementation(StorageType type) {
switch (type) {
case JSON:
return new ConfigurateProvider(new JsonLoader());
return new EconomyConfigurateProvider(new JsonLoader());
case YAML:
return new ConfigurateProvider(new YamlLoader());
return new EconomyConfigurateProvider(new YamlLoader());
case HOCON:
return new ConfigurateProvider(new HoconLoader());
return new EconomyConfigurateProvider(new HoconLoader());
}

throw new IllegalArgumentException("Unsupported storage type: " + type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;

public class ConfigurateProvider implements EconomyStorageImplementation {
public class EconomyConfigurateProvider implements EconomyStorageImplementation {

private final ConfigurateLoader loader;
private final Path root;
Expand All @@ -82,7 +82,7 @@ private Path transform(Path parent) {

private final LoadingCache<Path, ReentrantLock> ioLocks;

public ConfigurateProvider(@NotNull final ConfigurateLoader loader) {
public EconomyConfigurateProvider(@NotNull final ConfigurateLoader loader) {
this.loader = loader;
this.root = Paths.get("config").resolve("impactor").resolve("economy");
this.ioLocks = Caffeine.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package net.impactdev.impactor.core.mail;

import net.impactdev.impactor.api.mail.MailMessage;
import net.kyori.adventure.text.Component;

import java.time.Instant;
import java.util.UUID;

public class ImpactorMailMessage implements MailMessage {

private final UUID uuid;
private final Component message;
private final Instant timestamp;
private final UUID sender;

public ImpactorMailMessage(UUID sender, Component message) {
this.uuid = UUID.randomUUID();
this.message = message;
this.sender = sender;
this.timestamp = Instant.now();
}

public ImpactorMailMessage(UUID uuid, UUID sender, Component message, Instant timestamp) {
this.uuid = uuid;
this.message = message;
this.sender = sender;
this.timestamp = timestamp;
}

@Override
public Component content() {
return this.message;
}

@Override
public Instant timestamp() {
return this.timestamp;
}

@Override
public UUID uuid() {
return this.uuid;
}

@Override
public UUID sender() {
return this.sender;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package net.impactdev.impactor.core.mail;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.impactdev.impactor.api.configuration.Config;
import net.impactdev.impactor.api.mail.MailMessage;
import net.impactdev.impactor.api.mail.MailService;
import net.impactdev.impactor.api.mail.Mailbox;
import net.impactdev.impactor.api.storage.StorageType;
import net.impactdev.impactor.core.economy.EconomyConfig;
import net.impactdev.impactor.core.mail.storage.MailStorage;
import net.impactdev.impactor.core.mail.storage.MailStorageFactory;
import net.impactdev.impactor.core.plugin.BaseImpactorPlugin;
import net.kyori.adventure.text.Component;

import java.util.UUID;
import java.util.concurrent.CompletableFuture;

public final class ImpactorMailService implements MailService {

private final Config config;
private final MailStorage storage;

ImpactorMailService() {
this.config = Config.builder()
.path(BaseImpactorPlugin.instance().configurationDirectory().resolve("mail.conf"))
.provider(EconomyConfig.class)
.provideIfMissing(() -> BaseImpactorPlugin.instance().resource(root -> root.resolve("configs").resolve("mail.conf")))
.build();

this.storage = MailStorageFactory.instance(
BaseImpactorPlugin.instance(),
this.config.get(MailConfig.STORAGE_TYPE),
StorageType.JSON
);
}

@Override
public CompletableFuture<Mailbox> mailbox(UUID target) {
return this.storage.fetch(target);
}

@Override
public CompletableFuture<Void> send(UUID from, UUID to, Component message) {
return this.mailbox(to).thenAccept(mailbox -> {
MailMessage mail = new ImpactorMailMessage(from, message);
mailbox.append(mail);
});

}

@Override
public String name() {
return "Impactor Mail Service";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package net.impactdev.impactor.core.mail;

import com.google.common.collect.Lists;
import net.impactdev.impactor.api.mail.MailMessage;
import net.impactdev.impactor.api.mail.Mailbox;
import net.impactdev.impactor.api.mail.filters.MailFilter;

import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class ImpactorMailbox implements Mailbox {

private final List<MailMessage> messages = Lists.newCopyOnWriteArrayList();

public ImpactorMailbox(List<MailMessage> messages) {
this.messages.addAll(messages);
}

@Override
public Set<MailMessage> mail(MailFilter... filters) {
Stream<MailMessage> stream = this.messages.stream();
for(MailFilter filter : filters) {
stream = stream.filter(filter);
}

return stream.collect(Collectors.toUnmodifiableSet());
}

@Override
public CompletableFuture<Boolean> append(MailMessage message) {
return CompletableFuture.supplyAsync(() -> {
this.messages.add(message);

return true;
}).orTimeout(5, TimeUnit.SECONDS);
}

@Override
public CompletableFuture<Boolean> remove(MailMessage message) {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> removeIf(MailFilter... filters) {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> clear() {
return CompletableFuture.completedFuture(false);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.impactdev.impactor.core.mail;

import net.impactdev.impactor.api.configuration.key.ConfigKey;
import net.impactdev.impactor.api.storage.StorageType;

import static net.impactdev.impactor.api.configuration.key.ConfigKeyFactory.key;

public final class MailConfig {

public static final ConfigKey<StorageType> STORAGE_TYPE = key(adapter ->
StorageType.parse(adapter.getString("storage-method", "json"))
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.impactdev.impactor.core.mail;

import net.impactdev.impactor.api.mail.MailService;
import net.impactdev.impactor.api.providers.ServiceProvider;
import net.impactdev.impactor.core.modules.ImpactorModule;

public final class MailModule implements ImpactorModule {

@Override
public void services(ServiceProvider provider) {
provider.register(MailService.class, new ImpactorMailService());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package net.impactdev.impactor.core.mail.storage;

import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.impactdev.impactor.api.Impactor;
import net.impactdev.impactor.api.mail.MailMessage;
import net.impactdev.impactor.api.mail.Mailbox;
import net.impactdev.impactor.api.storage.Storage;
import net.impactdev.impactor.api.utility.ExceptionPrinter;
import net.impactdev.impactor.api.utility.printing.PrettyPrinter;
import net.impactdev.impactor.core.plugin.BaseImpactorPlugin;
import net.impactdev.impactor.core.utility.future.ThrowingRunnable;
import net.impactdev.impactor.core.utility.future.ThrowingSupplier;

import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;

public final class MailStorage implements Storage {

private final MailStorageImplementation implementation;
private final AsyncLoadingCache<UUID, Mailbox> mailboxes;

MailStorage(MailStorageImplementation implementation) {
this.implementation = implementation;
this.mailboxes = Caffeine.newBuilder()
.expireAfterAccess(1, TimeUnit.HOURS)
.buildAsync(this.implementation::fetch);
}

@Override
public void init() throws Exception {
this.implementation.init();
}

@Override
public void shutdown() throws Exception {
this.implementation.shutdown();
}

@Override
public CompletableFuture<Void> meta(PrettyPrinter printer) {
return run(() -> this.implementation.meta(printer)).orTimeout(5, TimeUnit.SECONDS);
}

public CompletableFuture<Mailbox> fetch(UUID target) {
return this.mailboxes.get(target);
}

public CompletableFuture<Void> save(UUID target, MailMessage message) {
return run(() -> this.implementation.save(target, message)).thenAccept(ignore -> this.mailboxes.synchronous().invalidate(target));
}

public CompletableFuture<Void> remove(UUID target, UUID message) {
return run(() -> this.implementation.remove(target, message)).thenAccept(ignore -> this.mailboxes.synchronous().invalidate(target));
}

private static CompletableFuture<Void> run(ThrowingRunnable runnable) {
return CompletableFuture.runAsync(() -> {
try {
runnable.run();
} catch (Exception e) {
ExceptionPrinter.print(BaseImpactorPlugin.instance().logger(), e);
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
}, Impactor.instance().scheduler().async());
}

private static <T> CompletableFuture<T> supply(ThrowingSupplier<T> supplier) {
return CompletableFuture.supplyAsync(() -> {
try {
return supplier.supply();
} catch (Exception e) {
ExceptionPrinter.print(BaseImpactorPlugin.instance().logger(), e);
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
}, Impactor.instance().scheduler().async());
}
}
Loading

0 comments on commit 8ad7acc

Please sign in to comment.