@@ -4,16 +4,24 @@ import kotlinx.coroutines.CoroutineScope
44import kotlinx.coroutines.launch
55import org.wordpress.android.fluxc.Dispatcher
66import org.wordpress.android.fluxc.generated.MediaActionBuilder
7+ import org.wordpress.android.fluxc.generated.UploadActionBuilder
78import org.wordpress.android.fluxc.model.MediaModel
9+ import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState
810import org.wordpress.android.fluxc.model.SiteModel
911import org.wordpress.android.fluxc.module.FLUXC_SCOPE
1012import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.WpAppNotifierHandler
1113import org.wordpress.android.fluxc.store.MediaStore.FetchMediaListResponsePayload
14+ import org.wordpress.android.fluxc.store.MediaStore.MediaError
15+ import org.wordpress.android.fluxc.store.MediaStore.MediaErrorType
16+ import org.wordpress.android.fluxc.store.MediaStore.MediaPayload
17+ import org.wordpress.android.fluxc.store.MediaStore.ProgressPayload
1218import org.wordpress.android.fluxc.utils.AppLogWrapper
19+ import org.wordpress.android.fluxc.utils.MediaUtils
1320import org.wordpress.android.fluxc.utils.MimeType
1421import org.wordpress.android.util.AppLog
1522import rs.wordpress.api.kotlin.WpApiClient
1623import rs.wordpress.api.kotlin.WpRequestResult
24+ import uniffi.wp_api.MediaCreateParams
1725import uniffi.wp_api.MediaDetailsPayload
1826import uniffi.wp_api.MediaListParams
1927import uniffi.wp_api.MediaWithEditContext
@@ -36,19 +44,7 @@ class MediaRSApiRestClient @Inject constructor(
3644) {
3745 fun fetchMediaList (site : SiteModel , number : Int , offset : Int , mimeType : MimeType .Type ? ) {
3846 scope.launch {
39- val authProvider = WpAuthenticationProvider .staticWithUsernameAndPassword(
40- username = site.apiRestUsernamePlain, password = site.apiRestPasswordPlain
41- )
42- val apiRootUrl = URL (site.buildUrl())
43- val client = WpApiClient (
44- wpOrgSiteApiRootUrl = apiRootUrl,
45- authProvider = authProvider,
46- appNotifier = object : WpAppNotifier {
47- override suspend fun requestedWithInvalidAuthentication () {
48- wpAppNotifierHandler.notifyRequestedWithInvalidAuthentication(site)
49- }
50- }
51- )
47+ val client = getWpApiClient(site)
5248 val mediaResponse = client.request { requestBuilder ->
5349 requestBuilder.media().listWithEditContext(
5450 MediaListParams (
@@ -62,12 +58,12 @@ class MediaRSApiRestClient @Inject constructor(
6258
6359 val mediaModelList = when (mediaResponse) {
6460 is WpRequestResult .Success -> {
65- appLogWrapper.d(AppLog .T .MAIN , " Fetched media list: ${mediaResponse.response.data.size} " )
61+ appLogWrapper.d(AppLog .T .MEDIA , " Fetched media list: ${mediaResponse.response.data.size} " )
6662 mediaResponse.response.data.toMediaModelList(site.id)
6763 }
6864
6965 else -> {
70- appLogWrapper.e(AppLog .T .MAIN , " Fetch media list failed: $mediaResponse " )
66+ appLogWrapper.e(AppLog .T .MEDIA , " Fetch media list failed: $mediaResponse " )
7167 emptyList()
7268 }
7369 }
@@ -92,6 +88,207 @@ class MediaRSApiRestClient @Inject constructor(
9288 dispatcher.dispatch(MediaActionBuilder .newFetchedMediaListAction(payload))
9389 }
9490
91+ fun fetchMedia (site : SiteModel , media : MediaModel ? ) {
92+ if (media == null ) {
93+ val error = MediaError (MediaErrorType .NULL_MEDIA_ARG )
94+ error.logMessage = " Requested media is null"
95+ notifyMediaFetched(site, null , error)
96+ return
97+ }
98+
99+ scope.launch {
100+ val client = getWpApiClient(site)
101+
102+ val mediaResponse = client.request { requestBuilder ->
103+ requestBuilder.media().retrieveWithEditContext(media.mediaId)
104+ }
105+
106+
107+ when (mediaResponse) {
108+ is WpRequestResult .Success -> {
109+ appLogWrapper.d(AppLog .T .MEDIA , " Fetched media with ID: " + media.mediaId)
110+
111+ val responseMedia: MediaModel = mediaResponse.response.data.toMediaModel(site.id).apply {
112+ localSiteId = site.id
113+ }
114+ notifyMediaFetched(site, responseMedia, null )
115+ }
116+
117+ else -> {
118+ val mediaError = parseMediaError(mediaResponse)
119+ appLogWrapper.e(AppLog .T .MEDIA , " Fetch media failed: ${mediaError.message} " )
120+ notifyMediaFetched(site, media, mediaError)
121+ }
122+ }
123+ }
124+ }
125+
126+ @Suppress(" UseCheckOrError" ) // Allow to throw IllegalStateException
127+ private fun parseMediaError (mediaResponse : WpRequestResult <* >): MediaError {
128+ return when (mediaResponse) {
129+ is WpRequestResult .Success -> {
130+ throw IllegalStateException (" Success media response should not be parsed as an error" )
131+ }
132+ is WpRequestResult .MediaFileNotFound <* > -> {
133+ appLogWrapper.e(AppLog .T .MEDIA , " Media file not found: $mediaResponse " )
134+ MediaError (MediaErrorType .NOT_FOUND ).apply {
135+ message = " Media file not found"
136+ }
137+ }
138+
139+ is WpRequestResult .ResponseParsingError <* > -> {
140+ appLogWrapper.e(AppLog .T .MEDIA , " Response parsing error: $mediaResponse " )
141+ MediaError (MediaErrorType .PARSE_ERROR ).apply {
142+ message = " Failed to parse response"
143+ }
144+ }
145+
146+ is WpRequestResult .SiteUrlParsingError <* > -> {
147+ appLogWrapper.e(AppLog .T .MEDIA , " Site URL parsing error: $mediaResponse " )
148+ MediaError (MediaErrorType .MALFORMED_MEDIA_ARG ).apply {
149+ message = " Invalid site URL"
150+ }
151+ }
152+
153+ is WpRequestResult .InvalidHttpStatusCode <* >,
154+ is WpRequestResult .WpError <* >,
155+ is WpRequestResult .RequestExecutionFailed <* >,
156+ is WpRequestResult .UnknownError <* > -> {
157+ appLogWrapper.e(AppLog .T .MEDIA , " Unknown error: $mediaResponse " )
158+ MediaError (MediaErrorType .GENERIC_ERROR ).apply {
159+ message = " Unknown error occurred"
160+ }
161+ }
162+ }
163+ }
164+
165+ private fun notifyMediaFetched (
166+ site : SiteModel ,
167+ media : MediaModel ? ,
168+ error : MediaError ?
169+ ) {
170+ val payload = MediaPayload (site, media, error)
171+ dispatcher.dispatch(MediaActionBuilder .newFetchedMediaAction(payload))
172+ }
173+
174+ fun deleteMedia (site : SiteModel , media : MediaModel ? ) {
175+ if (media == null ) {
176+ val error = MediaError (MediaErrorType .NULL_MEDIA_ARG )
177+ error.logMessage = " Media to delete is null"
178+ notifyMediaDeleted(site, null , error)
179+ return
180+ }
181+
182+ scope.launch {
183+ val client = getWpApiClient(site)
184+
185+ val mediaResponse = client.request { requestBuilder ->
186+ requestBuilder.media().delete(media.mediaId)
187+ }
188+
189+ when (mediaResponse) {
190+ is WpRequestResult .Success -> {
191+ appLogWrapper.d(AppLog .T .MEDIA , " Deleted media with ID: " + media.mediaId)
192+
193+ val responseMedia: MediaModel = mediaResponse.response.data.previous.toMediaModel(site.id).apply {
194+ localSiteId = site.id
195+ }
196+ notifyMediaDeleted(site, responseMedia, null )
197+ }
198+
199+ else -> {
200+ val mediaError = parseMediaError(mediaResponse)
201+ appLogWrapper.e(AppLog .T .MEDIA , " Delete media failed: ${mediaError.message} " )
202+ notifyMediaDeleted(site, media, mediaError)
203+ }
204+ }
205+ }
206+ }
207+
208+ private fun notifyMediaDeleted (
209+ site : SiteModel ,
210+ media : MediaModel ? ,
211+ error : MediaError ?
212+ ) {
213+ val payload = MediaPayload (site, media, error)
214+ dispatcher.dispatch(MediaActionBuilder .newDeletedMediaAction(payload))
215+ }
216+
217+ fun uploadMedia (site : SiteModel , media : MediaModel ? ) {
218+ if (media == null || media.id == 0 ) {
219+ // we can't have a MediaModel without an ID - otherwise we can't keep track of them.
220+ val error = MediaError (MediaErrorType .INVALID_ID )
221+ if (media == null ) {
222+ error.logMessage = " Media object is null on upload"
223+ } else {
224+ error.logMessage = " Media ID is 0 on upload"
225+ }
226+ notifyMediaUploaded(media, error)
227+ return
228+ }
229+
230+ if (media.filePath == null || ! MediaUtils .canReadFile(media.filePath)) {
231+ val error = MediaError (MediaErrorType .FS_READ_PERMISSION_DENIED )
232+ error.logMessage = " Can't read file on upload"
233+ notifyMediaUploaded(media, error)
234+ return
235+ }
236+
237+ scope.launch {
238+ val client = getWpApiClient(site)
239+
240+ val mediaResponse = client.request { requestBuilder ->
241+ requestBuilder.media().create(
242+ params = MediaCreateParams (title = media.title),
243+ filePath = media.filePath!! , // We have already checked the nullability but it's mutable
244+ fileContentType = media.mimeType.orEmpty(),
245+ requestId = null
246+ )
247+ }
248+
249+ when (mediaResponse) {
250+ is WpRequestResult .Success -> {
251+ appLogWrapper.d(AppLog .T .MEDIA , " Uploaded media with ID: " + media.id)
252+
253+ val responseMedia: MediaModel = mediaResponse.response.data.toMediaModel(site.id).apply {
254+ id = media.id // be sure we are using the same local id when getting the remote response
255+ localSiteId = site.id
256+ }
257+ notifyMediaUploaded(responseMedia, null )
258+ }
259+
260+ else -> {
261+ val mediaError = parseMediaError(mediaResponse)
262+ appLogWrapper.e(AppLog .T .MEDIA , " Upload media failed: ${mediaError.message} " )
263+ notifyMediaUploaded(media, mediaError)
264+ }
265+ }
266+ }
267+ }
268+
269+ private fun notifyMediaUploaded (media : MediaModel ? , error : MediaError ? ) {
270+ media?.setUploadState(if (error == null ) MediaUploadState .UPLOADED else MediaUploadState .FAILED )
271+ val payload = ProgressPayload (media, 1f , error == null , error)
272+ dispatcher.dispatch(UploadActionBuilder .newUploadedMediaAction(payload))
273+ }
274+
275+ private fun getWpApiClient (site : SiteModel ): WpApiClient {
276+ val authProvider = WpAuthenticationProvider .staticWithUsernameAndPassword(
277+ username = site.apiRestUsernamePlain, password = site.apiRestPasswordPlain
278+ )
279+ val apiRootUrl = URL (site.buildUrl())
280+ val client = WpApiClient (
281+ wpOrgSiteApiRootUrl = apiRootUrl,
282+ authProvider = authProvider,
283+ appNotifier = object : WpAppNotifier {
284+ override suspend fun requestedWithInvalidAuthentication () {
285+ wpAppNotifierHandler.notifyRequestedWithInvalidAuthentication(site)
286+ }
287+ }
288+ )
289+ return client
290+ }
291+
95292 private fun List<MediaWithEditContext>.toMediaModelList (
96293 siteId : Int
97294 ): List <MediaModel > = map { it.toMediaModel(siteId) }
@@ -110,7 +307,7 @@ class MediaRSApiRestClient @Inject constructor(
110307 fileExtension = this @toMediaModel.mediaType.toString()
111308 uploadDate = this @toMediaModel.date
112309 authorId = this @toMediaModel.author
113- uploadState = org.wordpress.android.fluxc.model. MediaModel . MediaUploadState .UPLOADED .toString()
310+ uploadState = MediaUploadState .UPLOADED .toString()
114311
115312 // Parse the media details
116313 when (val parsedType = this @toMediaModel.mediaDetails.parseAsMimeType(this @toMediaModel.mimeType)) {
0 commit comments