Skip to content

Commit 9e4a8de

Browse files
committed
Cleanup
1 parent ad776c6 commit 9e4a8de

File tree

1 file changed

+75
-82
lines changed

1 file changed

+75
-82
lines changed

ios/Sources/GutenbergKit/Sources/Service/EditorService.swift

Lines changed: 75 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ actor EditorService {
4242
self.siteURL = siteURL
4343
self.urlSession = urlSession
4444

45-
self.storeURL = URL.applicationDirectory
46-
.appendingPathComponent("GutenbergKit", isDirectory: true)
45+
self.storeURL = EditorService.rootURL
4746
.appendingPathComponent(siteURL.sha1, isDirectory: true)
4847
}
4948

49+
private static var rootURL: URL {
50+
URL.applicationDirectory.appendingPathComponent("GutenbergKit", isDirectory: true)
51+
}
52+
5053
/// Set up the editor for the given site.
5154
///
5255
/// - warning: The request make take a significant amount of time the first
@@ -138,7 +141,7 @@ actor EditorService {
138141

139142
// Only write manifest to disk after all assets are successfully fetched
140143
do {
141-
createStoreDirectoryIfNeeded()
144+
FileManager.default.createDirectoryIfNeeded(at: storeURL)
142145
try manifestData.write(to: manifestFileURL)
143146
log(.info, "Manifest saved to disk")
144147
} catch {
@@ -169,15 +172,15 @@ actor EditorService {
169172
private func fetchEditorSettings(baseURL: URL, authHeader: String) async throws -> Data {
170173
let data = try await fetchData(for: baseURL.appendingPathComponent("/wp-block-editor/v1/settings"), authHeader: authHeader)
171174
do {
172-
createStoreDirectoryIfNeeded()
175+
FileManager.default.createDirectoryIfNeeded(at: storeURL)
173176
try data.write(to: editorSettingsFileURL)
174177
} catch {
175178
assertionFailure("Failed to save settings: \(error)")
176179
}
177180
return data
178181
}
179182

180-
// MARK: - Manifest
183+
// MARK: - Assets Manifest
181184

182185
/// Fetches the editor assets manifest from the WordPress REST API
183186
/// Does not write to disk - use this to get manifest data without persisting it
@@ -194,43 +197,13 @@ actor EditorService {
194197
/// Returns the editor assets manifest as a JSON string, with JavaScript and stylesheet links
195198
/// modified so that their content can be cached and reused by the editor.
196199
///
197-
/// Verifies that all required assets are cached before returning the manifest.
198-
///
199200
/// - Parameter siteURL: The site URL to extract the scheme for scheme-less links
200201
/// - Returns: JSON string of the processed manifest
201-
/// - Throws: `EditorServiceError` if assets are missing or manifest processing fails
202202
private func getManifestForEditor(siteURL: String) throws -> String {
203203
// For scheme-less links (i.e. '//stats.wp.com/w.js'), use the scheme in `siteURL`.
204204
let siteURLScheme = URL(string: siteURL)?.scheme
205205
let data = try Data(contentsOf: manifestFileURL)
206206
let manifest = try JSONDecoder().decode(EditorAssetsManifest.self, from: data)
207-
let assetLinks = try manifest.parseAssetLinks()
208-
209-
// Verify all assets are cached
210-
let fileManager = FileManager.default
211-
var missingAssets: [String] = []
212-
213-
for urlString in assetLinks {
214-
let filename = cachedFilename(for: urlString)
215-
let localURL = assetsDirectoryURL.appendingPathComponent(filename)
216-
217-
if !fileManager.fileExists(atPath: localURL.path) {
218-
missingAssets.append(urlString)
219-
}
220-
}
221-
222-
if !missingAssets.isEmpty {
223-
log(.error, "Missing \(missingAssets.count) asset(s) from cache")
224-
for (index, asset) in missingAssets.prefix(5).enumerated() {
225-
log(.error, " [\(index + 1)] \(asset)")
226-
}
227-
if missingAssets.count > 5 {
228-
log(.error, " ... and \(missingAssets.count - 5) more")
229-
}
230-
throw URLError(.resourceUnavailable)
231-
}
232-
233-
log(.info, "All \(assetLinks.count) manifest assets verified in cache")
234207

235208
// Process manifest for editor
236209
let processedData = try manifest.renderForEditor(defaultScheme: siteURLScheme)
@@ -244,85 +217,93 @@ actor EditorService {
244217

245218
/// Fetches all assets from the manifest and stores them on the device
246219
private func fetchAssets(manifestData: Data) async throws {
220+
let startTime = CFAbsoluteTimeGetCurrent()
247221
let manifest = try JSONDecoder().decode(EditorAssetsManifest.self, from: manifestData)
248222
let assetLinks = try manifest.parseAssetLinks()
223+
.filter { isSupportedAsset($0) }
249224

250225
log(.info, "Found \(assetLinks.count) assets to fetch")
251226

252-
// Create assets directory if needed
253-
let fileManager = FileManager.default
254-
if !fileManager.fileExists(atPath: assetsDirectoryURL.path) {
255-
try fileManager.createDirectory(at: assetsDirectoryURL, withIntermediateDirectories: true)
256-
}
227+
FileManager.default.createDirectoryIfNeeded(at: assetsDirectoryURL)
257228

258229
// Track statistics
259230
var fetchedCount = 0
260231
var cachedCount = 0
261-
var totalSize: Int64 = 0
232+
var assetURLs: [URL] = []
262233

263234
// Fetch all assets in parallel
264-
try await withThrowingTaskGroup(of: (Bool, Int64).self) { group in
235+
await withTaskGroup(of: Result<(Bool, URL), Error>.self) { group in
265236
for link in assetLinks {
266237
group.addTask {
267-
try await self.fetchAsset(from: link)
238+
await Result { try await self.fetchAsset(from: link) }
268239
}
269240
}
270241

271-
for try await (wasCached, size) in group {
272-
if wasCached {
273-
cachedCount += 1
274-
} else {
275-
fetchedCount += 1
242+
for await result in group {
243+
switch result {
244+
case .success(let (wasCached, url)):
245+
if wasCached {
246+
cachedCount += 1
247+
} else {
248+
fetchedCount += 1
249+
}
250+
assetURLs.append(url)
251+
case .failure(let error):
252+
log(.error, "Failed to fetch asset: \(error)")
276253
}
277-
totalSize += size
278254
}
279255
}
280256

281-
let totalSizeMB = Double(totalSize) / (1024 * 1024)
282-
log(.info, "Assets loaded: \(fetchedCount) fetched, \(cachedCount) cached, total size: \(String(format: "%.2f", totalSizeMB)) MB")
257+
let totalTime = CFAbsoluteTimeGetCurrent() - startTime
258+
log(.info, "Assets loaded: \(fetchedCount) fetched, \(cachedCount) cached, total size: \(assetURLs.reduce(0) { $0 + $1.fileSize }.formatted) in \(String(format: "%.2f", totalTime))s")
283259
}
284260

285-
/// Fetches a single asset and stores it on disk
286-
/// - Returns: A tuple indicating (wasCached, fileSize)
287-
private func fetchAsset(from urlString: String) async throws -> (Bool, Int64) {
261+
/// Checks if an asset URL is supported
262+
private func isSupportedAsset(_ urlString: String) -> Bool {
288263
guard let url = URL(string: urlString) else {
289264
log(.warn, "Malformed asset link: \(urlString)")
290-
return (false, 0)
265+
return false
291266
}
292267

293268
guard url.scheme == "http" || url.scheme == "https" else {
294269
log(.warn, "Unexpected asset link: \(urlString)")
295-
return (false, 0)
270+
return false
296271
}
297272

298273
let supportedResourceSuffixes = [".js", ".css", ".js.map"]
299274
guard supportedResourceSuffixes.contains(where: { url.lastPathComponent.hasSuffix($0) }) else {
300275
log(.warn, "Unsupported asset URL: \(url)")
301-
return (false, 0)
276+
return false
277+
}
278+
279+
return true
280+
}
281+
282+
/// Fetches a single asset and stores it on disk
283+
/// - Returns: A tuple indicating (wasCached, fileURL)
284+
private func fetchAsset(from urlString: String) async throws -> (Bool, URL) {
285+
guard let url = URL(string: urlString) else {
286+
throw URLError(.badURL)
302287
}
303288

304289
let localURL = assetsDirectoryURL.appendingPathComponent(cachedFilename(for: urlString))
305290

306-
// Check if already cached
307291
if FileManager.default.fileExists(atPath: localURL.path) {
308-
let size = try? FileManager.default.attributesOfItem(atPath: localURL.path)[.size] as? Int64 ?? 0
309-
return (true, size ?? 0)
292+
return (true, localURL)
310293
}
311294

295+
let startTime = CFAbsoluteTimeGetCurrent()
312296
let (downloadedURL, response) = try await urlSession.download(from: url)
313-
if let status = (response as? HTTPURLResponse)?.statusCode, (200..<300).contains(status) {
314-
let size = try? FileManager.default.attributesOfItem(atPath: downloadedURL.path)[.size] as? Int64 ?? 0
315-
do {
316-
try FileManager.default.moveItem(at: downloadedURL, to: localURL)
317-
} catch {
318-
log(.error, "Failed to move downloaded assets \(downloadedURL) \(localURL)")
319-
}
320-
log(.debug, "Downloaded asset: \(url.lastPathComponent) (\(size ?? 0) bytes)")
321-
return (false, size ?? 0)
322-
} else {
323-
log(.error, "Received unexpected HTTP response for URL: \(url)")
324-
return (false, 0)
297+
let downloadTime = CFAbsoluteTimeGetCurrent() - startTime
298+
299+
guard let status = (response as? HTTPURLResponse)?.statusCode, (200..<300).contains(status) else {
300+
throw URLError(.badServerResponse)
325301
}
302+
303+
try FileManager.default.moveItem(at: downloadedURL, to: localURL)
304+
305+
log(.debug, "Downloaded asset: \(url.lastPathComponent) (\(localURL.fileSize.formatted)) in \(String(format: "%.2f", downloadTime))s")
306+
return (false, localURL)
326307
}
327308

328309
/// Loads a cached asset from disk
@@ -347,11 +328,9 @@ actor EditorService {
347328

348329
/// Deletes all cached editor data for all sites
349330
static func deleteAllData() throws {
350-
let rootURL = URL.documentsDirectory.appendingPathComponent("GutenbergKit", isDirectory: true)
351-
guard FileManager.default.fileExists(atPath: rootURL.path) else {
352-
return
331+
if FileManager.default.fileExists(atPath: EditorService.rootURL.path()) {
332+
try FileManager.default.removeItem(at: EditorService.rootURL)
353333
}
354-
try FileManager.default.removeItem(at: rootURL)
355334
}
356335

357336
/// Generates a cached filename from an asset URL using SHA256 hash
@@ -365,12 +344,6 @@ actor EditorService {
365344
return hash
366345
}
367346

368-
private func createStoreDirectoryIfNeeded() {
369-
if !FileManager.default.fileExists(atPath: storeURL.path) {
370-
try? FileManager.default.createDirectory(at: storeURL, withIntermediateDirectories: true)
371-
}
372-
}
373-
374347
private func fetchData(for requestURL: URL, authHeader: String) async throws -> Data {
375348
var request = URLRequest(url: requestURL)
376349
request.setValue(authHeader, forHTTPHeaderField: "Authorization")
@@ -393,3 +366,23 @@ private extension Result {
393366
}
394367
}
395368
}
369+
370+
private extension URL {
371+
var fileSize: Int64 {
372+
(try? FileManager.default.attributesOfItem(atPath: path())[.size] as? Int64) ?? 0
373+
}
374+
}
375+
376+
private extension Int64 {
377+
var formatted: String {
378+
ByteCountFormatter.string(fromByteCount: self, countStyle: .file)
379+
}
380+
}
381+
382+
private extension FileManager {
383+
func createDirectoryIfNeeded(at url: URL) {
384+
if !fileExists(atPath: url.path) {
385+
try? createDirectory(at: url, withIntermediateDirectories: true)
386+
}
387+
}
388+
}

0 commit comments

Comments
 (0)