Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Locale loading and reloading fixes #267

Merged
merged 2 commits into from Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -56,7 +56,6 @@
import net.draycia.carbon.common.util.Exceptions;
import net.draycia.carbon.common.util.FileUtil;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.InvalidKeyException;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -225,21 +224,10 @@ private void loadConfigChannels_(final CarbonMessages messages) {
continue;
}

final @Nullable ChatChannel chatChannel;

try {
chatChannel = this.loadChannel(channelConfigFile);
} catch (final ConfigurateException configurateException) {
if (this.findThrowableRootCause(configurateException) instanceof InvalidKeyException keyException) {
this.logger.error("Invalid channel key found: " + keyException.getMessage());
}
continue;
}

final @Nullable ChatChannel chatChannel = this.loadChannel(channelConfigFile);
if (chatChannel == null) {
continue;
}

final Key channelKey = chatChannel.key();
if (this.defaultKey.equals(channelKey)) {
this.logger.info("Default channel is [" + channelKey + "]");
Expand All @@ -265,15 +253,6 @@ private void loadConfigChannels_(final CarbonMessages messages) {
this.logger.info("Registered channels: [" + channels + "]");
}

private Throwable findThrowableRootCause(Throwable throwable) {
Objects.requireNonNull(throwable);
Throwable rootCause = throwable;
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
rootCause = rootCause.getCause();
}
return rootCause;
}

private void saveDefaultChannelConfig() {
try {
final Path configFile = this.configChannelDir.resolve("global.conf");
Expand All @@ -287,17 +266,18 @@ private void saveDefaultChannelConfig() {
}
}

private @Nullable ChatChannel loadChannel(final Path channelFile) throws ConfigurateException {
private @Nullable ChatChannel loadChannel(final Path channelFile) {
final ConfigurationLoader<?> loader = this.configFactory.configurationLoader(channelFile);

try {
final ConfigurationNode loaded = updateNode(loader.load());
loader.save(loaded);
return MAPPER.load(loaded);
} catch (final ConfigurateException configurateException) {
this.logger.warn("Failed to load channel from file '{}'", channelFile);
throw configurateException;
} catch (final ConfigurateException exception) {
this.logger.warn("Failed to load channel from file '{}'", channelFile, exception);
}

return null;
}

private void sendMessageInChannelAsPlayer(
Expand Down
Expand Up @@ -43,7 +43,6 @@
import net.draycia.carbon.common.messages.placeholders.UUIDPlaceholderResolver;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.KeyPattern;
import net.kyori.adventure.text.Component;
import net.kyori.moonshine.Moonshine;
import net.kyori.moonshine.exception.scan.UnscannableMethodException;
Expand All @@ -68,21 +67,12 @@ public final class ConfigChatChannel implements ChatChannel {

private transient @MonotonicNonNull @Inject CarbonChat carbonChat;

@Comment("""
The channel's name.
This is also what's used for channel commands.
The 'key' option overrides this.
Must follow the format of [a-z0-9/.-_]
""")
@KeyPattern
private String name = "global";

@Comment("""
The channel's key, used to track the channel.
You only need to change the second part of the key. "global" by default.
The value is what's used in commands, this is probably what you want to change.
""")
private @Nullable Key key = null;
private @Nullable Key key = Key.key("carbon", "global");

@Comment("""
The permission required to use the /channel <channelname> and /<channelname> commands.
Expand All @@ -102,7 +92,6 @@ public final class ConfigChatChannel implements ChatChannel {

private @Nullable Boolean shouldRegisterCommands = true;

@Deprecated(since = "2.0", forRemoval = true)
private @Nullable String commandName = null;

private @Nullable List<String> commandAliases = Collections.emptyList();
Expand Down Expand Up @@ -139,7 +128,7 @@ public boolean shouldRegisterCommands() {

@Override
public String commandName() {
return Objects.requireNonNullElse(this.commandName, this.key().value());
return Objects.requireNonNullElse(this.commandName, this.key.value());
}

@Override
Expand Down Expand Up @@ -172,7 +161,7 @@ public ChannelPermissionResult speechPermitted(final CarbonPlayer carbonPlayer)

@Override
public ChannelPermissionResult hearingPermitted(final CarbonPlayer player) {
return ChannelPermissionResult.allowedIf(empty(), () -> player.hasPermission(this.permission() + ".see") && !player.leftChannels().contains(this.key()));
return ChannelPermissionResult.allowedIf(empty(), () -> player.hasPermission(this.permission() + ".see") && !player.leftChannels().contains(this.key));
}

@Override
Expand Down Expand Up @@ -204,11 +193,7 @@ public Set<CarbonPlayer> filterRecipients(final CarbonPlayer sender, final Set<C

@Override
public @NonNull Key key() {
if (this.key != null) {
return this.key;
}

return Key.key("carbon", this.name);
return Objects.requireNonNull(this.key);
}

public String messageFormat(final CarbonPlayer sender) {
Expand Down
Expand Up @@ -67,7 +67,7 @@
public final class CarbonMessageSource implements IMessageSource<Audience, String> {

private final Locale defaultLocale;
private final Map<Locale, Properties> locales = new HashMap<>();
private volatile Map<Locale, Properties> locales = Map.of();
private final Path pluginJar;
private final Logger logger;
private final Path dataDirectory;
Expand Down Expand Up @@ -110,6 +110,8 @@ private CarbonMessageSource(
}

private void reloadTranslations() throws IOException {
final Map<Locale, Properties> map = new HashMap<>();

final Path localeDirectory = this.dataDirectory.resolve("locale");

// Create locale directory
Expand All @@ -132,7 +134,7 @@ private void reloadTranslations() throws IOException {
return;
}

this.readLocale(localeDirectory, localeFile, locale);
this.tryLoadLocale(map, localeDirectory, localeFile, locale);
}));

try (final Stream<Path> paths = Files.list(localeDirectory)) {
Expand All @@ -145,16 +147,25 @@ private void reloadTranslations() throws IOException {
return;
}

if (this.locales.containsKey(locale)) {
if (map.containsKey(locale)) {
return;
}

this.readLocale(localeDirectory, localeFile, locale);
this.tryLoadLocale(map, localeDirectory, localeFile, locale);
});
}

this.locales = Map.copyOf(map);
}

private void tryLoadLocale(final Map<Locale, Properties> map, final Path localeDirectory, final Path localeFile, final Locale locale) {
final @Nullable Properties properties = this.readLocale(localeDirectory, localeFile, locale);
if (properties != null) {
map.put(locale, properties);
}
}

private void readLocale(final Path localeDirectory, final Path localeFile, final Locale locale) {
private @Nullable Properties readLocale(final Path localeDirectory, final Path localeFile, final Locale locale) {
this.logger.info("Found locale {} ({}) in: {}", locale.getDisplayName(), locale, localeFile);

final Properties properties = new Properties() {
Expand All @@ -170,11 +181,12 @@ public synchronized Set<Map.Entry<Object, Object>> entrySet() {

try {
this.loadProperties(properties, localeDirectory, localeFile);
this.locales.put(locale, properties);

this.logger.info("Successfully loaded locale {} ({})", locale.getDisplayName(), locale);
return properties;
} catch (final IOException ex) {
this.logger.warn("Unable to load locale {} ({}) from source: {}", locale.getDisplayName(), locale, localeFile, ex);
return null;
}
}

Expand Down Expand Up @@ -247,37 +259,40 @@ private void walkPluginJar(final Consumer<Stream<Path>> user) throws IOException
private void loadProperties(
final Properties properties,
final Path localeDirectory,
final Path localeFile
final Path sourceFile
) throws IOException {
final Path savedFile = localeDirectory.resolve(localeFile.getFileName().toString());
if (localeFile.normalize().toAbsolutePath().equals(savedFile.normalize().toAbsolutePath())) {
// Origin file and destination file are the same (ie locale was not in the jar, user added)
return;
}
final Path userFile = localeDirectory.resolve(sourceFile.getFileName().toString());
final boolean samePath = sourceFile.normalize().toAbsolutePath().equals(userFile.normalize().toAbsolutePath());

// If the file in the localeDirectory exists, read it to the properties
if (Files.isRegularFile(savedFile)) {
final InputStream inputStream = Files.newInputStream(savedFile);
if (Files.isRegularFile(userFile)) {
// If the file in the localeDirectory exists, read it to the properties
final InputStream inputStream = Files.newInputStream(userFile);
try (final Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
properties.load(reader);
}
} else if (samePath && !Files.isRegularFile(userFile)) {
throw new IllegalStateException("sourceFile == userFile, and is not a regular file (%s)".formatted(userFile));
}

boolean write = !Files.isRegularFile(savedFile);
boolean write = false;

// Read the file in the jar and add missing entries
try (final Reader reader = new InputStreamReader(Files.newInputStream(localeFile), StandardCharsets.UTF_8)) {
final Properties packaged = new Properties();
packaged.load(reader);
if (Files.isRegularFile(sourceFile) && !samePath) {
try (final Reader reader = new InputStreamReader(Files.newInputStream(sourceFile), StandardCharsets.UTF_8)) {
final Properties packaged = new Properties();
packaged.load(reader);

for (final Map.Entry<Object, Object> entry : packaged.entrySet()) {
write |= properties.putIfAbsent(entry.getKey(), entry.getValue()) == null;
for (final Map.Entry<Object, Object> entry : packaged.entrySet()) {
write |= properties.putIfAbsent(entry.getKey(), entry.getValue()) == null;
}
}
}

// todo: copy missing entries from default english locale as well?

// Write properties back to file
if (write) {
try (final Writer outputStream = Files.newBufferedWriter(savedFile)) {
try (final Writer outputStream = Files.newBufferedWriter(userFile)) {
properties.store(outputStream, null);
}
}
Expand Down