-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathMediaRepository.swift
More file actions
178 lines (157 loc) · 7.34 KB
/
Copy pathMediaRepository.swift
File metadata and controls
178 lines (157 loc) · 7.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import Foundation
import WordPressData
import WordPressKit
final class MediaRepository {
enum Error: Swift.Error {
case mediaNotFound
case remoteAPIUnavailable
case unknown
}
private let coreDataStack: CoreDataStackSwift
private let remoteFactory: MediaServiceRemoteFactory
init(coreDataStack: CoreDataStackSwift, remoteFactory: MediaServiceRemoteFactory = .init()) {
self.coreDataStack = coreDataStack
self.remoteFactory = remoteFactory
}
/// Get the Media object from the server using the blog and the mediaID as the identifier of the resource
func getMedia(
withID mediaID: NSNumber,
in blogID: TaggedManagedObjectID<Blog>
) async throws -> TaggedManagedObjectID<Media> {
let remote = try await remote(for: blogID)
let remoteMedia: RemoteMedia? = try await withCheckedThrowingContinuation { continuation in
remote.getMediaWithID(mediaID) {
continuation.resume(returning: $0)
} failure: {
continuation.resume(throwing: $0 ?? MediaRepository.Error.unknown)
}
}
guard let remoteMedia else {
throw MediaRepository.Error.mediaNotFound
}
return try await coreDataStack.performAndSave { context in
let blog = try context.existingObject(with: blogID)
let media = Media.existingMediaWith(mediaID: mediaID, inBlog: blog) ?? Media.makeMedia(blog: blog)
MediaHelper.update(media: media, with: remoteMedia)
return TaggedManagedObjectID(media)
}
}
/// Deletes the Media object from the server. Note the Media is deleted, not trashed.
func delete(_ mediaID: TaggedManagedObjectID<Media>) async throws {
// Delete the media from WordPress Media Library
let queryResult: (MediaServiceRemote, RemoteMedia)? = try await coreDataStack.performQuery {
[remoteFactory] context in
guard let media = try? context.existingObject(with: mediaID) else {
return nil
}
// No need to delete the media from Media Library if it's not synced
if media.remoteStatus != .sync {
return nil
}
let remote = try remoteFactory.remote(for: media.blog)
return (remote, RemoteMedia.from(media))
}
if let queryResult {
let (remote, remoteMedia) = queryResult
try await withCheckedThrowingContinuation { [remote] continuation in
remote.delete(
remoteMedia,
success: { continuation.resume(returning: ()) },
failure: { continuation.resume(throwing: $0!) }
)
}
}
// Delete the media locally from Core Data.
//
// Considering the intent of calling this method is to delete the media object,
// when it doesn't exist, we can treat the flow as success, since the intent is fulfilled.
try? await coreDataStack.performAndSave { context in
let media = try context.existingObject(with: mediaID)
context.delete(media)
}
}
}
private extension MediaRepository {
func remote(for blogID: TaggedManagedObjectID<Blog>) async throws -> MediaServiceRemote {
try await coreDataStack.performQuery { [remoteFactory] context in
let blog = try context.existingObject(with: blogID)
return try remoteFactory.remote(for: blog)
}
}
}
@objc public class MediaServiceRemoteFactory: NSObject {
/// Picks the media remote based on the site type and the credentials available:
///
/// Site API used
/// ----------------------------------------- --------------------------------------
/// WordPress.com Simple / Atomic WordPress.com REST (MediaServiceRemoteREST)
/// Self-hosted (incl. Jetpack), app password Site REST (MediaServiceRemoteCoreREST)
/// Self-hosted (incl. Jetpack), no app pw WordPress.com REST (MediaServiceRemoteREST)
/// Self-hosted, no WP.com account, no app pw XML-RPC (MediaServiceRemoteXMLRPC)
///
/// Self-hosted sites prefer their own REST API via an application password so media bypasses the
/// WordPress.com Jetpack proxy, which some hosts block. WordPress.com-managed sites (Simple and
/// Atomic) stay on the WordPress.com REST API, where WordPress.com-specific features such as video
/// upload restrictions and storage limits based on the site's plan are enforced.
@objc(remoteForBlog:error:)
public func remote(for blog: Blog) throws -> MediaServiceRemote {
if !(blog.isHostedAtWPcom || blog.isAtomic),
let site = try? WordPressSite(blog: blog), case .selfHosted = site
{
return MediaServiceRemoteCoreREST(client: WordPressClientFactory.shared.instance(for: site))
}
if blog.supports(.wpComRESTAPI), let dotComID = blog.dotComID, let api = blog.wordPressComRestApi {
return MediaServiceRemoteREST(wordPressComRestApi: api, siteID: dotComID)
}
if let username = blog.username, let password = blog.password, let api = blog.xmlrpcApi {
return MediaServiceRemoteXMLRPC(api: api, username: username, password: password)
}
throw MediaRepository.Error.remoteAPIUnavailable
}
}
extension MediaServiceRemote {
func getMediaLibraryCount(forMediaTypes types: [MediaType]) async throws -> Int {
try await getMediaLibraryCount(forMediaTypes: types.map(\.stringValue))
}
func getMediaLibraryCount(forMediaTypes mediaTypes: [String]) async throws -> Int {
var total = 0
for type in mediaTypes {
total += try await self.getMediaLibraryCount(forMediaType: type)
}
return total
}
private func getMediaLibraryCount(forMediaType type: String) async throws -> Int {
try await withCheckedThrowingContinuation { continuation in
self.getMediaLibraryCount(
forType: type,
withSuccess: { continuation.resume(returning: $0) },
failure: { continuation.resume(throwing: $0!) }
)
}
}
}
extension RemoteMedia {
@objc(remoteMediaWithMedia:)
public static func from(_ media: Media) -> RemoteMedia {
let remoteMedia = RemoteMedia()
remoteMedia.mediaID = media.mediaID
remoteMedia.url = media.remoteURL.flatMap(URL.init(string:))
remoteMedia.largeURL = media.remoteLargeURL.flatMap(URL.init(string:))
remoteMedia.mediumURL = media.remoteMediumURL.flatMap(URL.init(string:))
remoteMedia.date = media.creationDate
remoteMedia.file = media.filename
remoteMedia.`extension` = media.fileExtension() ?? "unknown"
remoteMedia.title = media.title
remoteMedia.caption = media.caption
remoteMedia.descriptionText = media.desc
remoteMedia.alt = media.alt
remoteMedia.height = media.height
remoteMedia.width = media.width
remoteMedia.localURL = media.absoluteLocalURL
remoteMedia.mimeType = media.mimeType
remoteMedia.videopressGUID = media.videopressGUID
remoteMedia.remoteThumbnailURL = media.remoteThumbnailURL
remoteMedia.postID = media.postID
return remoteMedia
}
}