personal/q3k: add minecraft plugins

Also drive-by modify WORKSPACE to add required deps.

Also drive-by update deps in WORKSPACE.

Also drive-by remove old stackb/proto library from WORKSPACE (only used
in cccampix, which is dead, and stackb/proto should be replaceable by
the main grpc lib by this point).

Change-Id: I7ac7fe2237e859dc1c45bf41a016174ed8e9ee71
diff --git a/personal/q3k/minecraft/plugin/BUILD b/personal/q3k/minecraft/plugin/BUILD
new file mode 100644
index 0000000..ac8e49f
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/BUILD
@@ -0,0 +1,14 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["genpluginyml.go"],
+    importpath = "code.hackerspace.pl/personal/q3k/minecraft/plugins/genpluginyml",
+    visibility = ["//visibility:private"],
+)
+
+go_binary(
+    name = "genpluginyml",
+    embed = [":go_default_library"],
+    visibility = ["//visibility:public"],
+)
diff --git a/personal/q3k/minecraft/plugin/defs.bzl b/personal/q3k/minecraft/plugin/defs.bzl
new file mode 100644
index 0000000..898b057
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/defs.bzl
@@ -0,0 +1,64 @@
+load("//bzl:rules.bzl", copy_binary="copy_go_binary")
+
+def _plugin_yml_gen_impl(ctx):
+    ctx.actions.run(
+        mnemonic = "PluginYmlGen",
+        progress_message = "Generating plugin.yml",
+
+        inputs = [ctx.info_file],
+        outputs = [ctx.outputs.out],
+        executable = ctx.executable._genpluginyml,
+        arguments = [
+            "-name", ctx.label.name,
+            "-author", ctx.attr.author,
+            "-main", ctx.attr.main,
+            "-version", ctx.attr.version,
+            "-status_file", ctx.info_file.path,
+            "-out_file", ctx.outputs.out.path,
+        ],
+    )
+
+plugin_yml_gen = rule(
+    implementation = _plugin_yml_gen_impl,
+    attrs = {
+        "main": attr.string(mandatory = True),
+        "version": attr.string(default = ""),
+        "author": attr.string(default = "bazel"),
+        "_genpluginyml": attr.label(
+            default = Label("//personal/q3k/minecraft/plugin:genpluginyml"),
+            cfg = "host",
+            executable = True,
+            allow_files = True,
+        ),
+    },
+    outputs = {
+        "out": "plugin.yml",
+    },
+)
+
+def bukkit_plugin(name, srcs, deps, main, author="", version=""):
+    ymlname = name + "-yml"
+    plugin_yml_gen(
+        name = ymlname,
+        author = author,
+        version = version,
+        main = main,
+    )
+
+    jarname = name + "-jar"
+    native.java_binary(
+        name = jarname,
+        create_executable = False,
+        srcs = srcs,
+        deps = deps,
+        classpath_resources = [":" + ymlname],
+    )
+
+    copy_binary(
+        name = name + ".jar",
+        src = ":" + jarname + "_deploy.jar",
+    )
+    native.alias(
+        name = name,
+        actual = ":" + name + ".jar",
+    )
diff --git a/personal/q3k/minecraft/plugin/example/BUILD b/personal/q3k/minecraft/plugin/example/BUILD
new file mode 100644
index 0000000..e3bc13a
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/example/BUILD
@@ -0,0 +1,11 @@
+load("//personal/q3k/minecraft/plugin:defs.bzl", "bukkit_plugin")
+bukkit_plugin(
+    name = "example",
+    main = "hscloud.personal.q3k.minecraft.plugin.example.Main",
+    srcs = [
+        "Main.java"
+    ],
+    deps = [
+        "@maven//:org_spigotmc_spigot_api",
+    ],
+)
diff --git a/personal/q3k/minecraft/plugin/example/Main.java b/personal/q3k/minecraft/plugin/example/Main.java
new file mode 100644
index 0000000..a67ee78
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/example/Main.java
@@ -0,0 +1,15 @@
+package hscloud.personal.q3k.minecraft.plugin.example;
+
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class Main extends JavaPlugin {
+    @Override
+    public void onEnable() {
+        System.out.println("enabled");
+    }
+
+    @Override
+    public void onDisable() {
+        System.out.println("disabled");
+    }
+}
diff --git a/personal/q3k/minecraft/plugin/genpluginyml.go b/personal/q3k/minecraft/plugin/genpluginyml.go
new file mode 100644
index 0000000..1174f8f
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/genpluginyml.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"strings"
+)
+
+var (
+	flagName    string
+	flagAuthor  string
+	flagMain    string
+	flagVersion string
+
+	flagStatusFile string
+	flagOutFile    string
+)
+
+func main() {
+	flag.StringVar(&flagName, "name", "", "plugin name")
+	flag.StringVar(&flagAuthor, "author", "", "plugin author")
+	flag.StringVar(&flagMain, "main", "", "plugin main class")
+	flag.StringVar(&flagVersion, "version", "", "plugin version, if not given, workspace status will be used")
+	flag.StringVar(&flagStatusFile, "status_file", "", "path to workspace status file for version generation")
+	flag.StringVar(&flagOutFile, "out_file", "plugin.yml", "path to output plugin.yml")
+	flag.Parse()
+
+	if flagVersion == "" {
+		status, err := ioutil.ReadFile(flagStatusFile)
+		if err != nil {
+			log.Fatalf("ReadFile(%q): %v", flagStatusFile, err)
+		}
+
+		for _, line := range strings.Split(string(status), "\n") {
+			line = strings.TrimSpace(line)
+			parts := strings.Fields(line)
+			if len(parts) != 2 {
+				continue
+			}
+			if parts[0] == "STABLE_GIT_VERSION" {
+				flagVersion = fmt.Sprintf("hscloud-git-%s", parts[1])
+			}
+		}
+	}
+
+	if flagVersion == "" {
+		log.Fatalf("could not determine version from status")
+	}
+
+	// a yaml is a json, a json is a yaml
+	yml := struct {
+		Name       string `json:"name"`
+		Version    string `json:"version"`
+		Author     string `json:"author"`
+		Main       string `json:"main"`
+		APIVersion string `json:"api-version"`
+	}{
+		Name:       flagName,
+		Version:    flagVersion,
+		Author:     flagAuthor,
+		Main:       flagMain,
+		APIVersion: "1.15",
+	}
+
+	out, err := json.Marshal(&yml)
+	if err != nil {
+		log.Fatalf("marshal: %v", err)
+	}
+	err = ioutil.WriteFile(flagOutFile, out, 0644)
+	if err != nil {
+		log.Fatalf("WriteFile(%q): %v", out, err)
+	}
+}
diff --git a/personal/q3k/minecraft/plugin/hscloud/BUILD b/personal/q3k/minecraft/plugin/hscloud/BUILD
new file mode 100644
index 0000000..418c5c5
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/hscloud/BUILD
@@ -0,0 +1,19 @@
+load("//personal/q3k/minecraft/plugin:defs.bzl", "bukkit_plugin")
+bukkit_plugin(
+    name = "hscloud",
+    main = "hscloud.personal.q3k.minecraft.plugin.hscloud.Main",
+    author = "q3k",
+    srcs = [
+        "Main.java",
+        "StateSynchronizer.java",
+    ],
+    deps = [
+        "//personal/q3k/minecraft/plugin/hscloud/proto:hscloud_java_grpc",
+        "//personal/q3k/minecraft/plugin/hscloud/proto:hscloud_java_proto",
+        "@maven//:org_spigotmc_spigot_api",
+        "@io_grpc_grpc_java//api",
+        "@io_grpc_grpc_java//stub",
+        "@maven//:io_grpc_grpc_netty_shaded",
+        "@maven//:io_grpc_grpc_services",
+    ],
+)
diff --git a/personal/q3k/minecraft/plugin/hscloud/Main.java b/personal/q3k/minecraft/plugin/hscloud/Main.java
new file mode 100644
index 0000000..242bbb1
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/hscloud/Main.java
@@ -0,0 +1,108 @@
+package hscloud.personal.q3k.minecraft.plugin.hscloud;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.logging.Logger;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.protobuf.services.ProtoReflectionService;
+import io.grpc.stub.StreamObserver;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitScheduler;
+
+import hscloud.personal.q3k.minecraft.plugin.hscloud.proto.IntrospectorGrpc;
+import hscloud.personal.q3k.minecraft.plugin.hscloud.proto.Proto;
+
+
+public class Main extends JavaPlugin {
+    private Server server_;
+    private static final Logger logger_ = Logger.getLogger(Main.class.getName());
+    private static final int port_ = 2137;
+
+    public StateSynchronizer synchronizer_;
+
+    public Main() {
+        synchronizer_ = new StateSynchronizer();
+        server_ = ServerBuilder
+            .forPort(port_)
+            .addService(new IntrospectorService(synchronizer_))
+            .addService(ProtoReflectionService.newInstance())
+            .build();
+    }
+
+    @Override
+    public void onEnable() {
+
+        BukkitScheduler scheduler = getServer().getScheduler();
+        scheduler.scheduleSyncRepeatingTask(this, new Runnable() {
+            @Override
+            public void run() {
+                ArrayList<StateSynchronizer.Player> players = new ArrayList<StateSynchronizer.Player>();
+                for (Player p : Bukkit.getOnlinePlayers()) {
+                    StateSynchronizer.Player pp = new StateSynchronizer.Player(p.getPlayerListName(), p.getUniqueId().toString());
+                    players.add(pp);
+                }
+                synchronizer_.setPlayers(players.toArray(new StateSynchronizer.Player[0]));
+
+                World world = Bukkit.getWorld("world");
+                if (world != null) {
+                    synchronizer_.setTime(world.getTime());
+                }
+            }
+        }, 0, 20L);
+
+        try {
+            server_.start();
+        } catch (IOException e) {
+            logger_.severe("could not start gRPC");
+            e.printStackTrace(System.err);
+            return;
+        }
+        logger_.info("gRPC started, listening on " + port_);
+    }
+
+    @Override
+    public void onDisable() {
+        if (server_ != null) {
+            try {
+                server_.shutdown().awaitTermination(30, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                logger_.severe("could not stop gRPC");
+                e.printStackTrace(System.err);
+            }
+        }
+        server_ = null;
+    }
+
+    public static class IntrospectorService extends IntrospectorGrpc.IntrospectorImplBase {
+        private StateSynchronizer synchronizer_;
+
+        public IntrospectorService(StateSynchronizer synchronizer) {
+            synchronizer_ = synchronizer;
+        }
+
+        @Override
+        public void status(Proto.StatusRequest request, StreamObserver<Proto.StatusResponse> responseObserver) {
+            StateSynchronizer.Player[] players = synchronizer_.getPlayers();
+
+            Proto.StatusResponse.Builder builder = Proto.StatusResponse.newBuilder();
+            builder.setTime(synchronizer_.getTime());
+            for (StateSynchronizer.Player p : players) {
+                builder.addPlayers(Proto.Player.newBuilder()
+                    .setUsername(p.getName())
+                    .setUuid(p.getUUID())
+                    .build());
+
+
+            }
+            responseObserver.onNext(builder.build());
+            responseObserver.onCompleted();
+        }
+    }
+}
diff --git a/personal/q3k/minecraft/plugin/hscloud/StateSynchronizer.java b/personal/q3k/minecraft/plugin/hscloud/StateSynchronizer.java
new file mode 100644
index 0000000..a10f5d5
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/hscloud/StateSynchronizer.java
@@ -0,0 +1,49 @@
+package hscloud.personal.q3k.minecraft.plugin.hscloud;
+
+public class StateSynchronizer {
+    private Player[] players_;
+    private long time_;
+    private Object lock_ = new Object();
+
+    public void setPlayers(Player[] players) {
+        synchronized(lock_) {
+            players_ = players;
+        }
+    }
+
+    public void setTime(long time) {
+        synchronized(lock_) {
+            time_ = time;
+        }
+    }
+
+    public Player[] getPlayers() {
+        synchronized(lock_) {
+            return players_;
+        }
+    }
+
+    public long getTime() {
+        synchronized(lock_) {
+            return time_;
+        }
+    }
+
+    public static class Player {
+        private String name_;
+        private String uuid_;
+
+        public Player(String name, String uuid) {
+            name_ = name;
+            uuid_ = uuid;
+        }
+
+        public String getName() {
+            return name_;
+        }
+
+        public String getUUID() {
+            return uuid_;
+        }
+    }
+}
diff --git a/personal/q3k/minecraft/plugin/hscloud/proto/BUILD b/personal/q3k/minecraft/plugin/hscloud/proto/BUILD
new file mode 100644
index 0000000..139d994
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/hscloud/proto/BUILD
@@ -0,0 +1,20 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library")
+
+proto_library(
+    name = "hscloud_proto",
+    srcs = ["hscloud.proto"],
+)
+
+java_proto_library(
+    name = "hscloud_java_proto",
+    deps = [":hscloud_proto"],
+    visibility = ["//visibility:public"],
+)
+
+java_grpc_library(
+    name = "hscloud_java_grpc",
+    srcs = [":hscloud_proto"],
+    deps = [":hscloud_java_proto"],
+    visibility = ["//visibility:public"],
+)
diff --git a/personal/q3k/minecraft/plugin/hscloud/proto/hscloud.proto b/personal/q3k/minecraft/plugin/hscloud/proto/hscloud.proto
new file mode 100644
index 0000000..05b8da7
--- /dev/null
+++ b/personal/q3k/minecraft/plugin/hscloud/proto/hscloud.proto
@@ -0,0 +1,21 @@
+syntax = "proto3";
+package hscloud.personal.q3k.minecraft.plugin.hscloud;
+option java_package = "hscloud.personal.q3k.minecraft.plugin.hscloud.proto";
+option java_outer_classname = "Proto";
+
+service Introspector {
+    rpc Status(StatusRequest) returns (StatusResponse);
+}
+
+message StatusRequest {
+}
+
+message StatusResponse {
+    repeated Player players = 1;
+    int64 time = 2;
+}
+
+message Player {
+    string username = 1;
+    string uuid = 2;
+}