A music player that connects to your cloud/distributed storage.
1module Brain.Tracks.State exposing (..)
2
3import Alien
4import Brain.Common.State as Common
5import Brain.Ports as Ports
6import Brain.Types exposing (..)
7import Brain.User.State as User
8import Dict
9import Dict.Ext as Dict
10import Json.Decode as Json exposing (Decoder)
11import Json.Encode
12import List.Extra as List
13import Queue
14import Return exposing (andThen, return)
15import Return.Ext as Return
16import Sources exposing (Source)
17import Sources.Processing exposing (ContextForTagsSync, HttpMethod(..), TagUrls)
18import Sources.Services
19import Time
20import Tracks exposing (Track)
21import Tracks.Encoding
22
23
24
25-- 🔱
26
27
28add : List Track -> Manager
29add list model =
30 case list of
31 [] ->
32 Return.singleton model
33
34 tracks ->
35 model
36 |> User.saveTracksAndUpdateSearchIndex
37 (List.append model.hypaethralUserData.tracks tracks)
38 |> andThen
39 (tracks
40 |> Json.Encode.list Tracks.Encoding.encodeTrack
41 |> Common.giveUI Alien.AddTracks
42 )
43
44
45download : Json.Value -> Manager
46download json model =
47 let
48 { prefixTrackNumber, trackIds, zipName } =
49 json
50 |> Json.decodeValue downloadParamsDecoder
51 |> Result.withDefault
52 { prefixTrackNumber = False
53 , trackIds = []
54 , zipName = "failed-to-decode-json"
55 }
56 in
57 model.hypaethralUserData.tracks
58 |> Tracks.pick trackIds
59 |> List.indexedMap Tuple.pair
60 |> Json.Encode.list
61 (\( idx, track ) ->
62 Json.Encode.object
63 [ ( "filename"
64 , [ if prefixTrackNumber then
65 (idx + 1)
66 |> String.fromInt
67 |> String.padLeft 2 '0'
68 |> (\s -> s ++ " - ")
69
70 else
71 ""
72 , track.tags.artist
73 |> Maybe.map (\a -> a ++ " - ")
74 |> Maybe.withDefault ""
75 , track.tags.title
76 ]
77 |> String.concat
78 |> Json.Encode.string
79 )
80
81 --
82 , ( "path"
83 , Json.Encode.string track.path
84 )
85 , ( "url"
86 , track
87 |> Queue.makeTrackUrl
88 model.currentTime
89 model.hypaethralUserData.sources
90 |> Json.Encode.string
91 )
92 ]
93 )
94 |> (\encodedTracks ->
95 Json.Encode.object
96 [ ( "name", Json.Encode.string zipName )
97 , ( "tracks", encodedTracks )
98 ]
99 )
100 |> Ports.downloadTracks
101 |> return model
102
103
104gotSearchResults : List String -> Manager
105gotSearchResults results =
106 Common.giveUI Alien.SearchTracks (Json.Encode.list Json.Encode.string results)
107
108
109makeArtworkTrackUrls : Json.Value -> Manager
110makeArtworkTrackUrls json model =
111 json
112 |> Json.decodeValue
113 (Json.dict Json.string)
114 |> Result.map
115 (\dict ->
116 let
117 maybeSource =
118 Maybe.andThen
119 (\trackSourceId ->
120 List.find
121 (.id >> (==) trackSourceId)
122 model.hypaethralUserData.sources
123 )
124 (Dict.get "trackSourceId" dict)
125
126 trackPath =
127 Dict.fetch "trackPath" "" dict
128
129 mkTrackUrl =
130 makeTrackUrl model.currentTime trackPath maybeSource
131 in
132 dict
133 |> Dict.insert "trackGetUrl" (mkTrackUrl Get)
134 |> Dict.insert "trackHeadUrl" (mkTrackUrl Head)
135 |> Json.Encode.dict identity Json.Encode.string
136 |> Ports.provideArtworkTrackUrls
137 |> return model
138 )
139 |> Result.withDefault
140 (Return.singleton model)
141
142
143removeByPaths : { sourceId : String, paths : List String } -> Manager
144removeByPaths args model =
145 User.saveTracksAndUpdateSearchIndex
146 (model.hypaethralUserData.tracks
147 |> Tracks.removeByPaths args
148 |> .kept
149 )
150 model
151
152
153removeBySourceId : Json.Value -> Manager
154removeBySourceId data model =
155 case Json.decodeValue Json.string data of
156 Ok sourceId ->
157 User.saveTracksAndUpdateSearchIndex
158 (model.hypaethralUserData.tracks
159 |> Tracks.removeBySourceId sourceId
160 |> .kept
161 )
162 model
163
164 Err _ ->
165 Return.singleton model
166
167
168removeFromCache : Json.Value -> Manager
169removeFromCache data =
170 Return.communicate (Ports.removeTracksFromCache data)
171
172
173replaceTags : ContextForTagsSync -> Manager
174replaceTags context model =
175 model.hypaethralUserData.tracks
176 |> List.foldr
177 (\track ( acc, trackIds, tags ) ->
178 case List.elemIndex track.id trackIds of
179 Just idx ->
180 let
181 newTags =
182 tags
183 |> List.getAt idx
184 |> Maybe.andThen identity
185 |> Maybe.withDefault track.tags
186 in
187 ( { track | tags = newTags } :: acc
188 , List.removeAt idx trackIds
189 , List.removeAt idx tags
190 )
191
192 Nothing ->
193 ( track :: acc
194 , trackIds
195 , tags
196 )
197 )
198 ( []
199 , context.trackIds
200 , context.receivedTags
201 )
202 |> (\( a, _, _ ) ->
203 User.saveTracksAndUpdateSearchIndex a model
204 )
205 |> andThen
206 (\m ->
207 m.hypaethralUserData.tracks
208 |> Json.Encode.list Tracks.Encoding.encodeTrack
209 |> (\data -> Common.giveUI Alien.ReloadTracks data m)
210 )
211
212
213search : Json.Value -> Manager
214search encodedSearchTerm =
215 encodedSearchTerm
216 |> Json.decodeValue Json.string
217 |> Result.map Ports.requestSearch
218 |> Result.withDefault Cmd.none
219 |> Return.communicate
220
221
222storeInCache : Json.Value -> Manager
223storeInCache data =
224 Return.communicate (Ports.storeTracksInCache data)
225
226
227syncTrackTags : Json.Value -> Manager
228syncTrackTags data model =
229 let
230 result =
231 Json.decodeValue
232 (Json.list <|
233 Json.map3
234 (\path sourceId trackId ->
235 { path = path
236 , sourceId = sourceId
237 , trackId = trackId
238 }
239 )
240 (Json.field "path" Json.string)
241 (Json.field "sourceId" Json.string)
242 (Json.field "trackId" Json.string)
243 )
244 data
245
246 ( sources, _ ) =
247 result
248 |> Result.withDefault []
249 |> List.foldl
250 (\{ sourceId } ( dict, acc ) ->
251 if List.member sourceId acc then
252 ( dict, acc )
253
254 else
255 case List.find (.id >> (==) sourceId) model.hypaethralUserData.sources of
256 Just source ->
257 ( Dict.insert sourceId source dict, sourceId :: acc )
258
259 Nothing ->
260 ( dict, sourceId :: acc )
261 )
262 ( Dict.empty, [] )
263 in
264 case result of
265 Ok list ->
266 list
267 |> List.foldr
268 (\{ path, sourceId, trackId } ( accPaths, accUrls, accIds ) ->
269 sources
270 |> Dict.get sourceId
271 |> Maybe.map
272 (tagUrls model.currentTime path)
273 |> Maybe.map
274 (\urls ->
275 ( path :: accPaths, urls :: accUrls, trackId :: accIds )
276 )
277 |> Maybe.withDefault
278 ( accPaths, accUrls, accIds )
279 )
280 ( [], [], [] )
281 |> (\( accPaths, accUrls, accIds ) ->
282 Ports.syncTags
283 { receivedFilePaths = accPaths
284 , receivedTags = []
285 , trackIds = accIds
286 , urlsForTags = accUrls
287 }
288 )
289 |> return model
290
291 Err _ ->
292 Return.singleton model
293
294
295updateSearchIndex : Json.Value -> Manager
296updateSearchIndex data =
297 Return.communicate (Ports.updateSearchIndex data)
298
299
300
301-- ⚗️
302
303
304downloadParamsDecoder :
305 Decoder
306 { prefixTrackNumber : Bool
307 , trackIds : List String
308 , zipName : String
309 }
310downloadParamsDecoder =
311 Json.map3
312 (\a b c ->
313 { prefixTrackNumber = a
314 , trackIds = b
315 , zipName = c
316 }
317 )
318 (Json.field "prefixTrackNumber" <| Json.bool)
319 (Json.field "trackIds" <| Json.list Json.string)
320 (Json.field "zipName" <| Json.string)
321
322
323makeTrackUrl : Time.Posix -> String -> Maybe Source -> HttpMethod -> String
324makeTrackUrl timestamp trackPath maybeSource httpMethod =
325 case maybeSource of
326 Just source ->
327 Sources.Services.makeTrackUrl
328 source.service
329 timestamp
330 source.id
331 source.data
332 httpMethod
333 trackPath
334
335 Nothing ->
336 "<missing-source>"
337
338
339tagUrls : Time.Posix -> String -> Source -> TagUrls
340tagUrls currentTime path source =
341 let
342 maker =
343 Sources.Services.makeTrackUrl source.service currentTime source.id source.data
344 in
345 { getUrl = maker Get path
346 , headUrl = maker Head path
347 }