diff --git a/pom.xml b/pom.xml
index e8cbd5a..c2cc454 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,5 +109,24 @@
test
+
+
+ com.google.guava
+ guava
+ 27.1-jre
+
+
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.27.2.1
+
+
+ org.jetbrains
+ annotations
+ 17.0.0
+ compile
+
\ No newline at end of file
diff --git a/src/main/java/net/simon987/musicgraph/Main.java b/src/main/java/net/simon987/musicgraph/Main.java
index d00d706..b572185 100644
--- a/src/main/java/net/simon987/musicgraph/Main.java
+++ b/src/main/java/net/simon987/musicgraph/Main.java
@@ -1,6 +1,9 @@
package net.simon987.musicgraph;
+import net.simon987.musicgraph.io.*;
import net.simon987.musicgraph.logging.LogManager;
+import net.simon987.musicgraph.webapi.ArtistController;
+import net.simon987.musicgraph.webapi.CoverController;
import net.simon987.musicgraph.webapi.Index;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
@@ -12,12 +15,10 @@ import java.util.logging.Logger;
public class Main {
- private final static Logger LOGGER = LogManager.getLogger();
+ private final static Logger logger = LogManager.getLogger();
public static void main(String[] args) {
- LOGGER.info("test");
-
startHttpServer();
}
@@ -25,8 +26,12 @@ public class Main {
ResourceConfig rc = new ResourceConfig();
- rc.registerClasses(Index.class);
+ rc.registerInstances(new MusicDatabase());
+ rc.registerInstances(new SQLiteCoverArtDatabase("covers.db"));
+ rc.registerClasses(Index.class);
+ rc.registerClasses(ArtistController.class);
+ rc.registerClasses(CoverController.class);
rc.registerClasses(JacksonFeature.class);
try {
diff --git a/src/main/java/net/simon987/musicgraph/entities/Artist.java b/src/main/java/net/simon987/musicgraph/entities/Artist.java
new file mode 100644
index 0000000..43c27aa
--- /dev/null
+++ b/src/main/java/net/simon987/musicgraph/entities/Artist.java
@@ -0,0 +1,29 @@
+package net.simon987.musicgraph.entities;
+
+
+import java.util.List;
+
+public class Artist {
+
+ public Long id;
+ public String mbid;
+ public String name;
+ public List labels;
+ public int listeners;
+ public int playCount;
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Artist) {
+ return ((Artist) obj).id.equals(id);
+ }
+ return false;
+ }
+}
+
+
diff --git a/src/main/java/net/simon987/musicgraph/entities/ArtistDetails.java b/src/main/java/net/simon987/musicgraph/entities/ArtistDetails.java
new file mode 100644
index 0000000..fcf1d40
--- /dev/null
+++ b/src/main/java/net/simon987/musicgraph/entities/ArtistDetails.java
@@ -0,0 +1,16 @@
+package net.simon987.musicgraph.entities;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ArtistDetails {
+
+ public ArtistDetails() {
+ releases = new ArrayList<>();
+ tags = new ArrayList<>();
+ }
+
+ public String name;
+ public List releases;
+ public List tags;
+}
diff --git a/src/main/java/net/simon987/musicgraph/entities/Relation.java b/src/main/java/net/simon987/musicgraph/entities/Relation.java
new file mode 100644
index 0000000..dadee2e
--- /dev/null
+++ b/src/main/java/net/simon987/musicgraph/entities/Relation.java
@@ -0,0 +1,8 @@
+package net.simon987.musicgraph.entities;
+
+
+public class Relation {
+ public float weight;
+ public long source;
+ public long target;
+}
diff --git a/src/main/java/net/simon987/musicgraph/entities/SearchResult.java b/src/main/java/net/simon987/musicgraph/entities/SearchResult.java
new file mode 100644
index 0000000..09399c4
--- /dev/null
+++ b/src/main/java/net/simon987/musicgraph/entities/SearchResult.java
@@ -0,0 +1,18 @@
+package net.simon987.musicgraph.entities;
+
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class SearchResult {
+
+ public Set artists;
+ public List relations;
+
+ public SearchResult() {
+ artists = new HashSet<>();
+ relations = new ArrayList<>();
+ }
+}
diff --git a/src/main/java/net/simon987/musicgraph/entities/Tag.java b/src/main/java/net/simon987/musicgraph/entities/Tag.java
new file mode 100644
index 0000000..2d4be31
--- /dev/null
+++ b/src/main/java/net/simon987/musicgraph/entities/Tag.java
@@ -0,0 +1,12 @@
+package net.simon987.musicgraph.entities;
+
+public class Tag {
+ public String name;
+ public long weight;
+
+
+ public Tag(String name, long weight) {
+ this.name = name;
+ this.weight = weight;
+ }
+}
diff --git a/src/main/java/net/simon987/musicgraph/io/ICoverArtDatabase.java b/src/main/java/net/simon987/musicgraph/io/ICoverArtDatabase.java
new file mode 100644
index 0000000..2347444
--- /dev/null
+++ b/src/main/java/net/simon987/musicgraph/io/ICoverArtDatabase.java
@@ -0,0 +1,11 @@
+package net.simon987.musicgraph.io;
+
+public interface ICoverArtDatabase {
+
+ /**
+ * @param mbid MusicBrainz id
+ * @return null if not found
+ * @throws Exception if unexpected error
+ */
+ byte[] getCover(String mbid) throws Exception;
+}
diff --git a/src/main/java/net/simon987/musicgraph/io/MusicDatabase.java b/src/main/java/net/simon987/musicgraph/io/MusicDatabase.java
new file mode 100644
index 0000000..dc056b5
--- /dev/null
+++ b/src/main/java/net/simon987/musicgraph/io/MusicDatabase.java
@@ -0,0 +1,158 @@
+package net.simon987.musicgraph.io;
+
+import com.google.common.collect.ImmutableList;
+import net.simon987.musicgraph.entities.*;
+import net.simon987.musicgraph.logging.LogManager;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.neo4j.driver.v1.*;
+import org.neo4j.driver.v1.types.Node;
+import org.neo4j.driver.v1.types.Relationship;
+
+import javax.inject.Singleton;
+import java.util.*;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+@Singleton
+public class MusicDatabase extends AbstractBinder {
+
+ private Driver driver;
+
+ private static Logger logger = LogManager.getLogger();
+
+ @Override
+ protected void configure() {
+ bind(this).to(MusicDatabase.class);
+ }
+
+ public MusicDatabase() {
+ driver = GraphDatabase.driver("bolt://localhost:7687",
+ AuthTokens.basic("neo4j", "neo4j"));
+ }
+
+ private StatementResult query(Session session, String query, Map args) {
+
+ long start = System.nanoTime();
+ StatementResult result = session.run(query, args);
+ long end = System.nanoTime();
+ long took = (end - start) / 1000000;
+
+ logger.info(String.format("Query %s (Took %dms)", query, took));
+
+ return result;
+ }
+
+ public ArtistDetails getArtistDetails(String mbid) {
+
+ Map params = new HashMap<>();
+ params.put("mbid", mbid);
+
+ try (Session session = driver.session()) {
+
+ StatementResult result = query(session,
+ "MATCH (a:Artist {id: $mbid})-[:CREDITED_FOR]->(r:Release)\n" +
+ "WITH collect(r.id) as releases, a\n" +
+ "OPTIONAL MATCH (a)-[r:IS_TAGGED]->(t:Tag)\n" +
+ "RETURN a {name:a.name, releases:releases, tags:collect({weight: r.weight, name: t.name})}\n" +
+ "LIMIT 1",
+ params);
+
+ ArtistDetails details = new ArtistDetails();
+
+ try {
+
+ if (result.hasNext()) {
+ Map map = result.next().get("a").asMap();
+
+ details.name = (String) map.get("name");
+ details.releases.addAll((Collection) map.get("releases"));
+ details.tags.addAll(
+ ((List