diff --git a/src/Applications/Brain.elm b/src/Applications/Brain.elm index a40cba9d..bfc34ebb 100644 --- a/src/Applications/Brain.elm +++ b/src/Applications/Brain.elm @@ -5,8 +5,10 @@ import Brain.Authentication import Brain.Core exposing (..) import Brain.Ports import Brain.Reply as Reply exposing (Reply(..)) +import Brain.Sources.Processing import Json.Decode import Replying exposing (return) +import Sources.Processing.Encoding @@ -31,12 +33,16 @@ init flags = ( ----------------------------------------- -- Initial model ----------------------------------------- - { authentication = Brain.Authentication.initialModel } + { authentication = Brain.Authentication.initialModel + , sourceProcessing = Brain.Sources.Processing.initialModel + } ----------------------------------------- -- Initial command ----------------------------------------- , Cmd.batch - [ Cmd.map AuthenticationMsg Brain.Authentication.initialCommand ] + [ Cmd.map AuthenticationMsg Brain.Authentication.initialCommand + , Cmd.map SourceProcessingMsg Brain.Sources.Processing.initialCommand + ] ) @@ -70,6 +76,16 @@ update msg model = , msg = sub } + SourceProcessingMsg sub -> + updateChild + { mapCmd = SourceProcessingMsg + , mapModel = \child -> { model | sourceProcessing = child } + , update = Brain.Sources.Processing.update + } + { model = model.sourceProcessing + , msg = sub + } + -- 📣 | Children & Replies @@ -90,14 +106,13 @@ translateReply reply = |> NotifyUI LoadEnclosedUserData data -> - data - |> Alien.broadcast Alien.LoadEnclosedUserData - |> NotifyUI + NotifyUI (Alien.broadcast Alien.LoadEnclosedUserData data) LoadHypaethralUserData data -> - data - |> Alien.broadcast Alien.LoadHypaethralUserData - |> NotifyUI + NotifyUI (Alien.broadcast Alien.LoadHypaethralUserData data) + + ReportSourceProcessingError data -> + NotifyUI (Alien.broadcast Alien.ReportSourceProcessingError data) updateChild = @@ -109,10 +124,17 @@ updateChild = subscriptions : Model -> Sub Msg -subscriptions _ = +subscriptions model = Sub.batch [ Brain.Ports.fromCache translateAlienEvent , Brain.Ports.fromUI translateAlienEvent + + ----------------------------------------- + -- Children + ----------------------------------------- + , Sub.map + SourceProcessingMsg + (Brain.Sources.Processing.subscriptions model.sourceProcessing) ] @@ -125,6 +147,19 @@ translateAlienEvent event = Just Alien.AuthMethod -> AuthenticationMsg (Brain.Authentication.MethodRetrieved event.data) + Just Alien.ProcessSources -> + -- Only proceed to the processing if we got all the necessary data, + -- otherwise report an error in the UI. + case Json.Decode.decodeValue Sources.Processing.Encoding.argumentsDecoder event.data of + Ok arguments -> + SourceProcessingMsg (Brain.Sources.Processing.Process arguments) + + Err error -> + error + |> Json.Decode.errorToString + |> Alien.report Alien.ReportGenericError + |> NotifyUI + Just Alien.SaveEnclosedUserData -> AuthenticationMsg (Brain.Authentication.SaveEnclosedData event.data) diff --git a/src/Applications/Brain/Core.elm b/src/Applications/Brain/Core.elm index ef6bba92..9dcc49ea 100644 --- a/src/Applications/Brain/Core.elm +++ b/src/Applications/Brain/Core.elm @@ -2,6 +2,7 @@ module Brain.Core exposing (Flags, Model, Msg(..)) import Alien import Brain.Authentication +import Brain.Sources.Processing @@ -17,7 +18,9 @@ type alias Flags = type alias Model = - { authentication : Brain.Authentication.Model } + { authentication : Brain.Authentication.Model + , sourceProcessing : Brain.Sources.Processing.Model + } @@ -31,3 +34,4 @@ type Msg -- Children ----------------------------------------- | AuthenticationMsg Brain.Authentication.Msg + | SourceProcessingMsg Brain.Sources.Processing.Msg diff --git a/src/Applications/Brain/Reply.elm b/src/Applications/Brain/Reply.elm index a7149590..17f7fc6c 100644 --- a/src/Applications/Brain/Reply.elm +++ b/src/Applications/Brain/Reply.elm @@ -13,3 +13,4 @@ type Reply | HideLoadingScreen | LoadEnclosedUserData Json.Value | LoadHypaethralUserData Json.Value + | ReportSourceProcessingError Json.Value diff --git a/src/Applications/Brain/Sources/Processing.elm b/src/Applications/Brain/Sources/Processing.elm new file mode 100644 index 00000000..27df3002 --- /dev/null +++ b/src/Applications/Brain/Sources/Processing.elm @@ -0,0 +1,175 @@ +module Brain.Sources.Processing exposing (Model, Msg(..), initialCommand, initialModel, subscriptions, update) + +import Brain.Reply exposing (Reply(..)) +import Http exposing (Error(..)) +import Json.Encode as Encode +import Replying exposing (R3D3) +import Sources exposing (Service, Source) +import Sources.Processing exposing (..) +import Sources.Services as Services +import Time +import Tracks exposing (Track) + + + +-- 🌳 + + +type alias Model = + { currentTime : Time.Posix + , origin : String + , status : Status + } + + +initialModel : Model +initialModel = + { currentTime = Time.millisToPosix 0 + , origin = "ORIGIN_UNKNOWN" + , status = NotProcessing + } + + +initialCommand : Cmd Msg +initialCommand = + Cmd.none + + + +-- 📣 + + +type Msg + = Process Arguments + | NextInLine + ----------------------------------------- + -- Steps + ----------------------------------------- + | PrepareStep Context (Result Http.Error String) + | TreeStep Context (Result Http.Error String) + | TagsStep ContextForTags + ----------------------------------------- + -- Bits & Pieces + ----------------------------------------- + | SetCurrentTime Time.Posix + + +update : Msg -> Model -> R3D3 Model Msg Reply +update msg model = + case msg of + {- If already processing, do nothing. + If there are no sources, do nothing. + If there are sources, start processing the first source. + -} + Process { origin, sources, tracks } -> + ( model + , Cmd.none + , Nothing + ) + + {- If not processing, do nothing. + If there are no sources left, do nothing. + If there are sources left, start processing the next source in line. + -} + NextInLine -> + ( model + , Cmd.none + , Nothing + ) + + ----------------------------------------- + -- PHASE 1 + -- Prepare for processing. + ----------------------------------------- + PrepareStep context (Ok response) -> + ( model + , Cmd.none + , Nothing + ) + + PrepareStep context (Err err) -> + ( model + , Cmd.none + , Just [ reportHttpError context.source err ] + ) + + ----------------------------------------- + -- PHASE 2 + -- Make a file list/tree. + ----------------------------------------- + TreeStep context (Ok response) -> + ( model + , Cmd.none + , Nothing + ) + + TreeStep context (Err err) -> + ( model + , Cmd.none + , Just [ reportHttpError context.source err ] + ) + + ----------------------------------------- + -- PHASE 3 + -- Get the tags for each file in the file list. + ----------------------------------------- + TagsStep tagsContext -> + ( model + , Cmd.none + , Nothing + ) + + ----------------------------------------- + -- BITS & PIECES + ----------------------------------------- + SetCurrentTime time -> + ( { model | currentTime = time } + , Cmd.none + , Nothing + ) + + + +-- 📣 | Common + + +reportHttpError : Source -> Http.Error -> Reply +reportHttpError source err = + reportError { sourceId = source.id, error = translateHttpError source.service err } + + +reportError : { sourceId : String, error : String } -> Reply +reportError { sourceId, error } = + [ ( "sourceId", Encode.string sourceId ) + , ( "error", Encode.string error ) + ] + |> Encode.object + |> ReportSourceProcessingError + + +translateHttpError : Service -> Http.Error -> String +translateHttpError service err = + case err of + NetworkError -> + "Cannot connect to this source" + + Timeout -> + "Source did not respond (timeout)" + + BadUrl _ -> + "Diffuse error, invalid url was used" + + BadStatus _ -> + "Got a faulty response from this source" + + BadBody response -> + Services.parseErrorResponse service response + + + +-- 📰 + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Time.every (60 * 1000) SetCurrentTime diff --git a/src/Library/Alien.elm b/src/Library/Alien.elm index 156f967e..f6640934 100644 --- a/src/Library/Alien.elm +++ b/src/Library/Alien.elm @@ -23,6 +23,7 @@ type Tag | AuthEnclosedData | AuthMethod -- from UI + | ProcessSources | SaveEnclosedUserData | SaveHypaethralUserData | SignIn @@ -31,6 +32,8 @@ type Tag | HideLoadingScreen | LoadEnclosedUserData | LoadHypaethralUserData + | ReportGenericError + | ReportSourceProcessingError @@ -76,6 +79,9 @@ tagToString tag = ----------------------------------------- -- From UI ----------------------------------------- + ProcessSources -> + "PROCESS_SOURCES" + SaveEnclosedUserData -> "SAVE_ENCLOSED_USER_DATA" @@ -100,6 +106,12 @@ tagToString tag = LoadHypaethralUserData -> "LOAD_HYPAETHRAL_USER_DATA" + ReportGenericError -> + "REPORT_GENERIC_ERROR" + + ReportSourceProcessingError -> + "REPORT_SOURCE_PROCESSING_ERROR" + tagFromString : String -> Maybe Tag tagFromString string = @@ -116,6 +128,9 @@ tagFromString string = ----------------------------------------- -- From UI ----------------------------------------- + "PROCESS_SOURCES" -> + Just ProcessSources + "SAVE_ENCLOSED_USER_DATA" -> Just SaveEnclosedUserData @@ -140,5 +155,11 @@ tagFromString string = "LOAD_HYPAETHRAL_USER_DATA" -> Just LoadHypaethralUserData + "REPORT_GENERIC_ERROR" -> + Just ReportGenericError + + "REPORT_SOURCE_PROCESSING_ERROR" -> + Just ReportSourceProcessingError + _ -> Nothing diff --git a/src/Library/Sources/Processing.elm b/src/Library/Sources/Processing.elm index 9ad4389a..5603d5d8 100644 --- a/src/Library/Sources/Processing.elm +++ b/src/Library/Sources/Processing.elm @@ -1,7 +1,24 @@ -module Sources.Processing exposing (HttpMethod(..), Marker(..), PrepationAnswer, TreeAnswer, httpMethod) +module Sources.Processing exposing (Arguments, Context, ContextForTags, HttpMethod(..), Marker(..), PrepationAnswer, Status(..), TreeAnswer, httpMethod) import Http -import Sources exposing (SourceData) +import Sources exposing (Source, SourceData) +import Tracks exposing (Tags, Track) + + + +-- 🌳 + + +type Status + = Processing (List ( Source, List Track )) + | NotProcessing + + +type alias Arguments = + { origin : String + , sources : List Source + , tracks : List Track + } @@ -27,6 +44,28 @@ type alias TreeAnswer marker = +-- CONTEXTS + + +type alias Context = + { filePaths : List String + , origin : String + , preparationMarker : Marker + , source : Source + , treeMarker : Marker + } + + +type alias ContextForTags = + { nextFilePaths : List String + , receivedFilePaths : List String + , receivedTags : List (Maybe Tags) + , sourceId : String + , urlsForTags : List String + } + + + -- HTTP diff --git a/src/Library/Sources/Processing/Encoding.elm b/src/Library/Sources/Processing/Encoding.elm new file mode 100644 index 00000000..c698cf75 --- /dev/null +++ b/src/Library/Sources/Processing/Encoding.elm @@ -0,0 +1,21 @@ +module Sources.Processing.Encoding exposing (argumentsDecoder) + +{-| Encoding. +-} + +import Json.Decode as Decode exposing (Decoder) +import Sources.Encoding as Sources +import Sources.Processing exposing (Arguments) +import Tracks.Encoding as Tracks + + + +-- 🔱 + + +argumentsDecoder : Decoder Arguments +argumentsDecoder = + Decode.map3 Arguments + (Decode.field "origin" Decode.string) + (Decode.field "sources" <| Decode.list Sources.decoder) + (Decode.field "tracks" <| Decode.list Tracks.trackDecoder) diff --git a/src/Library/Tracks/Encoding.elm b/src/Library/Tracks/Encoding.elm new file mode 100644 index 00000000..cfc216eb --- /dev/null +++ b/src/Library/Tracks/Encoding.elm @@ -0,0 +1,97 @@ +module Tracks.Encoding exposing (decodeFavourite, decodeTrack, encodeFavourite, encodeMaybe, encodeTags, encodeTrack, favouriteDecoder, tagsDecoder, trackDecoder) + +import Json.Decode as Decode +import Json.Encode as Encode +import Tracks exposing (..) + + + +-- ENCODE + + +encodeTrack : Track -> Encode.Value +encodeTrack track = + Encode.object + [ ( "id", Encode.string track.id ) + , ( "path", Encode.string track.path ) + , ( "sourceId", Encode.string track.sourceId ) + , ( "tags", encodeTags track.tags ) + ] + + +encodeFavourite : Favourite -> Encode.Value +encodeFavourite fav = + Encode.object + [ ( "artist", Encode.string fav.artist ) + , ( "title", Encode.string fav.title ) + ] + + +encodeTags : Tags -> Encode.Value +encodeTags tags = + Encode.object + [ ( "disc", Encode.int tags.disc ) + , ( "nr", Encode.int tags.nr ) + + -- + , ( "album", Encode.string tags.album ) + , ( "artist", Encode.string tags.artist ) + , ( "title", Encode.string tags.title ) + + -- + , ( "genre", encodeMaybe tags.genre Encode.string ) + , ( "picture", encodeMaybe tags.picture Encode.string ) + , ( "year", encodeMaybe tags.year Encode.int ) + ] + + +encodeMaybe : Maybe a -> (a -> Encode.Value) -> Encode.Value +encodeMaybe maybe encoder = + maybe + |> Maybe.map encoder + |> Maybe.withDefault Encode.null + + + +-- DECODE + + +decodeTrack : Decode.Value -> Maybe Track +decodeTrack = + Decode.decodeValue trackDecoder + >> Result.toMaybe + + +decodeFavourite : Decode.Value -> Maybe Favourite +decodeFavourite = + Decode.decodeValue favouriteDecoder + >> Result.toMaybe + + +trackDecoder : Decode.Decoder Track +trackDecoder = + Decode.map4 Track + (Decode.field "id" Decode.string) + (Decode.field "path" Decode.string) + (Decode.field "sourceId" Decode.string) + (Decode.field "tags" tagsDecoder) + + +tagsDecoder : Decode.Decoder Tags +tagsDecoder = + Decode.map8 Tags + (Decode.field "disc" Decode.int) + (Decode.field "nr" Decode.int) + (Decode.field "album" Decode.string) + (Decode.field "artist" Decode.string) + (Decode.field "title" Decode.string) + (Decode.maybe <| Decode.field "genre" Decode.string) + (Decode.maybe <| Decode.field "picture" Decode.string) + (Decode.maybe <| Decode.field "year" Decode.int) + + +favouriteDecoder : Decode.Decoder Favourite +favouriteDecoder = + Decode.map2 Favourite + (Decode.field "artist" Decode.string) + (Decode.field "title" Decode.string)