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.

Add Azure services

+1262 -25
+2 -1
elm.json
··· 33 33 "icidasset/elm-sha": "2.0.2", 34 34 "justgage/tachyons-elm": "4.1.3", 35 35 "mpizenberg/elm-pointer-events": "4.0.0", 36 + "newlandsvalley/elm-binary-base64": "1.0.3", 36 37 "noahzgordon/elm-color-extra": "1.0.2", 37 38 "rtfeldman/elm-css": "16.0.0", 38 39 "rtfeldman/elm-hex": "1.0.0", ··· 50 51 "direct": {}, 51 52 "indirect": {} 52 53 } 53 - } 54 + }
+242
src/Library/Binary/Ext.elm
··· 1 + module Binary.Ext exposing (fromBase64, intToBase64, toBase64) 2 + 3 + import Binary exposing (Bits) 4 + 5 + 6 + 7 + -- BASE64 8 + 9 + 10 + fromBase64 : String -> Bits 11 + fromBase64 encoded = 12 + Binary.empty 13 + 14 + 15 + {-| To Base64. 16 + 17 + >>> import Binary 18 + 19 + >>> toBase64 (Binary.fromStringAsUtf8 "") 20 + "" 21 + 22 + >>> toBase64 (Binary.fromStringAsUtf8 "f") 23 + "Zg==" 24 + 25 + >>> toBase64 (Binary.fromStringAsUtf8 "fo") 26 + "Zm8=" 27 + 28 + >>> toBase64 (Binary.fromStringAsUtf8 "foo") 29 + "Zm9v" 30 + 31 + >>> toBase64 (Binary.fromStringAsUtf8 "foob") 32 + "Zm9vYg==" 33 + 34 + >>> toBase64 (Binary.fromStringAsUtf8 "fooba") 35 + "Zm9vYmE=" 36 + 37 + >>> toBase64 (Binary.fromStringAsUtf8 "foobar") 38 + "Zm9vYmFy" 39 + 40 + -} 41 + toBase64 : Bits -> String 42 + toBase64 bits = 43 + bits 44 + |> Binary.chunksOf 6 45 + |> List.map (Binary.toDecimal >> intToBase64) 46 + |> String.fromList 47 + 48 + 49 + intToBase64 : Int -> Char 50 + intToBase64 i = 51 + case i of 52 + 0 -> 53 + 'A' 54 + 55 + 1 -> 56 + 'B' 57 + 58 + 2 -> 59 + 'C' 60 + 61 + 3 -> 62 + 'D' 63 + 64 + 4 -> 65 + 'E' 66 + 67 + 5 -> 68 + 'F' 69 + 70 + 6 -> 71 + 'G' 72 + 73 + 7 -> 74 + 'H' 75 + 76 + 8 -> 77 + 'I' 78 + 79 + 9 -> 80 + 'J' 81 + 82 + 10 -> 83 + 'K' 84 + 85 + 11 -> 86 + 'L' 87 + 88 + 12 -> 89 + 'M' 90 + 91 + 13 -> 92 + 'N' 93 + 94 + 14 -> 95 + 'O' 96 + 97 + 15 -> 98 + 'P' 99 + 100 + 16 -> 101 + 'Q' 102 + 103 + 17 -> 104 + 'R' 105 + 106 + 18 -> 107 + 'S' 108 + 109 + 19 -> 110 + 'T' 111 + 112 + 20 -> 113 + 'U' 114 + 115 + 21 -> 116 + 'V' 117 + 118 + 22 -> 119 + 'W' 120 + 121 + 23 -> 122 + 'X' 123 + 124 + 24 -> 125 + 'Y' 126 + 127 + 25 -> 128 + 'Z' 129 + 130 + 26 -> 131 + 'a' 132 + 133 + 27 -> 134 + 'b' 135 + 136 + 28 -> 137 + 'c' 138 + 139 + 29 -> 140 + 'd' 141 + 142 + 30 -> 143 + 'e' 144 + 145 + 31 -> 146 + 'f' 147 + 148 + 32 -> 149 + 'g' 150 + 151 + 33 -> 152 + 'h' 153 + 154 + 34 -> 155 + 'i' 156 + 157 + 35 -> 158 + 'j' 159 + 160 + 36 -> 161 + 'k' 162 + 163 + 37 -> 164 + 'l' 165 + 166 + 38 -> 167 + 'm' 168 + 169 + 39 -> 170 + 'n' 171 + 172 + 40 -> 173 + 'o' 174 + 175 + 41 -> 176 + 'p' 177 + 178 + 42 -> 179 + 'q' 180 + 181 + 43 -> 182 + 'r' 183 + 184 + 44 -> 185 + 's' 186 + 187 + 45 -> 188 + 't' 189 + 190 + 46 -> 191 + 'u' 192 + 193 + 47 -> 194 + 'v' 195 + 196 + 48 -> 197 + 'w' 198 + 199 + 49 -> 200 + 'x' 201 + 202 + 50 -> 203 + 'y' 204 + 205 + 51 -> 206 + 'z' 207 + 208 + 52 -> 209 + '0' 210 + 211 + 53 -> 212 + '1' 213 + 214 + 54 -> 215 + '2' 216 + 217 + 55 -> 218 + '3' 219 + 220 + 56 -> 221 + '4' 222 + 223 + 57 -> 224 + '5' 225 + 226 + 58 -> 227 + '6' 228 + 229 + 59 -> 230 + '7' 231 + 232 + 60 -> 233 + '8' 234 + 235 + 61 -> 236 + '9' 237 + 238 + 62 -> 239 + '+' 240 + 241 + _ -> 242 + '/'
+8 -1
src/Library/Common.elm
··· 1 - module Common exposing (Switch(..), urlOrigin) 1 + module Common exposing (Switch(..), queryString, urlOrigin) 2 2 3 + import Tuple.Ext as Tuple 3 4 import Url exposing (Protocol(..), Url) 5 + import Url.Builder as Url 4 6 5 7 6 8 ··· 14 16 15 17 16 18 -- 🔱 19 + 20 + 21 + queryString : List ( String, String ) -> String 22 + queryString = 23 + List.map (Tuple.uncurry Url.string) >> Url.toQuery 17 24 18 25 19 26 urlOrigin : Url -> String
+26 -5
src/Library/Cryptography/Hmac.elm
··· 34 34 ..> |> String.toLower 35 35 "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" 36 36 37 + >>> Binary.fromHex "4a656665" 38 + ..> |> encrypt64 SHA.sha256 "what do ya want for nothing?" 39 + ..> |> Binary.toHex 40 + ..> |> String.toLower 41 + "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" 42 + 43 + >>> Binary.fromHex "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 44 + ..> |> encrypt64 SHA.sha256 "Test Using Larger Than Block-Size Key - Hash Key First" 45 + ..> |> Binary.toHex 46 + ..> |> String.toLower 47 + "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54" 48 + 37 49 -} 38 50 encrypt64 : HashFunction -> String -> Bits -> Bits 39 51 encrypt64 = ··· 60 72 61 73 keyWithBlockSize = 62 74 if keySize > blockSize then 63 - hash key 75 + padRight blockSize (hash key) 64 76 65 77 else if keySize < blockSize then 66 - False 67 - |> List.repeat (blockSize - keySize) 68 - |> List.append (Binary.toBooleans key) 69 - |> Binary.fromBooleans 78 + padRight blockSize key 70 79 71 80 else 72 81 key ··· 83 92 |> hash 84 93 |> Binary.append binSeqTwo 85 94 |> hash 95 + 96 + 97 + padRight : Int -> Bits -> Bits 98 + padRight int bits = 99 + let 100 + size = 101 + Binary.width bits 102 + in 103 + False 104 + |> List.repeat (int - size) 105 + |> List.append (Binary.toBooleans bits) 106 + |> Binary.fromBooleans 86 107 87 108 88 109
+2
src/Library/Sources.elm
··· 40 40 41 41 type Service 42 42 = AmazonS3 43 + | AzureBlob 44 + | AzureFile 43 45 | Dropbox 44 46 | Google 45 47 | Ipfs
+70
src/Library/Sources/Services.elm
··· 7 7 import Sources exposing (..) 8 8 import Sources.Processing exposing (..) 9 9 import Sources.Services.AmazonS3 as AmazonS3 10 + import Sources.Services.AzureBlob as AzureBlob 11 + import Sources.Services.AzureFile as AzureFile 10 12 import Sources.Services.Dropbox as Dropbox 11 13 import Sources.Services.Google as Google 12 14 import Sources.Services.Ipfs as Ipfs ··· 23 25 AmazonS3 -> 24 26 AmazonS3.initialData 25 27 28 + AzureBlob -> 29 + AzureBlob.initialData 30 + 31 + AzureFile -> 32 + AzureFile.initialData 33 + 26 34 Dropbox -> 27 35 Dropbox.initialData 28 36 ··· 39 47 AmazonS3 -> 40 48 AmazonS3.makeTrackUrl 41 49 50 + AzureBlob -> 51 + AzureBlob.makeTrackUrl 52 + 53 + AzureFile -> 54 + AzureFile.makeTrackUrl 55 + 42 56 Dropbox -> 43 57 Dropbox.makeTrackUrl 44 58 ··· 61 75 AmazonS3 -> 62 76 AmazonS3.makeTree 63 77 78 + AzureBlob -> 79 + AzureBlob.makeTree 80 + 81 + AzureFile -> 82 + AzureFile.makeTree 83 + 64 84 Dropbox -> 65 85 Dropbox.makeTree 66 86 ··· 77 97 AmazonS3 -> 78 98 AmazonS3.parseErrorResponse 79 99 100 + AzureBlob -> 101 + AzureBlob.parseErrorResponse 102 + 103 + AzureFile -> 104 + AzureFile.parseErrorResponse 105 + 80 106 Dropbox -> 81 107 Dropbox.parseErrorResponse 82 108 ··· 93 119 AmazonS3 -> 94 120 AmazonS3.parsePreparationResponse 95 121 122 + AzureBlob -> 123 + AzureBlob.parsePreparationResponse 124 + 125 + AzureFile -> 126 + AzureFile.parsePreparationResponse 127 + 96 128 Dropbox -> 97 129 Dropbox.parsePreparationResponse 98 130 ··· 109 141 AmazonS3 -> 110 142 AmazonS3.parseTreeResponse 111 143 144 + AzureBlob -> 145 + AzureBlob.parseTreeResponse 146 + 147 + AzureFile -> 148 + AzureFile.parseTreeResponse 149 + 112 150 Dropbox -> 113 151 Dropbox.parseTreeResponse 114 152 ··· 124 162 case service of 125 163 AmazonS3 -> 126 164 AmazonS3.postProcessTree 165 + 166 + AzureBlob -> 167 + AzureBlob.postProcessTree 168 + 169 + AzureFile -> 170 + AzureFile.postProcessTree 127 171 128 172 Dropbox -> 129 173 Dropbox.postProcessTree ··· 147 191 AmazonS3 -> 148 192 AmazonS3.prepare 149 193 194 + AzureBlob -> 195 + AzureBlob.prepare 196 + 197 + AzureFile -> 198 + AzureFile.prepare 199 + 150 200 Dropbox -> 151 201 Dropbox.prepare 152 202 ··· 163 213 AmazonS3 -> 164 214 AmazonS3.properties 165 215 216 + AzureBlob -> 217 + AzureBlob.properties 218 + 219 + AzureFile -> 220 + AzureFile.properties 221 + 166 222 Dropbox -> 167 223 Dropbox.properties 168 224 ··· 182 238 case str of 183 239 "AmazonS3" -> 184 240 Just AmazonS3 241 + 242 + "AzureBlob" -> 243 + Just AzureBlob 244 + 245 + "AzureFile" -> 246 + Just AzureFile 185 247 186 248 "Dropbox" -> 187 249 Just Dropbox ··· 202 264 AmazonS3 -> 203 265 "AmazonS3" 204 266 267 + AzureBlob -> 268 + "AzureBlob" 269 + 270 + AzureFile -> 271 + "AzureFile" 272 + 205 273 Dropbox -> 206 274 "Dropbox" 207 275 ··· 218 286 labels : List ( String, String ) 219 287 labels = 220 288 [ ( typeToKey AmazonS3, "Amazon S3" ) 289 + , ( typeToKey AzureBlob, "Azure Blob Storage" ) 290 + , ( typeToKey AzureFile, "Azure File Storage" ) 221 291 , ( typeToKey Dropbox, "Dropbox" ) 222 292 , ( typeToKey Google, "Google Drive" ) 223 293 , ( typeToKey Ipfs, "IPFS" )
+2 -3
src/Library/Sources/Services/AmazonS3/Presign.elm
··· 1 1 module Sources.Services.AmazonS3.Presign exposing (presignedUrl) 2 2 3 3 import Binary exposing (Bits) 4 + import Common 4 5 import Cryptography.HMAC as HMAC 5 6 import DateFormat as Date 6 7 import Dict ··· 14 15 import String.Ext as String 15 16 import Time 16 17 import Url 17 - import Url.Builder as Url 18 18 19 19 20 20 ··· 127 127 ] 128 128 |> List.append extraParams 129 129 |> List.sortBy Tuple.first 130 - |> List.map (\( a, b ) -> Url.string a b) 131 - |> Url.toQuery 130 + |> Common.queryString 132 131 |> String.dropLeft 1 133 132 |> encodeAdditionalCharacters 134 133
+228
src/Library/Sources/Services/Azure/Authorization.elm
··· 1 + module Sources.Services.Azure.Authorization exposing (Computation(..), SignatureDependencies, StorageMethod(..), makeSignature, presignedUrl) 2 + 3 + {-| Resources: 4 + 5 + - <https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas#account-sas-example> 6 + - <https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-a-Service-SAS?redirectedfrom=MSDN> 7 + 8 + -} 9 + 10 + import Base64 11 + import Binary 12 + import BinaryBase64 13 + import Common 14 + import Cryptography.HMAC as Hmac 15 + import DateFormat as Date 16 + import Dict 17 + import Dict.Ext as Dict 18 + import SHA 19 + import Sources exposing (SourceData) 20 + import Sources.Processing exposing (HttpMethod) 21 + import Sources.Services.Common as Utils 22 + import String.Ext as String 23 + import Time 24 + import Url 25 + 26 + 27 + 28 + -- Types 29 + 30 + 31 + type Computation 32 + = List 33 + | Read 34 + 35 + 36 + type StorageMethod 37 + = Blob 38 + | File 39 + 40 + 41 + 42 + -- Public functions 43 + 44 + 45 + presignedUrl : 46 + StorageMethod 47 + -> Computation 48 + -> HttpMethod 49 + -> Int 50 + -> Time.Posix 51 + -> SourceData 52 + -> String 53 + -> List ( String, String ) 54 + -> String 55 + presignedUrl storageMethod computation httpMethod hoursToLive currentTime srcData pathToFile params = 56 + let 57 + azure = 58 + srcData 59 + 60 + accountName = 61 + Dict.fetchUnknown "accountName" azure 62 + 63 + accountKey = 64 + Dict.fetchUnknown "accountKey" azure 65 + 66 + container = 67 + Dict.fetchUnknown "container" azure 68 + 69 + -- {var} Time (y-MM-ddTHH:mmZ) 70 + expiryTime = 71 + Date.format 72 + [ Date.yearNumber 73 + , Date.text "-" 74 + , Date.monthFixed 75 + , Date.text "-" 76 + , Date.dayOfMonthFixed 77 + , Date.text "T" 78 + , Date.hourMilitaryFixed 79 + , Date.text ":" 80 + , Date.minuteFixed 81 + , Date.text "Z" 82 + ] 83 + Time.utc 84 + (currentTime 85 + |> Time.posixToMillis 86 + |> (+) 3600000 87 + |> Time.millisToPosix 88 + ) 89 + 90 + -- {var} Other 91 + permissions = 92 + case computation of 93 + List -> 94 + "l" 95 + 96 + Read -> 97 + "r" 98 + 99 + resourceType = 100 + case storageMethod of 101 + Blob -> 102 + "blob" 103 + 104 + File -> 105 + "file" 106 + 107 + resType = 108 + case storageMethod of 109 + Blob -> 110 + "container" 111 + 112 + File -> 113 + "directory" 114 + 115 + -- Signature 116 + signatureStuff = 117 + { accountKey = accountKey 118 + , accountName = accountName 119 + , expiryTime = expiryTime 120 + , permissions = permissions 121 + , protocol = "https" 122 + , resources = "co" 123 + , services = "bf" 124 + , startTime = "" 125 + , version = "2017-04-17" 126 + } 127 + in 128 + String.concat 129 + [ "https://" 130 + , Url.percentEncode accountName 131 + , "." 132 + , Url.percentEncode resourceType 133 + , ".core.windows.net/" 134 + , Url.percentEncode container 135 + , "/" 136 + , Url.percentEncode (String.chopStart "/" pathToFile) 137 + 138 + -- Start query params 139 + , case Common.queryString params of 140 + "" -> 141 + "?" 142 + 143 + qs -> 144 + qs 145 + 146 + -- Query params for certain requests 147 + , case computation of 148 + List -> 149 + "&restype=" ++ resType ++ "&comp=list" 150 + 151 + _ -> 152 + "" 153 + 154 + -- Signature things 155 + , "&sv=" 156 + , Url.percentEncode signatureStuff.version 157 + , "&ss=" 158 + , Url.percentEncode signatureStuff.services 159 + , "&srt=" 160 + , Url.percentEncode signatureStuff.resources 161 + , "&sp=" 162 + , Url.percentEncode signatureStuff.permissions 163 + , "&se=" 164 + , Url.percentEncode signatureStuff.expiryTime 165 + , "&spr=" 166 + , Url.percentEncode signatureStuff.protocol 167 + , "&sig=" 168 + , Url.percentEncode (makeSignature signatureStuff) 169 + ] 170 + 171 + 172 + 173 + -- Signature 174 + 175 + 176 + type alias SignatureDependencies = 177 + { accountKey : String 178 + , accountName : String 179 + , expiryTime : String 180 + , permissions : String 181 + , protocol : String 182 + , resources : String 183 + , services : String 184 + , startTime : String 185 + , version : String 186 + } 187 + 188 + 189 + {-| Make a signature. 190 + 191 + >>> makeSignature { accountKey = "93K17Co74T2lDHk2rA+wmb/avIAS6u6lPnZrk2hyT+9+aov82qNhrcXSNGZCzm9mjd4d75/oxxOr6r1JVpgTLA==", accountName = "tsmatsuzsttest0001", expiryTime = "2016-07-08T04:41:20Z", permissions = "rwdlacup", protocol = "https", resources = "sco", services = "bfqt", startTime = "2016-06-29T04:41:20Z", version = "2015-04-05" } 192 + "+XuDjuLE1Sv/FrJTLz8YjsaDukWNTKX7e8G8Ew+5aps=" 193 + 194 + -} 195 + makeSignature : SignatureDependencies -> String 196 + makeSignature { accountKey, accountName, expiryTime, permissions, protocol, resources, services, startTime, version } = 197 + let 198 + message = 199 + -- accountname + "\n" + 200 + -- signedpermissions + "\n" + 201 + -- signedservice + "\n" + 202 + -- signedresourcetype + "\n" + 203 + -- signedstart + "\n" + 204 + -- signedexpiry + "\n" + 205 + -- signedIP + "\n" + 206 + -- signedProtocol + "\n" + 207 + -- signedversion + "\n" 208 + String.join "\n" 209 + [ accountName 210 + , permissions 211 + , services 212 + , resources 213 + , startTime 214 + , expiryTime 215 + , "" 216 + , protocol 217 + , version ++ "\n" 218 + ] 219 + in 220 + accountKey 221 + |> BinaryBase64.decode 222 + |> Result.withDefault [] 223 + |> List.map (Binary.fromDecimal >> Binary.ensureSize 8) 224 + |> Binary.concat 225 + |> Hmac.encrypt64 SHA.sha256 message 226 + |> Binary.chunksOf 8 227 + |> List.map Binary.toDecimal 228 + |> BinaryBase64.encode
+68
src/Library/Sources/Services/Azure/BlobParser.elm
··· 1 + module Sources.Services.Azure.BlobParser exposing (parseErrorResponse, parseTreeResponse) 2 + 3 + import Sources.Processing exposing (Marker(..), TreeAnswer) 4 + import Xml.Decode exposing (..) 5 + 6 + 7 + 8 + -- TREE 9 + 10 + 11 + parseTreeResponse : String -> Marker -> TreeAnswer Marker 12 + parseTreeResponse response _ = 13 + response 14 + |> decodeString 15 + (map2 16 + (\f m -> { filePaths = f, marker = m }) 17 + filePathsDecoder 18 + markerDecoder 19 + ) 20 + |> Result.withDefault { filePaths = [], marker = TheEnd } 21 + 22 + 23 + filePathsDecoder : Decoder (List String) 24 + filePathsDecoder = 25 + string 26 + |> single 27 + |> path [ "Name" ] 28 + |> list 29 + |> path [ "Blobs", "Blob" ] 30 + 31 + 32 + markerDecoder : Decoder Marker 33 + markerDecoder = 34 + map 35 + (\maybeNextMarker -> 36 + case maybeNextMarker of 37 + Just "" -> 38 + TheEnd 39 + 40 + Just nextMarker -> 41 + InProgress nextMarker 42 + 43 + Nothing -> 44 + TheEnd 45 + ) 46 + (maybe <| path [ "NextMarker" ] <| single string) 47 + 48 + 49 + 50 + -- ERROR 51 + 52 + 53 + parseErrorResponse : String -> String 54 + parseErrorResponse response = 55 + response 56 + |> decodeString errorMessagesDecoder 57 + |> Result.toMaybe 58 + |> Maybe.andThen List.head 59 + |> Maybe.withDefault "Invalid request" 60 + 61 + 62 + errorMessagesDecoder : Decoder (List String) 63 + errorMessagesDecoder = 64 + string 65 + |> single 66 + |> path [ "Message" ] 67 + |> list 68 + |> path [ "Error" ]
+154
src/Library/Sources/Services/Azure/FileMarker.elm
··· 1 + module Sources.Services.Azure.FileMarker exposing (MarkerItem(..), concat, itemToString, paramSeparator, prefixer, removeOne, separator, stringToItem, takeOne) 2 + 3 + {-| Custom `Marker` for the Azure File API. 4 + 5 + The Azure File API currently doesn't make a recursive list, 6 + so we have to manage that ourselves. 7 + 8 + This custom marker is a combination of: 9 + 10 + - The default `marker` param, see URI parameters at <https://docs.microsoft.com/en-us/rest/api/storageservices/list-directories-and-files> 11 + - Our custom logic to handle recursive listings 12 + 13 + Example: InProgress "dir=example ¶ param=defaultMarker" 14 + 15 + -} 16 + 17 + import Sources.Processing exposing (Marker(..)) 18 + 19 + 20 + type MarkerItem 21 + = Directory String 22 + | Param { directory : String, marker : String } 23 + 24 + 25 + separator : String 26 + separator = 27 + " ɑ " 28 + 29 + 30 + prefixer : String 31 + prefixer = 32 + " β " 33 + 34 + 35 + paramSeparator : String 36 + paramSeparator = 37 + " ɣ " 38 + 39 + 40 + 41 + -- IN 42 + 43 + 44 + concat : List MarkerItem -> Marker -> Marker 45 + concat list marker = 46 + let 47 + listStringified = 48 + List.map itemToString list 49 + 50 + result = 51 + case marker of 52 + InProgress m -> 53 + [ listStringified, String.split separator m ] 54 + |> List.concat 55 + |> String.join separator 56 + 57 + _ -> 58 + String.join separator listStringified 59 + in 60 + case result of 61 + "" -> 62 + TheEnd 63 + 64 + r -> 65 + InProgress r 66 + 67 + 68 + 69 + -- OUT 70 + 71 + 72 + {-| Take the first item and return it. 73 + -} 74 + takeOne : Marker -> Maybe MarkerItem 75 + takeOne marker = 76 + case marker of 77 + InProgress m -> 78 + m 79 + |> String.split separator 80 + |> List.head 81 + |> Maybe.andThen stringToItem 82 + 83 + _ -> 84 + Nothing 85 + 86 + 87 + {-| Remove the first item if there is one. 88 + -} 89 + removeOne : Marker -> Marker 90 + removeOne marker = 91 + case marker of 92 + InProgress m -> 93 + let 94 + tmp = 95 + m 96 + |> String.split separator 97 + |> List.drop 1 98 + |> String.join separator 99 + in 100 + case tmp of 101 + "" -> 102 + TheEnd 103 + 104 + x -> 105 + InProgress x 106 + 107 + _ -> 108 + TheEnd 109 + 110 + 111 + 112 + -- CONVERSIONS 113 + 114 + 115 + itemToString : MarkerItem -> String 116 + itemToString item = 117 + case item of 118 + Directory d -> 119 + "dir" ++ prefixer ++ d 120 + 121 + Param { directory, marker } -> 122 + "par" ++ prefixer ++ directory ++ paramSeparator ++ marker 123 + 124 + 125 + stringToItem : String -> Maybe MarkerItem 126 + stringToItem string = 127 + let 128 + exploded = 129 + String.split prefixer string 130 + in 131 + case List.head exploded of 132 + Just "dir" -> 133 + exploded 134 + |> List.drop 1 135 + |> String.join prefixer 136 + |> Directory 137 + |> Just 138 + 139 + Just "par" -> 140 + exploded 141 + |> List.drop 1 142 + |> String.join prefixer 143 + |> String.split paramSeparator 144 + |> (\x -> 145 + case x of 146 + [ dir, mar ] -> 147 + Just (Param { directory = dir, marker = mar }) 148 + 149 + _ -> 150 + Nothing 151 + ) 152 + 153 + _ -> 154 + Nothing
+104
src/Library/Sources/Services/Azure/FileParser.elm
··· 1 + module Sources.Services.Azure.FileParser exposing (parseErrorResponse, parseTreeResponse) 2 + 3 + import Dict.Ext as Dict 4 + import Sources.Processing exposing (Marker(..), TreeAnswer) 5 + import Sources.Services.Azure.BlobParser 6 + import Sources.Services.Azure.FileMarker as FileMarker exposing (MarkerItem(..)) 7 + import Sources.Services.Common exposing (cleanPath) 8 + import Xml.Decode exposing (..) 9 + 10 + 11 + 12 + -- TREE 13 + 14 + 15 + parseTreeResponse : String -> Marker -> TreeAnswer Marker 16 + parseTreeResponse response previousMarker = 17 + response 18 + |> decodeString (treeDecoder previousMarker) 19 + |> Result.withDefault { filePaths = [], marker = TheEnd } 20 + 21 + 22 + treeDecoder : Marker -> Decoder (TreeAnswer Marker) 23 + treeDecoder previousMarker = 24 + usedDirectoryDecoder 25 + |> map cleanPath 26 + |> andThen 27 + (\usedDirectory -> 28 + map2 29 + (\a b -> ( usedDirectory, a, b )) 30 + (map (List.map <| String.append usedDirectory) filePathsDecoder) 31 + (map (List.map <| String.append usedDirectory) directoryPathsDecoder) 32 + ) 33 + |> andThen 34 + (\( usedDirectory, filePaths, directoryPaths ) -> 35 + previousMarker 36 + |> FileMarker.removeOne 37 + |> FileMarker.concat (List.map Directory directoryPaths) 38 + |> markerDecoder usedDirectory 39 + |> map (\marker -> { filePaths = filePaths, marker = marker }) 40 + ) 41 + 42 + 43 + usedDirectoryDecoder : Decoder String 44 + usedDirectoryDecoder = 45 + stringAttr "DirectoryPath" 46 + 47 + 48 + filePathsDecoder : Decoder (List String) 49 + filePathsDecoder = 50 + string 51 + |> single 52 + |> path [ "Name" ] 53 + |> list 54 + |> path [ "Entries", "File" ] 55 + 56 + 57 + directoryPathsDecoder : Decoder (List String) 58 + directoryPathsDecoder = 59 + string 60 + |> single 61 + |> path [ "Name" ] 62 + |> list 63 + |> path [ "Entries", "Directory" ] 64 + 65 + 66 + markerDecoder : String -> Marker -> Decoder Marker 67 + markerDecoder usedDirectory markerWithDirectories = 68 + map 69 + (\maybeNextMarker -> 70 + case maybeNextMarker of 71 + Just "" -> 72 + markerWithDirectories 73 + 74 + Just marker -> 75 + FileMarker.concat 76 + [ Param { directory = usedDirectory, marker = marker } ] 77 + markerWithDirectories 78 + 79 + Nothing -> 80 + markerWithDirectories 81 + ) 82 + (maybe <| path [ "NextMarker" ] <| single string) 83 + 84 + 85 + 86 + -- ERROR 87 + 88 + 89 + parseErrorResponse : String -> String 90 + parseErrorResponse response = 91 + response 92 + |> decodeString errorMessagesDecoder 93 + |> Result.toMaybe 94 + |> Maybe.andThen List.head 95 + |> Maybe.withDefault "Invalid request" 96 + 97 + 98 + errorMessagesDecoder : Decoder (List String) 99 + errorMessagesDecoder = 100 + string 101 + |> single 102 + |> path [ "Message" ] 103 + |> list 104 + |> path [ "Error" ]
+168
src/Library/Sources/Services/AzureBlob.elm
··· 1 + module Sources.Services.AzureBlob exposing (defaults, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) 2 + 3 + {-| Microsoft Azure Blob Service. 4 + 5 + Resources: 6 + 7 + - <https://docs.microsoft.com/en-us/rest/api/storageservices/blob-service-rest-api> 8 + 9 + -} 10 + 11 + import Dict 12 + import Http 13 + import Sources exposing (Property, SourceData) 14 + import Sources.Pick 15 + import Sources.Processing exposing (..) 16 + import Sources.Services.Azure.Authorization exposing (..) 17 + import Sources.Services.Azure.BlobParser as Parser 18 + import Sources.Services.Common exposing (cleanPath, noPrep) 19 + import Time 20 + 21 + 22 + 23 + -- PROPERTIES 24 + -- 📟 25 + 26 + 27 + defaults = 28 + { directoryPath = "/" 29 + , name = "Music from Azure Blob Storage" 30 + } 31 + 32 + 33 + {-| The list of properties we need from the user. 34 + 35 + Tuple: (property, label, placeholder, isPassword) 36 + Will be used for the forms. 37 + 38 + -} 39 + properties : List Property 40 + properties = 41 + [ { key = "accountName" 42 + , label = "Account name" 43 + , placeholder = "myaccount" 44 + , password = False 45 + } 46 + , { key = "accountKey" 47 + , label = "Account key" 48 + , placeholder = "MXFPDkaN4KBT" 49 + , password = True 50 + } 51 + , { key = "container" 52 + , label = "Container" 53 + , placeholder = "music" 54 + , password = False 55 + } 56 + , { key = "directoryPath" 57 + , label = "Directory (aka. Prefix)" 58 + , placeholder = defaults.directoryPath 59 + , password = False 60 + } 61 + ] 62 + 63 + 64 + {-| Initial data set. 65 + -} 66 + initialData : SourceData 67 + initialData = 68 + Dict.fromList 69 + [ ( "accountName", "" ) 70 + , ( "accountKey", "" ) 71 + , ( "container", "" ) 72 + , ( "directoryPath", defaults.directoryPath ) 73 + , ( "name", defaults.name ) 74 + ] 75 + 76 + 77 + 78 + -- PREPARATION 79 + 80 + 81 + prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) 82 + prepare _ _ _ _ = 83 + Nothing 84 + 85 + 86 + 87 + -- TREE 88 + 89 + 90 + {-| Create a directory tree. 91 + 92 + List all the tracks in the container. 93 + Or a specific directory in the container. 94 + 95 + -} 96 + makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg 97 + makeTree srcData marker currentTime toMsg = 98 + let 99 + directoryPath = 100 + srcData 101 + |> Dict.get "directoryPath" 102 + |> Maybe.withDefault defaults.directoryPath 103 + |> cleanPath 104 + 105 + baseParams = 106 + [ ( "maxresults", "1000" ) ] 107 + 108 + params = 109 + case marker of 110 + InProgress s -> 111 + [ ( "marker", s ) ] 112 + 113 + _ -> 114 + [] 115 + 116 + url = 117 + presignedUrl Blob List Get 1 currentTime srcData directoryPath (baseParams ++ params) 118 + in 119 + Http.get 120 + { url = url 121 + , expect = Http.expectString toMsg 122 + } 123 + 124 + 125 + {-| Re-export parser functions. 126 + -} 127 + parsePreparationResponse : String -> SourceData -> Marker -> PrepationAnswer Marker 128 + parsePreparationResponse = 129 + noPrep 130 + 131 + 132 + parseTreeResponse : String -> Marker -> TreeAnswer Marker 133 + parseTreeResponse = 134 + Parser.parseTreeResponse 135 + 136 + 137 + parseErrorResponse : String -> String 138 + parseErrorResponse = 139 + Parser.parseErrorResponse 140 + 141 + 142 + 143 + -- POST 144 + 145 + 146 + {-| Post process the tree results. 147 + 148 + !!! Make sure we only use music files that we can use. 149 + 150 + -} 151 + postProcessTree : List String -> List String 152 + postProcessTree = 153 + Sources.Pick.selectMusicFiles 154 + 155 + 156 + 157 + -- TRACK URL 158 + 159 + 160 + {-| Create a public url for a file. 161 + 162 + We need this to play the track. 163 + (!) Creates a presigned url that's valid for 48 hours 164 + 165 + -} 166 + makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 167 + makeTrackUrl currentTime srcData method pathToFile = 168 + presignedUrl Blob Read Get 48 currentTime srcData pathToFile []
+172
src/Library/Sources/Services/AzureFile.elm
··· 1 + module Sources.Services.AzureFile exposing (defaults, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) 2 + 3 + {-| Microsoft Azure File Service. 4 + 5 + Resources: 6 + 7 + - <https://docs.microsoft.com/en-us/rest/api/storageservices/file-service-rest-api> 8 + 9 + -} 10 + 11 + import Dict 12 + import Http 13 + import Sources exposing (Property, SourceData) 14 + import Sources.Pick 15 + import Sources.Processing exposing (..) 16 + import Sources.Services.Azure.Authorization exposing (..) 17 + import Sources.Services.Azure.FileMarker as FileMarker exposing (MarkerItem(..)) 18 + import Sources.Services.Azure.FileParser as Parser 19 + import Sources.Services.Common exposing (cleanPath, noPrep) 20 + import Time 21 + 22 + 23 + 24 + -- PROPERTIES 25 + -- 📟 26 + 27 + 28 + defaults = 29 + { directoryPath = "/" 30 + , name = "Music from Azure File Storage" 31 + } 32 + 33 + 34 + {-| The list of properties we need from the user. 35 + 36 + Tuple: (property, label, placeholder, isPassword) 37 + Will be used for the forms. 38 + 39 + -} 40 + properties : List Property 41 + properties = 42 + [ { key = "accountName" 43 + , label = "Account name" 44 + , placeholder = "myaccount" 45 + , password = False 46 + } 47 + , { key = "accountKey" 48 + , label = "Account key" 49 + , placeholder = "MXFPDkaN4KBT" 50 + , password = True 51 + } 52 + , { key = "container" 53 + , label = "Share name" 54 + , placeholder = "music" 55 + , password = False 56 + } 57 + , { key = "directoryPath" 58 + , label = "Directory (aka. Prefix)" 59 + , placeholder = defaults.directoryPath 60 + , password = False 61 + } 62 + ] 63 + 64 + 65 + {-| Initial data set. 66 + -} 67 + initialData : SourceData 68 + initialData = 69 + Dict.fromList 70 + [ ( "accountName", "" ) 71 + , ( "accountKey", "" ) 72 + , ( "container", "" ) 73 + , ( "directoryPath", defaults.directoryPath ) 74 + , ( "name", defaults.name ) 75 + ] 76 + 77 + 78 + 79 + -- PREPARATION 80 + 81 + 82 + prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) 83 + prepare _ _ _ _ = 84 + Nothing 85 + 86 + 87 + 88 + -- TREE 89 + 90 + 91 + {-| Create a directory tree. 92 + 93 + List all the tracks in the container. 94 + Or a specific directory in the container. 95 + 96 + -} 97 + makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg 98 + makeTree srcData marker currentTime toMsg = 99 + let 100 + directoryPathFromSrcData = 101 + srcData 102 + |> Dict.get "directoryPath" 103 + |> Maybe.withDefault defaults.directoryPath 104 + |> cleanPath 105 + 106 + baseParams = 107 + [ ( "maxresults", "1000" ) ] 108 + 109 + ( directoryPath, params ) = 110 + case FileMarker.takeOne marker of 111 + Just (Directory directory) -> 112 + Tuple.pair directory [] 113 + 114 + Just (Param param) -> 115 + Tuple.pair param.directory [ ( "marker", param.marker ) ] 116 + 117 + _ -> 118 + Tuple.pair directoryPathFromSrcData [] 119 + 120 + url = 121 + presignedUrl File List Get 1 currentTime srcData directoryPath (baseParams ++ params) 122 + in 123 + Http.get 124 + { url = url 125 + , expect = Http.expectString toMsg 126 + } 127 + 128 + 129 + {-| Re-export parser functions. 130 + -} 131 + parsePreparationResponse : String -> SourceData -> Marker -> PrepationAnswer Marker 132 + parsePreparationResponse = 133 + noPrep 134 + 135 + 136 + parseTreeResponse : String -> Marker -> TreeAnswer Marker 137 + parseTreeResponse = 138 + Parser.parseTreeResponse 139 + 140 + 141 + parseErrorResponse : String -> String 142 + parseErrorResponse = 143 + Parser.parseErrorResponse 144 + 145 + 146 + 147 + -- POST 148 + 149 + 150 + {-| Post process the tree results. 151 + 152 + !!! Make sure we only use music files that we can use. 153 + 154 + -} 155 + postProcessTree : List String -> List String 156 + postProcessTree = 157 + Sources.Pick.selectMusicFiles 158 + 159 + 160 + 161 + -- TRACK URL 162 + 163 + 164 + {-| Create a public url for a file. 165 + 166 + We need this to play the track. 167 + (!) Creates a presigned url that's valid for 48 hours 168 + 169 + -} 170 + makeTrackUrl : Time.Posix -> SourceData -> HttpMethod -> String -> String 171 + makeTrackUrl currentTime srcData method pathToFile = 172 + presignedUrl File Read Get 48 currentTime srcData pathToFile []
+2 -4
src/Library/Sources/Services/Dropbox.elm
··· 4 4 -} 5 5 6 6 import Base64 7 + import Common 7 8 import Dict 8 9 import Dict.Ext as Dict 9 10 import Http ··· 16 17 import Sources.Services.Common exposing (cleanPath, noPrep) 17 18 import Sources.Services.Dropbox.Parser as Parser 18 19 import Time 19 - import Url 20 - import Url.Builder as Url 21 20 22 21 23 22 ··· 96 95 , ( "redirect_uri", origin ++ "/sources/new/dropbox" ) 97 96 , ( "state", state ) 98 97 ] 99 - |> List.map (\( a, b ) -> Url.string a b) 100 - |> Url.toQuery 98 + |> Common.queryString 101 99 |> String.append "https://www.dropbox.com/oauth2/authorize" 102 100 103 101
+6 -11
src/Library/Sources/Services/Google.elm
··· 4 4 -} 5 5 6 6 import Base64 7 + import Common 7 8 import Dict 8 9 import Dict.Ext as Dict 9 10 import Http ··· 15 16 import Sources.Processing exposing (..) 16 17 import Sources.Services.Google.Parser as Parser 17 18 import Time 18 - import Url 19 - import Url.Builder as Url 20 19 21 20 22 21 ··· 113 112 , ( "scope", "https://www.googleapis.com/auth/drive.readonly" ) 114 113 , ( "state", state ) 115 114 ] 116 - |> List.map (\( a, b ) -> Url.string a b) 117 - |> Url.toQuery 115 + |> Common.queryString 118 116 |> String.append "https://accounts.google.com/o/oauth2/v2/auth" 119 117 120 118 ··· 165 163 ] 166 164 167 165 query = 168 - queryParams 169 - |> List.map (\( a, b ) -> Url.string a b) 170 - |> Url.toQuery 166 + Common.queryString queryParams 171 167 172 168 url = 173 169 "https://www.googleapis.com/oauth2/v4/token" ++ query ··· 215 211 , ( "spaces", "drive" ) 216 212 ] 217 213 218 - params = 214 + queryString = 219 215 (case marker of 220 216 InProgress cursor -> 221 217 [ ( "pageToken", cursor ) ··· 225 221 [] 226 222 ) 227 223 |> List.append paramsBase 228 - |> List.map (\( a, b ) -> Url.string a b) 229 - |> Url.toQuery 224 + |> Common.queryString 230 225 in 231 226 Http.request 232 227 { method = "GET" 233 228 , headers = [ Http.header "Authorization" ("Bearer " ++ accessToken) ] 234 - , url = "https://www.googleapis.com/drive/v3/files" ++ params 229 + , url = "https://www.googleapis.com/drive/v3/files" ++ queryString 235 230 , body = Http.emptyBody 236 231 , expect = Http.expectString toMsg 237 232 , timeout = Nothing
+8
src/Library/Tuple/Ext.elm
··· 1 + module Tuple.Ext exposing (uncurry) 2 + 3 + -- 🔱 4 + 5 + 6 + uncurry : (a -> b -> c) -> ( a, b ) -> c 7 + uncurry fn ( a, b ) = 8 + fn a b