Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"features": {
"ghcr.io/devcontainers/features/git" : {},
"ghcr.io/devcontainers/features/java:1": {
"version": "21.0.7",
"version": "23",
"installGradle": true
}
},
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Please use it for being able to tell "It works on my machine".
- Select `From VCS Project`
- Select and connect with `Docker`
- Select `IntelliJ IDEA`
- Enter `Git Repository`: `https://github.com/o7studios/cheetah`
- Enter `Git Repository`: `https://github.com/o7studios/octopus-plugin`
- Select `Detection for devcontainer.json file` `Automatic`
- Press `Build Container and Continue`

Expand Down
2 changes: 1 addition & 1 deletion api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
dependencies {
api("studio.o7:octopus-sdk:0.0.2")
api("studio.o7:octopus-sdk:0.1.2")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify SDK version availability and breaking changes.

The SDK version jump from 0.0.2 to 0.1.2 corresponds to significant architectural changes. Please verify:

  1. The new SDK version is published and available
  2. Breaking changes are documented
  3. Migration from client-based to gRPC stub architecture is properly supported

🏁 Script executed:

#!/bin/bash
# Check if the new SDK version is available on Maven Central or the appropriate repository
echo "Checking SDK version availability..."
curl -s "https://repo1.maven.org/maven2/studio/o7/octopus-sdk/" | grep -o "0\.1\.2" || echo "SDK version 0.1.2 not found on Maven Central"

# Check for any release notes or changelog
curl -s "https://api.github.com/repos/o7studios/octopus-sdk/releases" | jq -r '.[] | select(.tag_name | contains("0.1.2")) | .body' 2>/dev/null || echo "No release information found"

Length of output: 317


SDK 0.1.2 Verified on Maven Central; Release Notes Missing

  • Version 0.1.2 is published and available on Maven Central.
  • No release notes or changelog entry for 0.1.2 were found in the GitHub releases.

Action items:

  • Please add or link to official release notes (e.g. CHANGELOG.md or GitHub release body) for 0.1.2.
  • Confirm that all breaking changes (client → gRPC stub refactoring, subscription management updates) are documented.
  • Include a migration guide in the project docs to assist users upgrading from 0.0.2 to 0.1.2.
🤖 Prompt for AI Agents
In api/build.gradle.kts at line 2, the SDK version 0.1.2 is added but there are
no release notes or changelog entries for this version. Add or link to the
official release notes for version 0.1.2 in the project documentation, ensure
all breaking changes like client to gRPC stub refactoring and subscription
management updates are documented, and include a migration guide to help users
upgrade from version 0.0.2 to 0.1.2.

}

information {
Expand Down
51 changes: 41 additions & 10 deletions api/src/main/java/studio/o7/octopus/plugin/api/Octopus.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package studio.o7.octopus.plugin.api;

import org.bukkit.plugin.java.JavaPlugin;
import lombok.NonNull;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import studio.o7.octopus.plugin.api.client.OctopusClient;
import studio.o7.octopus.plugin.Unsafe;
import studio.o7.octopus.sdk.gen.api.v1.Entry;
import studio.o7.octopus.sdk.gen.api.v1.Event;

import java.util.Collection;
import java.util.List;

@NullMarked
public interface Octopus {
Expand All @@ -12,13 +18,38 @@ static Octopus get() {
return Unsafe.getInstance().get();
}

OctopusClient getClient();

JavaPlugin getLibraryPlugin();

/**
* Returns true if event has been published successfully.
*/
boolean publishEvent(@NonNull Event event);

/**
* Get entries by key, optionally filtered.
*/
@NotNull List<Entry> getEntry(@NonNull String key);
Comment on lines +26 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Documentation inconsistency with method signature

The documentation mentions "optionally filtered" but the method signature doesn't include any filter parameters.

Update the documentation to match the actual functionality:

     /**
-     * Get entries by key, optionally filtered.
+     * Get entries by key.
      */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Get entries by key, optionally filtered.
*/
@NotNull List<Entry> getEntry(@NonNull String key);
/**
* Get entries by key.
*/
@NotNull List<Entry> getEntry(@NonNull String key);
🤖 Prompt for AI Agents
In api/src/main/java/studio/o7/octopus/plugin/api/Octopus.java around lines 25
to 28, the method documentation mentions "optionally filtered" but the method
signature does not accept any filter parameters. Update the Javadoc comment to
remove the reference to optional filtering so it accurately reflects that the
method only retrieves entries by key without any filters.


/**
* Adds list of keys that should be subscribed.
*/
void addSubscriptions(@NonNull Collection<String> subscriptions);


/**
* Removes list of keys that shouldn't be subscribed.
*/
void removeSubscriptions(@NonNull Collection<String> subscriptions);

/**
* Resets list of keys that should be subscribed completely.
* @apiNote Also resets all subscribed/unsubscribed keys
* which have been added by {@link Octopus#addSubscriptions}
* or removed by {@link Octopus#removeSubscriptions}
*/
@ApiStatus.Experimental
void setSubscriptions(@NonNull Collection<String> subscriptions);

/**
* Returns the identifier of this service (e.g. service-name)
*/
String getIdentifier();

String getHost();

int getPort();
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package studio.o7.octopus.plugin.api.client;

import studio.o7.octopus.sdk.gen.api.v1.EntryResponse;
import lombok.NonNull;
import studio.o7.octopus.sdk.gen.api.v1.Entry;
import studio.o7.octopus.sdk.gen.api.v1.Event;

import java.util.Collection;
import java.util.List;

public interface OctopusClient {

boolean emitEvent(Event event);
boolean publishEvent(@NonNull Event event);

EntryResponse getEntry(String key);
List<Entry> getEntry(@NonNull String key);

void initializeSubscription(List<String> subscriptions);

void updateSubscriptions(List<String> subscriptions);
void updateSubscriptions(@NonNull Collection<String> subscriptions);
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package studio.o7.octopus.plugin.api.events;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NonNull;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import studio.o7.octopus.sdk.gen.api.v1.Action;

@RequiredArgsConstructor
@Getter
public class OctopusActionEvent extends Event {

private static final HandlerList HANDLER_LIST = new HandlerList();
private final Action action;

private final String key;
private final Action action;

public OctopusActionEvent(@NonNull String key, @NonNull Action action) {
super(true);
this.key = key;
this.action = action;
}

@Override
public @NotNull HandlerList getHandlers() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package studio.o7.octopus.plugin.api.events;

import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import studio.o7.octopus.sdk.gen.api.v1.EntryEdit;
import studio.o7.octopus.sdk.gen.api.v1.EntryMutation;

@RequiredArgsConstructor
@Getter
public class OctopusEntryChangeEvent extends Event {

public final class OctopusEntryMutationEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final EntryEdit entry;

private final String key;
private final EntryMutation entryMutation;

public OctopusEntryMutationEvent(@NonNull String key, @NonNull EntryMutation entryMutation) {
super(true);
this.key = key;
this.entryMutation = entryMutation;
}

@Override
public @NotNull HandlerList getHandlers() {
Expand All @@ -23,5 +29,4 @@ public class OctopusEntryChangeEvent extends Event {
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}

}
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package studio.o7.octopus.plugin.api.events;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NonNull;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import studio.o7.octopus.sdk.gen.api.v1.PlayerNotification;

@RequiredArgsConstructor
@Getter
public class OctopusPlayerNotificationEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final PlayerNotification playerNotification;

private final String key;
private final PlayerNotification playerNotification;

public OctopusPlayerNotificationEvent(@NonNull String key, @NonNull PlayerNotification playerNotification) {
super(true);
this.key = key;
this.playerNotification = playerNotification;
}

@Override
public @NotNull HandlerList getHandlers() {
Expand Down
83 changes: 62 additions & 21 deletions plugin/src/main/java/studio/o7/octopus/plugin/OctopusImpl.java
Original file line number Diff line number Diff line change
@@ -1,49 +1,90 @@
package studio.o7.octopus.plugin;

import it.unimi.dsi.fastutil.objects.ObjectArraySet;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import studio.o7.octopus.plugin.api.Octopus;
import studio.o7.octopus.plugin.api.client.OctopusClient;
import studio.o7.octopus.plugin.client.OctopusClientImpl;
import studio.o7.octopus.sdk.OctopusSDK;
import studio.o7.octopus.sdk.gen.api.v1.Entry;
import studio.o7.octopus.sdk.gen.api.v1.EntryRequest;
import studio.o7.octopus.sdk.gen.api.v1.Event;
import studio.o7.octopus.sdk.gen.api.v1.OctopusServiceGrpc;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

public final class OctopusImpl implements Octopus {
private final Plugin plugin;

private final OctopusServiceGrpc.OctopusServiceStub stub = OctopusSDK.stub();
private final OctopusServiceGrpc.OctopusServiceBlockingStub blockingStub = OctopusSDK.blockingStub();

private final Logger logger;
private final OctopusSubscriptions subscriptions;

private final OctopusClient client;
private final String identifier;
private final String host;
private final int port;
private String identifier;

private final Collection<String> subscriptionList = Collections.synchronizedSet(new ObjectArraySet<>());

public OctopusImpl(Plugin plugin) {
this.identifier = "test-minecraft-plugin";
this.host = "127.0.0.1";
this.port = 50051;
this.client = new OctopusClientImpl(host, port, plugin, identifier);
this.plugin = plugin;
logger = plugin.getSLF4JLogger();
subscriptions = new OctopusSubscriptions(plugin, logger, stub);
}

@Override
public @NotNull OctopusClient getClient() {
return client;
public boolean publishEvent(@NonNull Event event) {
try {
var publishEventResponse = blockingStub.publishEvent(event);
logger.info("Published event on key {}", event.getKey());
return publishEventResponse.getSuccess();
} catch (Exception e) {
logger.error("Failed to publish event on key {}", event.getKey(), e);
return false;
}
}

@Override
public @NotNull JavaPlugin getLibraryPlugin() {
return JavaPlugin.getPlugin(OctopusPlugin.class);
public @NotNull List<Entry> getEntry(@NonNull String key) {
try {
return blockingStub.getEntry(EntryRequest.newBuilder().setKey(key).build()).getEntriesList();
} catch (Exception e) {
logger.error("Failed to retrieve entry on key {}", key, e);
return Collections.emptyList();
}
}

@Override
public @NotNull String getIdentifier() {
return identifier;
public void addSubscriptions(@NonNull Collection<String> subscriptions) {
this.subscriptionList.addAll(subscriptions);
this.subscriptions.updateSubscriptions(subscriptionList);
}

@Override
public @NotNull String getHost() {
return host;
public void removeSubscriptions(@NonNull Collection<String> subscriptions) {
this.subscriptionList.removeAll(subscriptions);
this.subscriptions.updateSubscriptions(subscriptionList);
}

@Override
public int getPort() {
return port;
public synchronized void setSubscriptions(@NonNull Collection<String> subscriptions) {
this.subscriptionList.clear();
this.subscriptionList.addAll(subscriptions);
this.subscriptions.updateSubscriptions(subscriptionList);
}

@Override
public @NotNull String getIdentifier() {
if (this.identifier != null) return identifier;
identifier = System.getProperty("octopus.identifier");
if (identifier == null || identifier.isEmpty())
identifier = System.getenv("OCTOPUS_IDENTIFIER");
if (identifier == null || identifier.isEmpty())
identifier = "default";
return identifier;
}
Comment on lines +80 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add thread safety to lazy identifier initialization.

The lazy initialization pattern is not thread-safe. Multiple threads could initialize the identifier field simultaneously.

 @Override
-public @NotNull String getIdentifier() {
+public synchronized @NotNull String getIdentifier() {
     if (this.identifier != null) return identifier;
     identifier = System.getProperty("octopus.identifier");
     if (identifier == null || identifier.isEmpty())
         identifier = System.getenv("OCTOPUS_IDENTIFIER");
     if (identifier == null || identifier.isEmpty())
         identifier = "default";
     return identifier;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public @NotNull String getIdentifier() {
if (this.identifier != null) return identifier;
identifier = System.getProperty("octopus.identifier");
if (identifier == null || identifier.isEmpty())
identifier = System.getenv("OCTOPUS_IDENTIFIER");
if (identifier == null || identifier.isEmpty())
identifier = "default";
return identifier;
}
@Override
public synchronized @NotNull String getIdentifier() {
if (this.identifier != null) return identifier;
identifier = System.getProperty("octopus.identifier");
if (identifier == null || identifier.isEmpty())
identifier = System.getenv("OCTOPUS_IDENTIFIER");
if (identifier == null || identifier.isEmpty())
identifier = "default";
return identifier;
}
🤖 Prompt for AI Agents
In plugin/src/main/java/studio/o7/octopus/plugin/OctopusImpl.java around lines
68 to 77, the lazy initialization of the identifier field is not thread-safe,
which can cause multiple threads to initialize it concurrently. To fix this,
synchronize the block of code that checks and sets the identifier or use a
thread-safe lazy initialization pattern such as double-checked locking to ensure
only one thread initializes the identifier field.

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import org.bukkit.plugin.java.JavaPlugin;
import studio.o7.octopus.plugin.api.Octopus;

@SuppressWarnings("UnstableApiUsage")
@Getter
public final class OctopusPlugin extends JavaPlugin implements PluginInstance {

Expand Down
Loading