1module Sources.Services.Dropbox exposing (authorizationSourceData, authorizationUrl, defaults, getProperDirectoryPath, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties)
2
3{-| Dropbox Service.
4-}
5
6import Base64
7import Common
8import Dict
9import Dict.Ext as Dict
10import Http
11import Json.Decode
12import Json.Encode
13import Sources exposing (Property, SourceData)
14import Sources.Pick
15import Sources.Processing exposing (..)
16import Sources.Services.Common exposing (cleanPath, noPrep)
17import Sources.Services.Dropbox.Parser as Parser
18import Time
19
20
21
22-- PROPERTIES
23-- 📟
24
25
26defaults =
27 { appKey = "kwsydtrzban41zr"
28 , name = "Music from Dropbox"
29 }
30
31
32{-| The list of properties we need from the user.
33
34Tuple: (property, label, placeholder, isPassword)
35Will be used for the forms.
36
37-}
38properties : List Property
39properties =
40 [ { key = "directoryPath"
41 , label = "Directory (Optional)"
42 , placeholder = "/"
43 , password = False
44 }
45 , { key = "appKey"
46 , label = "App key"
47 , placeholder = defaults.appKey
48 , password = False
49 }
50 ]
51
52
53{-| Initial data set.
54-}
55initialData : SourceData
56initialData =
57 Dict.fromList
58 [ ( "appKey", defaults.appKey )
59 , ( "directoryPath", "" )
60 , ( "name", defaults.name )
61 ]
62
63
64
65-- AUTHORIZATION
66
67
68{-| Authorization url.
69-}
70authorizationUrl : SourceData -> String -> String
71authorizationUrl sourceData origin =
72 let
73 encodeData data =
74 data
75 |> Dict.toList
76 |> List.map (Tuple.mapSecond Json.Encode.string)
77 |> Json.Encode.object
78
79 state =
80 sourceData
81 |> encodeData
82 |> Json.Encode.encode 0
83 |> Base64.encode
84 in
85 [ ( "response_type", "token" )
86 , ( "client_id", Dict.fetch "appKey" "unknown" sourceData )
87 , ( "redirect_uri", origin ++ "?path=sources/new/dropbox" )
88 , ( "state", state )
89 ]
90 |> Common.queryString
91 |> String.append "https://www.dropbox.com/oauth2/authorize"
92
93
94{-| Authorization source data.
95-}
96authorizationSourceData : { codeOrToken : Maybe String, state : Maybe String } -> SourceData
97authorizationSourceData args =
98 args.state
99 |> Maybe.andThen (Base64.decode >> Result.toMaybe)
100 |> Maybe.withDefault "{}"
101 |> Json.Decode.decodeString (Json.Decode.dict Json.Decode.string)
102 |> Result.withDefault Dict.empty
103 |> Dict.unionFlipped initialData
104 |> Dict.update "accessToken" (\_ -> args.codeOrToken)
105
106
107
108-- PREPARATION
109
110
111prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg)
112prepare _ _ _ _ =
113 Nothing
114
115
116
117-- TREE
118
119
120{-| Create a directory tree.
121
122List all the tracks in the bucket.
123Or a specific directory in the bucket.
124
125-}
126makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg
127makeTree srcData marker _ resultMsg =
128 let
129 accessToken =
130 Dict.fetch "accessToken" "" srcData
131
132 body =
133 (case marker of
134 TheBeginning ->
135 [ ( "limit", Json.Encode.int 2000 )
136 , ( "path", Json.Encode.string (getProperDirectoryPath srcData) )
137 , ( "recursive", Json.Encode.bool True )
138 ]
139
140 InProgress cursor ->
141 [ ( "cursor", Json.Encode.string cursor )
142 ]
143
144 TheEnd ->
145 []
146 )
147 |> Json.Encode.object
148 |> Http.jsonBody
149
150 url =
151 case marker of
152 TheBeginning ->
153 "https://api.dropboxapi.com/2/files/list_folder"
154
155 InProgress _ ->
156 "https://api.dropboxapi.com/2/files/list_folder/continue"
157
158 TheEnd ->
159 ""
160 in
161 Http.request
162 { method = "POST"
163 , headers = [ Http.header "Authorization" ("Bearer " ++ accessToken) ]
164 , url = url
165 , body = body
166 , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse
167 , timeout = Nothing
168 , tracker = Nothing
169 }
170
171
172getProperDirectoryPath : SourceData -> String
173getProperDirectoryPath srcData =
174 let
175 path =
176 srcData
177 |> Dict.get "directoryPath"
178 |> Maybe.withDefault ""
179 |> cleanPath
180 in
181 if path == "" then
182 ""
183
184 else
185 "/" ++ path
186
187
188{-| Re-export parser functions.
189-}
190parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker
191parsePreparationResponse =
192 noPrep
193
194
195parseTreeResponse : String -> Marker -> TreeAnswer Marker
196parseTreeResponse =
197 Parser.parseTreeResponse
198
199
200parseErrorResponse : String -> Maybe String
201parseErrorResponse =
202 Parser.parseErrorResponse
203
204
205
206-- POST
207
208
209{-| Post process the tree results.
210
211!!! Make sure we only use music files that we can use.
212
213-}
214postProcessTree : List String -> List String
215postProcessTree =
216 Sources.Pick.selectMusicFiles
217
218
219
220-- TRACK URL
221
222
223{-| Create a public url for a file.
224
225We need this to play the track.
226
227-}
228makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String
229makeTrackUrl _ _ srcData _ pathToFile =
230 "dropbox://" ++ Dict.fetch "accessToken" "" srcData ++ "@" ++ pathToFile