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.

Improve IPFS source

+116 -56
+1
CHANGELOG.md
··· 6 6 - Added keyboard shortcuts for play/pause, toggle shuffle, etc. 7 7 See UI section on the about page for more info. 8 8 - Added support for BTFS (an IPFS fork) 9 + - Improved IPFS support as a music source (now uses paths instead of hashes) 9 10 - Improved text readability (contrast, etc.) 10 11 11 12 ## 2.1.2
+2 -3
src/Library/Notifications.elm
··· 104 104 nothing 105 105 106 106 else 107 - slab 108 - Html.pre 109 - [] 107 + chunk 110 108 [ C.bg_black_50 109 + , C.break_all 111 110 , C.rounded 112 111 , C.mb_0 113 112 , C.mt_3
+67 -41
src/Library/Sources/Services/Ipfs.elm
··· 21 21 import String.Ext as String 22 22 import Task 23 23 import Time 24 + import Url 24 25 25 26 26 27 ··· 49 50 properties : List Property 50 51 properties = 51 52 [ { key = "directoryHash" 52 - , label = "Directory object hash / DNSLink domain" 53 + , label = "IPFS Path / DNSLink domain" 53 54 , placeholder = "QmVLDAhCY3X9P2u" 54 55 , password = False 55 56 } ··· 77 78 initialData = 78 79 Dict.fromList 79 80 [ ( "directoryHash", "" ) 80 - , ( "name", defaults.name ) 81 81 , ( "gateway", defaults.gateway ) 82 82 , ( "ipns", defaults.ipns ) 83 83 , ( "local", defaults.local ) 84 + , ( "name", defaults.name ) 84 85 ] 85 86 86 87 ··· 91 92 prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) 92 93 prepare _ srcData _ toMsg = 93 94 let 94 - isDnsLink = 95 - srcData 96 - |> Dict.get "directoryHash" 97 - |> Maybe.map (String.contains ".") 98 - 99 95 domainName = 100 96 srcData 101 97 |> Dict.get "directoryHash" ··· 105 101 |> String.chopEnd "/" 106 102 |> String.chopStart "_dnslink." 107 103 in 108 - case isDnsLink of 109 - Just True -> 110 - (Just << Http.request) 111 - { method = "GET" 112 - , headers = [] 113 - , url = extractGateway srcData ++ "/api/v0/dns?arg=" ++ domainName 114 - , body = Http.emptyBody 115 - , expect = Http.expectString toMsg 116 - , timeout = Nothing 117 - , tracker = Nothing 118 - } 104 + if isDnsLink srcData then 105 + (Just << Http.request) 106 + { method = "GET" 107 + , headers = [] 108 + , url = extractGateway srcData ++ "/api/v0/dns?arg=" ++ domainName 109 + , body = Http.emptyBody 110 + , expect = Http.expectString toMsg 111 + , timeout = Nothing 112 + , tracker = Nothing 113 + } 119 114 120 - _ -> 121 - Nothing 115 + else 116 + Nothing 122 117 123 118 124 119 ··· 149 144 |> boolFromString 150 145 |> (\b -> ifThenElse b "true" "false") 151 146 152 - hash = 147 + root = 148 + rootHash srcData 149 + 150 + path = 153 151 case marker of 154 152 InProgress _ -> 155 153 marker 156 154 |> Marker.takeOne 157 - |> Maybe.withDefault "MISSING_HASH" 155 + |> Maybe.map (\p -> root ++ "/" ++ p) 156 + |> Maybe.withDefault "" 158 157 159 158 _ -> 160 - srcData 161 - |> Dict.get "directoryHash" 162 - |> Maybe.andThen 163 - (\h -> 164 - if String.contains "." h then 165 - Dict.get "directoryHashFromDnsLink" srcData 166 - 167 - else 168 - Just h 169 - ) 170 - |> Maybe.withDefault "MISSING_HASH" 159 + root 171 160 in 172 161 (if resolveWithIpns then 173 162 Http.task 174 163 { method = "GET" 175 164 , headers = [] 176 - , url = gateway ++ "/api/v0/name/resolve?arg=" ++ hash ++ "&local=" ++ resolveLocally ++ "&encoding=json" 165 + , url = gateway ++ "/api/v0/name/resolve?arg=" ++ encodedPath path ++ "&local=" ++ resolveLocally ++ "&encoding=json" 177 166 , body = Http.emptyBody 178 167 , resolver = Http.stringResolver ipnsResolver 179 168 , timeout = Just (60 * 15 * 1000) 180 169 } 181 170 182 171 else 183 - Task.succeed { ipfsHash = hash } 172 + Task.succeed { ipfsPath = path } 184 173 ) 185 174 |> Task.andThen 186 - (\{ ipfsHash } -> 175 + (\{ ipfsPath } -> 187 176 Http.task 188 177 { method = "GET" 189 178 , headers = [] 190 - , url = gateway ++ "/api/v0/ls?arg=" ++ ipfsHash ++ "&encoding=json" 179 + , url = gateway ++ "/api/v0/ls?arg=" ++ encodedPath ipfsPath ++ "&encoding=json" 191 180 , body = Http.emptyBody 192 181 , resolver = Http.stringResolver Common.translateHttpResponse 193 182 , timeout = Just (60 * 15 * 1000) ··· 196 185 |> Task.attempt resultMsg 197 186 198 187 199 - ipnsResolver : Http.Response String -> Result Http.Error { ipfsHash : String } 188 + ipnsResolver : Http.Response String -> Result Http.Error { ipfsPath : String } 200 189 ipnsResolver response = 201 190 case response of 202 191 Http.BadUrl_ u -> ··· 214 203 Http.GoodStatus_ _ body -> 215 204 body 216 205 |> Json.decodeString (Json.field "Path" Json.string) 217 - |> Result.map (\hash -> { ipfsHash = hash }) 206 + |> Result.map (\path -> { ipfsPath = String.chopStart "/ipfs/" path }) 218 207 |> Result.mapError (Json.errorToString >> Http.BadBody) 219 208 220 209 ··· 259 248 260 249 -} 261 250 makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 262 - makeTrackUrl _ srcData _ hash = 263 - extractGateway srcData ++ "/ipfs/" ++ hash 251 + makeTrackUrl _ srcData _ path = 252 + extractGateway srcData ++ "/ipfs/" ++ rootHash srcData ++ "/" ++ encodedPath path 264 253 265 254 266 255 267 256 -- ⚗️ 257 + 258 + 259 + encodedPath : String -> String 260 + encodedPath path = 261 + path 262 + |> String.split "/" 263 + |> List.map Url.percentEncode 264 + |> String.join "/" 268 265 269 266 270 267 extractGateway : SourceData -> String ··· 283 280 ) 284 281 |> Maybe.map (String.chopEnd "/") 285 282 |> Maybe.withDefault defaultGateway 283 + 284 + 285 + isDnsLink : SourceData -> Bool 286 + isDnsLink srcData = 287 + srcData 288 + |> Dict.get "directoryHash" 289 + |> Maybe.map pathIsDnsLink 290 + |> Maybe.withDefault False 291 + 292 + 293 + pathIsDnsLink : String -> Bool 294 + pathIsDnsLink = 295 + String.contains "." 296 + 297 + 298 + rootHash : SourceData -> String 299 + rootHash srcData = 300 + srcData 301 + |> Dict.get "directoryHash" 302 + |> Maybe.andThen 303 + (\path -> 304 + if pathIsDnsLink path then 305 + Dict.get "directoryHashFromDnsLink" srcData 306 + 307 + else 308 + Just path 309 + ) 310 + |> Maybe.withDefault "" 311 + |> String.chopEnd "/"
+33 -4
src/Library/Sources/Services/Ipfs/Parser.elm
··· 16 16 parseDnsLookup : String -> SourceData -> Marker -> PrepationAnswer Marker 17 17 parseDnsLookup response srcData _ = 18 18 case decodeString dnsResultDecoder response of 19 - Ok hash -> 19 + Ok path -> 20 20 srcData 21 - |> Dict.insert "directoryHashFromDnsLink" hash 21 + |> Dict.insert "directoryHashFromDnsLink" (String.chopStart "/ipfs/" path) 22 22 |> (\s -> { sourceData = s, marker = TheEnd }) 23 23 24 24 Err _ -> ··· 53 53 parseTreeResponse : String -> Marker -> TreeAnswer Marker 54 54 parseTreeResponse response previousMarker = 55 55 let 56 + prefix = 57 + case previousMarker of 58 + TheBeginning -> 59 + "" 60 + 61 + _ -> 62 + response 63 + |> decodeString prefixDecoder 64 + |> Result.map 65 + (String.chopStart "/ipfs/" 66 + >> String.split "/" 67 + >> List.drop 1 68 + >> String.join "/" 69 + ) 70 + |> Result.map 71 + (\s -> 72 + if String.isEmpty s then 73 + "" 74 + 75 + else 76 + s ++ "/" 77 + ) 78 + |> Result.withDefault "" 79 + 56 80 links = 57 81 case decodeString treeDecoder response of 58 82 Ok l -> ··· 64 88 dirs = 65 89 links 66 90 |> List.filter (.typ >> (==) 1) 67 - |> List.map .hash 91 + |> List.map (\l -> prefix ++ l.name) 68 92 69 93 files = 70 94 links 71 95 |> List.filter (.typ >> (==) 2) 72 96 |> List.filter (.name >> isMusicFile) 73 - |> List.map .hash 97 + |> List.map (\l -> prefix ++ l.name) 74 98 in 75 99 { filePaths = 76 100 files ··· 79 103 |> Marker.removeOne 80 104 |> Marker.concat dirs 81 105 } 106 + 107 + 108 + prefixDecoder : Decoder String 109 + prefixDecoder = 110 + field "Objects" <| index 0 <| field "Hash" <| string 82 111 83 112 84 113 treeDecoder : Decoder (List Link)
+13 -8
src/Library/String/Ext.elm
··· 1 - module String.Ext exposing (chopEnd, chopStart, prepend) 1 + module String.Ext exposing (addSuffix, chopEnd, chopStart) 2 + 3 + {-| Flipped version of `append`. 4 + -} 2 5 3 6 -- 🔱 4 7 5 8 9 + addSuffix : String -> String -> String 10 + addSuffix a b = 11 + String.append b a 12 + 13 + 14 + {-| Chop something from the end of a string until it's not there anymore. 15 + -} 6 16 chopEnd : String -> String -> String 7 17 chopEnd needle str = 8 18 if String.endsWith needle str then ··· 14 24 str 15 25 16 26 27 + {-| Chop something from the beginning of a string until it's not there anymore. 28 + -} 17 29 chopStart : String -> String -> String 18 30 chopStart needle str = 19 31 if String.startsWith needle str then ··· 23 35 24 36 else 25 37 str 26 - 27 - 28 - {-| Flipped version of `append`. 29 - -} 30 - prepend : String -> String -> String 31 - prepend a b = 32 - String.append b a