1module Sources.Services.Google.Parser exposing (..)
2
3import Dict
4import Json.Decode exposing (..)
5import Json.Decode.Ext exposing (..)
6import Maybe.Extra
7import Sources exposing (SourceData)
8import Sources.Pick
9import Sources.Processing exposing (Marker(..), PrepationAnswer, TreeAnswer)
10import Sources.Services.Google.Marker as Marker
11import String.Path
12import Time
13
14
15
16-- PREPARATION
17
18
19parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker
20parsePreparationResponse response currentTimePosix srcData _ =
21 let
22 newAccessToken =
23 response
24 |> decodeString (field "access_token" string)
25 |> Result.withDefault ""
26
27 currentTime =
28 -- Current time in milliseconds
29 Time.posixToMillis currentTimePosix
30
31 expiresAt =
32 -- Unix timestamp in milliseconds
33 response
34 |> decodeString (field "expires_in" int)
35 -- time in seconds
36 |> Result.withDefault 2500
37 |> (\s -> currentTime + s * 1000)
38
39 maybeRefreshToken =
40 response
41 |> decodeString (maybe <| field "refresh_token" string)
42 |> Result.toMaybe
43 |> Maybe.Extra.join
44
45 refreshTokenUpdater dict =
46 case maybeRefreshToken of
47 Just refreshToken ->
48 Dict.insert "refreshToken" refreshToken dict
49
50 Nothing ->
51 dict
52 in
53 srcData
54 |> Dict.insert "accessToken" newAccessToken
55 |> Dict.insert "expiresAt" (String.fromInt expiresAt)
56 |> refreshTokenUpdater
57 |> Dict.remove "authCode"
58 |> (\s -> { sourceData = s, marker = TheEnd })
59
60
61
62-- TREE
63
64
65type alias Properties =
66 { id : String, name : String }
67
68
69type Item
70 = File Properties
71 | Directory Properties
72
73
74parseTreeResponse : String -> Marker -> TreeAnswer Marker
75parseTreeResponse response previousMarker =
76 let
77 nextPageToken =
78 response
79 |> decodeString (maybe <| field "nextPageToken" string)
80 |> Result.toMaybe
81 |> Maybe.Extra.join
82
83 items =
84 response
85 |> decodeString (field "files" <| listIgnore itemDecoder)
86 |> Result.withDefault []
87
88 usedDirectory =
89 previousMarker
90 |> Marker.takeOne
91 |> Maybe.map Marker.itemDirectory
92 |> Maybe.withDefault ""
93
94 usedPath =
95 usedDirectory
96 |> String.Path.dropRight 1
97 |> String.Path.addSuffix
98
99 ( directories, files ) =
100 List.partition
101 (\item ->
102 case item of
103 Directory _ ->
104 True
105
106 File _ ->
107 False
108 )
109 items
110 in
111 { filePaths =
112 files
113 |> List.map itemProperties
114 |> List.filter (.name >> Sources.Pick.isMusicFile)
115 |> List.map (\{ id, name } -> usedPath ++ id ++ "?name=" ++ name)
116 , marker =
117 previousMarker
118 |> Marker.removeOne
119 |> Marker.concat
120 (List.map
121 (itemProperties
122 >> (\props -> props.name ++ "/" ++ props.id)
123 >> String.append usedPath
124 >> Marker.Directory
125 )
126 directories
127 )
128 |> (case nextPageToken of
129 Just token ->
130 { directory = usedDirectory, token = token }
131 |> Marker.Param
132 |> List.singleton
133 |> Marker.concat
134
135 Nothing ->
136 identity
137 )
138 }
139
140
141itemDecoder : Decoder Item
142itemDecoder =
143 map4
144 (\id name mime _ ->
145 case mime of
146 "application/vnd.google-apps.folder" ->
147 Directory { id = id, name = name }
148
149 _ ->
150 File { id = id, name = name }
151 )
152 (field "id" string)
153 (field "name" string)
154 (field "mimeType" string)
155 (andThen
156 (\b ->
157 if b then
158 fail "Exclude deleted files"
159
160 else
161 succeed b
162 )
163 (field "trashed" bool)
164 )
165
166
167itemProperties : Item -> Properties
168itemProperties item =
169 case item of
170 Directory props ->
171 props
172
173 File props ->
174 props
175
176
177
178-- ERROR
179
180
181parseErrorResponse : String -> Maybe String
182parseErrorResponse response =
183 response
184 |> decodeString (at [ "error", "message" ] string)
185 |> Result.toMaybe