Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
649 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// RequestCoalleser.swift | ||
// PaywallArchiveBuilder | ||
// | ||
// Created by Brian Anglin on 4/26/24. | ||
// | ||
|
||
import Foundation | ||
|
||
public actor RequestCoalescence<Input: Identifiable, Output> { | ||
private var tasks: [Int: [(Output) -> Void]] = [:] | ||
|
||
public init() {} | ||
|
||
public func get(input: Input, request: @escaping (Input) async -> Output) async -> Output { | ||
if tasks[input.id.hashValue] != nil { | ||
// If there's already a task in progress, wait for it to finish | ||
return await withCheckedContinuation { continuation in | ||
appendCompletion(for: input.id.hashValue) { output in | ||
continuation.resume(returning: output) | ||
} | ||
} | ||
} else { | ||
// Start a new task if one isn't already in progress | ||
tasks[input.id.hashValue] = [] | ||
let output = await request(input) | ||
completeTasks(for: input.id.hashValue, with: output) | ||
return output | ||
} | ||
} | ||
|
||
private func appendCompletion(for hashValue: Int, completion: @escaping (Output) -> Void) { | ||
tasks[hashValue]?.append(completion) | ||
} | ||
|
||
private func completeTasks(for hashValue: Int, with output: Output) { | ||
tasks[hashValue]?.forEach { $0(output) } | ||
tasks[hashValue] = nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Brian Anglin on 4/27/24. | ||
// | ||
|
||
import Foundation | ||
|
||
// What we get back from the API | ||
|
||
public enum ArchivalManifestUsage: Codable { | ||
case always | ||
case never | ||
case ifAvailableOnPaywallOpen | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case always = "ALWAYS" | ||
case never = "NEVER" | ||
case ifAvailableOnPaywallOpen = "IF_AVAILABLE_ON_PAYWALL_OPEN" | ||
} | ||
|
||
public init(from decoder: any Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
let rawValue = try container.decode(String.self) | ||
let gatingType = CodingKeys(rawValue: rawValue) ?? .ifAvailableOnPaywallOpen | ||
switch gatingType { | ||
case .always: | ||
self = .always | ||
case .never: | ||
self = .never | ||
case .ifAvailableOnPaywallOpen: | ||
self = .ifAvailableOnPaywallOpen | ||
} | ||
} | ||
} | ||
|
||
public struct ArchivalManifest: Codable { | ||
public var use: ArchivalManifestUsage | ||
public var document: ArchivalManifestItem | ||
public var resources: [ArchivalManifestItem] | ||
public init(document: ArchivalManifestItem, resources: [ArchivalManifestItem], use: ArchivalManifestUsage) { | ||
self.document = document | ||
self.resources = resources | ||
self.use = use | ||
} | ||
} | ||
|
||
public struct ArchivalManifestItem: Codable, Identifiable { | ||
public var id: String { | ||
url.absoluteString | ||
} | ||
let url: URL | ||
let mimeType: String | ||
public init(url: URL, mimeType: String) { | ||
self.url = url | ||
self.mimeType = mimeType | ||
} | ||
} | ||
|
||
// What we return when the item is downloaded | ||
|
||
struct ArchivalManifestDownloaded: Codable { | ||
let document: ArchivalManifestItemDownloaded | ||
let items: [ArchivalManifestItemDownloaded] | ||
func toWebArchive() -> WebArchive { | ||
var webArchive = WebArchive(resource: document.toWebArchiveResource()) | ||
for item in items { | ||
webArchive.addSubresource(item.toWebArchiveResource()) | ||
} | ||
return webArchive | ||
} | ||
} | ||
|
||
|
||
public struct ArchivalManifestItemDownloaded: Codable { | ||
let url: URL | ||
let mimeType: String | ||
let data: Data | ||
let isMainDocument: Bool | ||
func toWebArchiveResource() -> WebArchiveResource { | ||
return WebArchiveResource(url: url, data: data, mimeType: mimeType) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Brian Anglin on 4/27/24. | ||
// | ||
|
||
import Foundation | ||
|
||
struct WebArchive: Encodable { | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case mainResource = "WebMainResource" | ||
case webSubresources = "WebSubresources" | ||
} | ||
|
||
let mainResource: WebArchiveMainResource | ||
var webSubresources: [WebArchiveResource] | ||
|
||
init(resource: WebArchiveResource) { | ||
self.mainResource = WebArchiveMainResource(baseResource: resource) | ||
self.webSubresources = [] | ||
} | ||
|
||
mutating func addSubresource(_ subresource: WebArchiveResource) { | ||
self.webSubresources.append(subresource) | ||
} | ||
} | ||
struct WebArchiveResource: Encodable { | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case url = "WebResourceURL" | ||
case data = "WebResourceData" | ||
case mimeType = "WebResourceMIMEType" | ||
} | ||
|
||
let url: URL | ||
let data: Data | ||
let mimeType: String | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(url.absoluteString, forKey: .url) | ||
try container.encode(data, forKey: .data) | ||
try container.encode(mimeType, forKey: .mimeType) | ||
} | ||
} | ||
struct WebArchiveMainResource: Encodable { | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case url = "WebResourceURL" | ||
case data = "WebResourceData" | ||
case mimeType = "WebResourceMIMEType" | ||
case textEncodingName = "WebResourceTextEncodingName" | ||
case frameName = "WebResourceFrameName" | ||
} | ||
|
||
let baseResource: WebArchiveResource | ||
|
||
func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(baseResource.url.absoluteString, forKey: .url) | ||
try container.encode(baseResource.data, forKey: .data) | ||
try container.encode(baseResource.mimeType, forKey: .mimeType) | ||
try container.encode("UTF-8", forKey: .textEncodingName) | ||
try container.encode("", forKey: .frameName) | ||
} | ||
} |
Oops, something went wrong.