A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 347 lines 11 kB view raw
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 }