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 198 lines 6.4 kB view raw
1module Syncing exposing (LocalConfig, RemoteConfig, task) 2 3import Json.Decode as Decode 4import Json.Encode as Json 5import Maybe.Extra as Maybe 6import Task exposing (Task) 7import Task.Extra as Task 8import Time 9import Time.Ext as Time 10import User.Layer as User exposing (..) 11 12 13 14-- 🌳 15 16 17type alias LocalConfig = 18 { localData : HypaethralData 19 , saveLocal : HypaethralBit -> Decode.Value -> Task String () 20 } 21 22 23type alias RemoteConfig = 24 { retrieve : HypaethralBit -> Task String (Maybe Decode.Value) 25 , save : HypaethralBit -> Decode.Value -> Task String () 26 } 27 28 29 30-- 🛠 31 32 33{-| Syncs all hypaethral data. 34 35Returns `Nothing` if the local data is preferred. 36 37🏝 LOCAL 38🛰 REMOTE 39 401. Try to pull remote `modified.json` timestamp 41 a. If newer, continue (#2) 42 b. If same, do nothing 43 c. If older, or not present, prefer local data 🏝 (stop & push) 442. Try to download all remote data 45 a. If any remote data, continue (#3) 46 b. If none, prefer local data 🏝 (stop & push) 473. Decode remote data and compare timestamps 48 a. If newer, use remote data 🛰 49 b. If same, do nothing 50 c. If older, prefer local data 🏝 (stop & push) 51 d. If no timestamps, if local data, prefer local 🏝 (stop & push), otherwise remote 🛰 52 53-} 54task : 55 Task String a 56 -> LocalConfig 57 -> RemoteConfig 58 -> Task String (Maybe HypaethralData) 59task initialTask localConfig remoteConfig = 60 initialTask 61 |> Task.andThen 62 (\_ -> 63 remoteConfig.retrieve ModifiedAt 64 ) 65 |> Task.andThen 66 (\maybeModifiedAt -> 67 let 68 maybeRemoteModifiedAt = 69 Maybe.andThen 70 (Decode.decodeValue Time.decoder >> Result.toMaybe) 71 maybeModifiedAt 72 in 73 case ( maybeRemoteModifiedAt, localConfig.localData.modifiedAt ) of 74 ( Just remoteModifiedAt, Just localModifiedAt ) -> 75 if Time.posixToMillis remoteModifiedAt == Time.posixToMillis localModifiedAt then 76 -- 🏝️ 77 Task.succeed Nothing 78 79 else if Time.posixToMillis remoteModifiedAt > Time.posixToMillis localModifiedAt then 80 -- 🛰️ 81 fetchRemote localConfig remoteConfig 82 83 else 84 -- 🏝️ → 🛰️ 85 pushLocalToRemote localConfig remoteConfig { return = Nothing } 86 87 ( Just _, Nothing ) -> 88 -- 🛰️ 89 fetchRemote localConfig remoteConfig 90 91 ( Nothing, _ ) -> 92 -- 🛰️ 93 fetchRemote localConfig remoteConfig 94 ) 95 96 97fetchRemote : 98 LocalConfig 99 -> RemoteConfig 100 -> Task String (Maybe HypaethralData) 101fetchRemote localConfig remoteConfig = 102 let 103 { localData, saveLocal } = 104 localConfig 105 106 { retrieve } = 107 remoteConfig 108 109 saveLocally data = 110 data 111 |> User.saveHypaethralData saveLocal 112 |> Task.map (\_ -> Just data) 113 114 noLocalData = 115 List.isEmpty localData.sources 116 && List.isEmpty localData.favourites 117 && List.isEmpty localData.playlists 118 in 119 retrieve 120 |> User.retrieveHypaethralData 121 |> Task.andThen 122 (\list -> 123 let 124 remoteHasExistingData = 125 List.any (Tuple.second >> Maybe.isJust) list 126 in 127 if remoteHasExistingData then 128 -- 🛰️ 129 Task.succeed list 130 131 else 132 -- 🏝️ → 🛰️ 133 pushLocalToRemote localConfig remoteConfig { return = list } 134 ) 135 |> Task.andThen 136 (\list -> 137 -- Decode remote 138 list 139 |> List.map (\( a, b ) -> ( hypaethralBitKey a, Maybe.withDefault Json.null b )) 140 |> Json.object 141 |> User.decodeHypaethralData 142 |> Task.fromResult 143 |> Task.mapError Decode.errorToString 144 ) 145 |> Task.andThen 146 (\remoteData -> 147 -- Compare modifiedAt timestamps 148 case ( remoteData.modifiedAt, localData.modifiedAt ) of 149 ( Just remoteModifiedAt, Just localModifiedAt ) -> 150 if Time.posixToMillis remoteModifiedAt == Time.posixToMillis localModifiedAt then 151 -- 🏝️ 152 Task.succeed Nothing 153 154 else if Time.posixToMillis remoteModifiedAt > Time.posixToMillis localModifiedAt then 155 -- 🛰️ 156 saveLocally remoteData 157 158 else 159 -- 🏝️ → 🛰️ 160 pushLocalToRemote localConfig remoteConfig { return = Nothing } 161 162 ( Just _, Nothing ) -> 163 -- 🛰️ 164 saveLocally remoteData 165 166 ( Nothing, Just _ ) -> 167 -- 🏝️ → 🛰️ 168 pushLocalToRemote localConfig remoteConfig { return = Nothing } 169 170 _ -> 171 if noLocalData then 172 -- 🛰️ 173 saveLocally remoteData 174 175 else 176 -- 🏝️ 177 Task.succeed Nothing 178 ) 179 180 181 182-- ㊙️ 183 184 185pushLocalToRemote : LocalConfig -> RemoteConfig -> { return : a } -> Task String a 186pushLocalToRemote localConfig remoteConfig { return } = 187 localConfig.localData 188 |> User.encodedHypaethralDataList 189 |> (case localConfig.localData.modifiedAt of 190 Just localModifiedAt -> 191 (::) ( ModifiedAt, Time.encode localModifiedAt ) 192 193 Nothing -> 194 identity 195 ) 196 |> List.map (\( bit, data ) -> remoteConfig.save bit data) 197 |> Task.sequence 198 |> Task.map (\_ -> return)