Sources/XCRemoteCache/Dependencies/OverlayReader.swift (92 lines of code) (raw):

// Copyright (c) 2021 Spotify AB. // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import Foundation /// Maps overlay's virtual URL with an actual (local) location struct OverlayMapping: Hashable { let virtual: URL let local: URL } enum JsonOverlayReaderError: Error { /// The source file is missing case missingSourceFile(URL) /// The file exists but its content is invalid case invalidSourceContent(URL) /// the overlay format is not supported - either contains a nested directory or a single file case unsupportedFormat } /// Provides virtual file system overlay mappings protocol OverlayReader { func provideMappings() throws -> [OverlayMapping] } class JsonOverlayReader: OverlayReader { enum Mode { /// Interrupts the operation if the representation file is missing case strict /// Assume empty overlay mapping if the file doesn't exist case bestEffort } private struct Overlay: Decodable { enum OverlayType: String, Decodable { case file case directory } struct Content: Decodable { let externalContents: String let name: String let type: OverlayType enum CodingKeys: String, CodingKey { case externalContents = "external-contents" case name case type } } struct RootContent: Decodable { let contents: [Content] let name: String let type: OverlayType } let roots: [RootContent] } private lazy var jsonDecoder = JSONDecoder() private let json: URL private let mode: Mode private let fileReader: FileReader init(_ json: URL, mode: Mode, fileReader: FileReader) { self.json = json self.mode = mode self.fileReader = fileReader } func provideMappings() throws -> [OverlayMapping] { guard let jsonContent = try fileReader.contents(atPath: json.path) else { switch mode { case .strict: throw JsonOverlayReaderError.missingSourceFile(json) case .bestEffort: debugLog("overlay mapping file \(json) doesn't exist. Skipping overlay for the best-effort mode.") return [] } } do { let overlay: Overlay = try jsonDecoder.decode(Overlay.self, from: jsonContent) let mappings: [OverlayMapping] = try overlay.roots.reduce([]) { prev, root in switch root.type { case .directory: // iterate all contents let dir = URL(fileURLWithPath: root.name) let mappings: [OverlayMapping] = try root.contents.map { content in switch content.type { case .file: let virtual = dir.appendingPathComponent(content.name) let local = URL(fileURLWithPath: content.externalContents) return .init(virtual: virtual, local: local) case .directory: throw JsonOverlayReaderError.unsupportedFormat } } return prev + mappings case .file: throw JsonOverlayReaderError.unsupportedFormat } } return mappings } catch { switch mode { case .strict: throw error case .bestEffort: errorLog("Overlay reader has failed with an error \(error). Best-effort mode - skipping an overlay.") return [] } } } }