package albums
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
)
func ReadReleaseTracks(releaseInfoBody []byte) ([]Track, error) {
var tracks []Track
var tracksResult map[string]interface{}
jsonErr := json.Unmarshal([]byte(releaseInfoBody), &tracksResult)
if jsonErr != nil {
return tracks, jsonErr
}
mediaSlice := reflect.ValueOf(tracksResult["media"])
if mediaSlice.IsValid() {
for i := 0; i < mediaSlice.Len(); i++ {
mediaInfo := mediaSlice.Index(i).Interface().(map[string]interface{})
tracksByMediaInfo := reflect.ValueOf(mediaInfo["tracks"])
for j := 0; j < tracksByMediaInfo.Len(); j++ {
trackInfo := tracksByMediaInfo.Index(j).Interface().(map[string]interface{})
var track Track
track.ID = reflect.ValueOf(trackInfo["id"]).String()
track.Title = reflect.ValueOf(trackInfo["title"]).String()
track.Lenght = int(trackInfo["length"].(float64))
tracks = append(tracks, track)
}
}
}
return tracks, nil
}
func getReleasesFromReleaseGroup(searchAlbumInfo SearchAlbumInfoInterface, releaseGroup ReleaseGroup) ([]Release, error) {
var releases []Release
var releaseResult map[string]interface{}
searchReleaseGroupUrl := fmt.Sprintf("https://musicbrainz.org/ws/2/release-group/%s?fmt=json&inc=releases", releaseGroup.ID)
releaseRequest, releaseRequesterr := http.NewRequest(http.MethodGet, searchReleaseGroupUrl, nil)
if releaseRequesterr != nil {
return releases, releaseRequesterr
}
releaseRequest.Header.Set("User-Agent", "https://github.com/a-castellano/music-manager-musicbrainz-wrapper")
reqReleaseResponse, reqReleaseResponseError := GetReleases(searchAlbumInfo, releaseRequest)
if reqReleaseResponseError != nil {
return releases, reqReleaseResponseError
}
releaseBody, releaseReadErr := ioutil.ReadAll(reqReleaseResponse.Body)
if releaseReadErr != nil {
return releases, releaseReadErr
}
jsonErr := json.Unmarshal([]byte(releaseBody), &releaseResult)
if jsonErr != nil {
return releases, jsonErr
}
releasesSlice := reflect.ValueOf(releaseResult["releases"])
if releasesSlice.IsValid() {
for i := 0; i < releasesSlice.Len(); i++ {
releaseInfo := releasesSlice.Index(i).Interface().(map[string]interface{})
releaseStatus := reflect.ValueOf(releaseInfo["status"]).String()
if releaseStatus == "Official" {
var release Release
release.ID = reflect.ValueOf(releaseInfo["id"]).String()
release.Title = reflect.ValueOf(releaseInfo["title"]).String()
releaseQueryString := fmt.Sprintf("https://musicbrainz.org/ws/2/release/%s?fmt=json&inc=recordings", release.ID)
reqRelease, errReqRelease := http.NewRequest(http.MethodGet, releaseQueryString, nil)
if errReqRelease != nil {
return releases, errReqRelease
}
reqRelease.Header.Set("User-Agent", "https://github.com/a-castellano/music-manager-musicbrainz-wrapper")
releaseInfoRaw, releaseInfoErr := searchAlbumInfo.GetReleaseInfo(reqRelease)
if releaseInfoErr != nil {
return releases, releaseInfoErr
}
releaseInfoBody, releaseInfoReadErr := ioutil.ReadAll(releaseInfoRaw.Body)
if releaseInfoReadErr != nil {
return releases, releaseInfoReadErr
}
releaseTracks, releaseTracksErr := ReadReleaseTracks(releaseInfoBody)
if releaseTracksErr != nil {
return releases, releaseTracksErr
}
release.Tracks = releaseTracks
releases = append(releases, release)
}
}
}
return releases, nil
}
package albums
import (
"net/http"
)
type Track struct {
ID string
Title string
Lenght int
Position int
}
type ReleaseGroup struct {
ID string
Title string
ReleaseYear int
Releases []Release
}
type Release struct {
ID string
Title string
Tracks []Track
}
type SearchAlbumInfoInterface interface {
SearchReleaseGroups(req *http.Request) (*http.Response, error)
GetReleases(req *http.Request) (*http.Response, error)
GetReleaseInfo(req *http.Request) (*http.Response, error)
}
type SearchAlbumInfo struct {
Client http.Client
}
func (s SearchAlbumInfo) SearchReleaseGroups(req *http.Request) (*http.Response, error) {
response, responseError := s.Client.Do(req)
return response, responseError
}
func (s SearchAlbumInfo) GetReleases(req *http.Request) (*http.Response, error) {
response, responseError := s.Client.Do(req)
return response, responseError
}
func (s SearchAlbumInfo) GetReleaseInfo(req *http.Request) (*http.Response, error) {
response, responseError := s.Client.Do(req)
return response, responseError
}
func SearchReleaseGroups(s SearchAlbumInfoInterface, req *http.Request) (*http.Response, error) {
response, responseError := s.SearchReleaseGroups(req)
return response, responseError
}
func GetReleases(s SearchAlbumInfoInterface, req *http.Request) (*http.Response, error) {
response, responseError := s.GetReleases(req)
return response, responseError
}
package albums
import (
"strings"
)
func SearchAlbum(searchAlbumInfo SearchAlbumInfoInterface, album string) (Release, []Release, error) {
var releases []Release
var release Release
var extraReleases []Release
albumString := strings.Replace(album, " ", "%20", -1)
releaseGroup, otherReleaseGroups, releaseGrouperr := getReleaseGroup(searchAlbumInfo, album, albumString)
if releaseGrouperr != nil {
return release, extraReleases, releaseGrouperr
}
releasesFromReleaseGroup, getReleasesErr := getReleasesFromReleaseGroup(searchAlbumInfo, releaseGroup)
releases = releasesFromReleaseGroup
if getReleasesErr != nil {
return release, extraReleases, releaseGrouperr
}
for _, releaseGroup := range otherReleaseGroups {
releasesFromReleaseGroup, getReleasesErr = getReleasesFromReleaseGroup(searchAlbumInfo, releaseGroup)
if getReleasesErr != nil {
return release, extraReleases, releaseGrouperr
}
releases = append(releases, releasesFromReleaseGroup...)
}
release = releases[0]
extraReleases = releases[1:]
return release, extraReleases, nil
}
package albums
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strconv"
"strings"
)
func readSearchedReleaseGroup(body []byte, album string) (ReleaseGroup, []ReleaseGroup, error) {
var mainResult ReleaseGroup
var otherResults []ReleaseGroup
var results map[string]interface{}
albumString := strings.ToLower(album)
jsonErr := json.Unmarshal([]byte(body), &results)
if jsonErr != nil {
return mainResult, otherResults, jsonErr
}
reflectedNumberOfResults := reflect.ValueOf(results["count"])
numberOfResults := int(reflectedNumberOfResults.Interface().(float64))
if numberOfResults == 0 {
return mainResult, otherResults, errors.New("No release group was found.")
} else {
releaseGroupSlice := reflect.ValueOf(results["release-groups"])
for i := 0; i < releaseGroupSlice.Len(); i++ {
candidate := releaseGroupSlice.Index(i).Interface().(map[string]interface{})
candidateTitle := strings.ToLower(candidate["title"].(string))
if albumString == candidateTitle {
score := int(candidate["score"].(float64))
if score == 100 && mainResult.ID == "" { // First ocurrence
mainResult.ID = reflect.ValueOf(candidate["id"]).String()
mainResult.Title = reflect.ValueOf(candidate["title"]).String()
releaseDate := reflect.ValueOf(candidate["first-release-date"]).String()
releaseYear, _ := strconv.Atoi(strings.Split(releaseDate, "-")[0])
mainResult.ReleaseYear = releaseYear
} else {
if score == 100 {
var extraReleaseGroup ReleaseGroup
extraReleaseGroup.ID = reflect.ValueOf(candidate["id"]).String()
extraReleaseGroup.Title = reflect.ValueOf(candidate["title"]).String()
releaseDate := reflect.ValueOf(candidate["first-release-date"]).String()
releaseYear, _ := strconv.Atoi(strings.Split(releaseDate, "-")[0])
extraReleaseGroup.ReleaseYear = releaseYear
otherResults = append(otherResults, extraReleaseGroup)
}
}
}
}
}
return mainResult, otherResults, nil
}
func getReleaseGroup(searchAlbumInfo SearchAlbumInfoInterface, album string, albumString string) (ReleaseGroup, []ReleaseGroup, error) {
var releaseGroup ReleaseGroup
var extraReleaseGroups []ReleaseGroup
searchReleaseGroupUrl := fmt.Sprintf("https://musicbrainz.org/ws/2/release-group/?query=%s&fmt=json", albumString)
reqReleaseGroup, errReqReleaseGroup := http.NewRequest(http.MethodGet, searchReleaseGroupUrl, nil)
if errReqReleaseGroup != nil {
return releaseGroup, extraReleaseGroups, errReqReleaseGroup
}
reqReleaseGroup.Header.Set("User-Agent", "https://github.com/a-castellano/music-manager-musicbrainz-wrapper")
reqReleaseGroupResponse, reqReleaseGroupResponseError := searchAlbumInfo.SearchReleaseGroups(reqReleaseGroup)
if reqReleaseGroupResponseError != nil {
return releaseGroup, extraReleaseGroups, reqReleaseGroupResponseError
}
releaseGroupBody, releaseGroupReadErr := ioutil.ReadAll(reqReleaseGroupResponse.Body)
if releaseGroupReadErr != nil {
return releaseGroup, extraReleaseGroups, releaseGroupReadErr
}
releaseGroup, extraReleaseGroups, releaseGroupErr := readSearchedReleaseGroup(releaseGroupBody, album)
if releaseGroupErr != nil {
return releaseGroup, extraReleaseGroups, releaseGroupErr
}
return releaseGroup, extraReleaseGroups, nil
}
package artists
import (
"encoding/json"
"fmt"
commontypes "github.com/a-castellano/music-manager-common-types/types"
"io/ioutil"
"net/http"
"reflect"
"strconv"
"strings"
)
func calculateRecordType(recordInfo map[string]interface{}) commontypes.RecordType {
var recordType commontypes.RecordType
var secondaryType string = ""
switch reflect.ValueOf(recordInfo["primary-type"]).String() {
case "Album":
recordType = commontypes.FullLength
case "EP":
recordType = commontypes.EP
case "Single":
recordType = commontypes.Single
default:
recordType = commontypes.Other
}
if reflect.ValueOf(recordInfo["secondary-types"]).Len() != 0 {
secondaryType = reflect.ValueOf(recordInfo["secondary-types"]).Index(0).Interface().(string)
switch secondaryType {
case "Compilation":
recordType = commontypes.Compilation
case "Live":
recordType = commontypes.Live
case "Demo":
recordType = commontypes.Demo
}
}
return recordType
}
func obtainRecordInfo(info interface{}) commontypes.Record {
var record commontypes.Record
recordInfo := info.(map[string]interface{})
record.Name = reflect.ValueOf(recordInfo["title"]).String()
record.ID = reflect.ValueOf(recordInfo["id"]).String()
record.URL = fmt.Sprintf("https://musicbrainz.org/release-group/%s", reflect.ValueOf(recordInfo["id"]).String())
record.Year, _ = strconv.Atoi(strings.Split(reflect.ValueOf(recordInfo["first-release-date"]).String(), "-")[0])
record.Type = calculateRecordType(recordInfo)
return record
}
func GetArtistRecords(client http.Client, artistData SearchArtistData) ([]commontypes.Record, error) {
var records []commontypes.Record
var artistInfo map[string]interface{}
url := fmt.Sprintf("https://musicbrainz.org/ws/2/artist/%s?fmt=json&inc=release-groups", artistData.ID)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return records, err
}
req.Header.Set("User-Agent", "https://github.com/a-castellano/music-manager-musicbrainz-wrapper")
res, getErr := client.Do(req)
if getErr != nil {
return records, getErr
}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
return records, readErr
}
jsonErr := json.Unmarshal([]byte(body), &artistInfo)
if jsonErr != nil {
return records, jsonErr
}
releaseGroups := reflect.ValueOf(artistInfo["release-groups"])
for i := 0; i < releaseGroups.Len(); i++ {
releaseInfo := releaseGroups.Index(i).Interface().(map[string]interface{})
record := obtainRecordInfo(releaseInfo)
records = append(records, record)
}
return records, nil
}
package artists
import (
"encoding/json"
"errors"
"fmt"
commontypes "github.com/a-castellano/music-manager-common-types/types"
"io/ioutil"
"net/http"
"reflect"
"strings"
)
type SearchArtistData commontypes.Artist
func processResult(searchResult map[string]interface{}, artist string) (SearchArtistData, []SearchArtistData, error) {
var artistData SearchArtistData
var artistExtraData []SearchArtistData
reflectedNumberOfResults := reflect.ValueOf(searchResult["count"])
numberOfResults := int(reflectedNumberOfResults.Interface().(float64))
if numberOfResults == 0 {
return artistData, artistExtraData, errors.New("No artist was found.")
} else {
artistSlice := reflect.ValueOf(searchResult["artists"])
for i := 0; i < artistSlice.Len(); i++ {
candidate := artistSlice.Index(i).Interface().(map[string]interface{})
if artist == candidate["name"] {
score := int(candidate["score"].(float64))
// change this, it coud be two artists with the same name
if score == 100 {
artistData.Name = reflect.ValueOf(candidate["name"]).String()
artistData.ID = reflect.ValueOf(candidate["id"]).String()
artistData.URL = fmt.Sprintf("https://musicbrainz.org/artist/%s", artistData.ID)
artistData.Country = reflect.ValueOf(candidate["country"]).String()
} else {
var extraArtist SearchArtistData
extraArtist.Name = reflect.ValueOf(candidate["name"]).String()
extraArtist.ID = reflect.ValueOf(candidate["id"]).String()
extraArtist.URL = fmt.Sprintf("https://musicbrainz.org/artist/%s", artistData.ID)
extraArtist.Country = reflect.ValueOf(candidate["country"]).String()
artistExtraData = append(artistExtraData, extraArtist)
}
}
}
}
return artistData, artistExtraData, nil
}
func SearchArtist(client http.Client, artist string) (SearchArtistData, []SearchArtistData, error) {
var artistData SearchArtistData
var artistExtraData []SearchArtistData
var searchResult map[string]interface{}
artistString := strings.Replace(artist, " ", "%20", -1)
url := fmt.Sprintf("https://musicbrainz.org/ws/2/artist/?query=%s&fmt=json", artistString)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return artistData, artistExtraData, err
}
req.Header.Set("User-Agent", "https://github.com/a-castellano/music-manager-musicbrainz-wrapper")
res, getErr := client.Do(req)
if getErr != nil {
return artistData, artistExtraData, getErr
}
body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil {
return artistData, artistExtraData, readErr
}
jsonErr := json.Unmarshal([]byte(body), &searchResult)
if jsonErr != nil {
return artistData, artistExtraData, jsonErr
}
return processResult(searchResult, artist)
}
package jobs
import (
"errors"
"fmt"
"net/http"
commontypes "github.com/a-castellano/music-manager-common-types/types"
"github.com/a-castellano/music-manager-musicbrainz-wrapper/artists"
)
func ProcessJob(data []byte, origin string, client http.Client) (bool, []byte, error) {
receivedJob, decodeJobErr := commontypes.DecodeJob(data)
var job commontypes.Job
var die bool = false
var err error
job.ID = receivedJob.ID
job.Type = receivedJob.Type
job.Status = receivedJob.Status
job.LastOrigin = origin
if decodeJobErr == nil {
// Job has been successfully decoded
switch receivedJob.Type {
case commontypes.ArtistInfoRetrieval:
var retrievalData commontypes.InfoRetrieval
retrievalData, err = commontypes.DecodeInfoRetrieval(receivedJob.Data)
if err == nil {
switch retrievalData.Type {
case commontypes.ArtistName:
data, extraData, errSearchArtist := artists.SearchArtist(client, retrievalData.Artist)
// If there is no artist info job must return empty data, but it is not an error.
if errSearchArtist != nil {
err = errors.New(errors.New("Artist retrieval failed: ").Error() + errSearchArtist.Error())
job.Error = err.Error()
job.Status = false
} else {
artistData := commontypes.Artist{}
artistData.Name = data.Name
artistData.URL = data.URL
artistData.ID = data.ID
artistData.Country = data.Country
artistData.Genre = data.Genre
artistinfo := commontypes.ArtistInfo{}
artistinfo.Data = artistData
for _, extraArtist := range extraData {
var artist commontypes.Artist
artist.Name = extraArtist.Name
artist.URL = extraArtist.URL
artist.ID = extraArtist.ID
artist.Country = extraArtist.Country
artist.Genre = extraArtist.Genre
artistinfo.ExtraData = append(artistinfo.ExtraData, artist)
}
job.Result, _ = commontypes.EncodeArtistInfo(artistinfo)
}
default:
err = errors.New("Music Manager MusicBrainz Wrapper - ArtistInfoRetrieval type should be only ArtistName.")
job.Error = err.Error()
job.Status = false
}
}
case commontypes.RecordInfoRetrieval:
fmt.Println("RecordInfoRetrieval")
case commontypes.Die:
die = true
default:
err = errors.New("Unknown Job Type for this service.")
job.Status = false
}
} else {
err = errors.New("Empty job data received.")
}
processedJob, _ := commontypes.EncodeJob(job)
return die, processedJob, err
}
package queues
import (
"fmt"
config "github.com/a-castellano/music-manager-config-reader/config_reader"
"github.com/a-castellano/music-manager-musicbrainz-wrapper/jobs"
"github.com/streadway/amqp"
"net/http"
"strconv"
)
func StartJobManagement(config config.Config, client http.Client) error {
connection_string := "amqp://" + config.Server.User + ":" + config.Server.Password + "@" + config.Server.Host + ":" + strconv.Itoa(config.Server.Port) + "/"
conn, err := amqp.Dial(connection_string)
if err != nil {
return fmt.Errorf("Failed to stablish connection with RabbitMQ: %w", err)
}
defer conn.Close()
incoming_ch, err := conn.Channel()
defer incoming_ch.Close()
if err != nil {
return fmt.Errorf("Failed to open incoming channel: %w", err)
}
outgoing_ch, err := conn.Channel()
defer outgoing_ch.Close()
if err != nil {
return fmt.Errorf("Failed to open outgoing channel: %w", err)
}
incoming_q, err := incoming_ch.QueueDeclare(
config.Incoming.Name,
true, // Durable
false, // DeleteWhenUnused
false, // Exclusive
false, // NoWait
nil, // arguments
)
if err != nil {
return fmt.Errorf("Failed to declare incoming queue: %w", err)
}
outgoing_q, err := outgoing_ch.QueueDeclare(
config.Outgoing.Name,
true, // Durable
false, // DeleteWhenUnused
false, // Exclusive
false, // NoWait
nil, // arguments
)
if err != nil {
return fmt.Errorf("Failed to declare outgoing queue: %w", err)
}
err = incoming_ch.Qos(
1, // prefetch count
0, // prefetch size
false, // global
)
if err != nil {
return fmt.Errorf("Failed to set incoming QoS: %w", err)
}
jobsToProcess, err := incoming_ch.Consume(
incoming_q.Name,
"", // consumer
false, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
if err != nil {
return fmt.Errorf("Failed to register a consumer: %w", err)
}
processJobs := make(chan bool)
go func() {
for job := range jobsToProcess {
die, jobResult, _ := jobs.ProcessJob(job.Body, config.Origin, client)
if die {
job.Ack(false)
processJobs <- false
return
}
err = outgoing_ch.Publish(
"", // exchange
outgoing_q.Name, // routing key
false, // mandatory
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "text/plain",
Body: jobResult,
})
if err != nil {
fmt.Println(err)
return
}
job.Ack(false)
}
return
}()
<-processJobs
return nil
}