-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #232 from cocoatype/224-exported-image-telemetry
Add telemetry for exporting
- Loading branch information
Showing
17 changed files
with
276 additions
and
72 deletions.
There are no files selected for viewing
28 changes: 28 additions & 0 deletions
28
Modules/Capabilities/Exporting/Sources/Assets/ExportableAsset.swift
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,28 @@ | ||
// Created by Geoff Pado on 12/5/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Photos | ||
|
||
protocol ExportableAsset { | ||
func requestContentEditingInput(with options: PHContentEditingInputRequestOptions?) async -> (PHContentEditingInput?, [AnyHashable: Any]) | ||
|
||
var changeRequest: PHAssetChangeRequest { get } | ||
} | ||
|
||
extension PHAsset: ExportableAsset { | ||
public func requestContentEditingInput(with options: PHContentEditingInputRequestOptions?) async -> (PHContentEditingInput?, [AnyHashable: Any]) { | ||
await withCheckedContinuation { continuation in | ||
requestContentEditingInput(with: options) { input, info in | ||
continuation.resume(returning: (input, info)) | ||
} | ||
} | ||
} | ||
|
||
public var changeRequest: PHAssetChangeRequest { | ||
PHAssetChangeRequest(for: self) | ||
} | ||
} | ||
|
||
enum ExportableAssetError: Error { | ||
case unexpectedNil | ||
} |
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 |
---|---|---|
@@ -1,20 +1,36 @@ | ||
// Created by Geoff Pado on 7/12/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Defaults | ||
import Foundation | ||
import Logging | ||
import Photos | ||
import Redactions | ||
|
||
public class CopyExporter: NSObject { | ||
private let preparedURL: URL | ||
public convenience init(preparedURL: URL) { | ||
self.init(preparedURL: preparedURL, logger: Logging.logger, library: PHPhotoLibrary.shared()) | ||
} | ||
|
||
public init(preparedURL: URL) { | ||
private let logger: any Logger | ||
private let library: any PhotoLibrary | ||
init( | ||
preparedURL: URL, | ||
logger: any Logger, | ||
library: any PhotoLibrary | ||
) { | ||
self.preparedURL = preparedURL | ||
self.logger = logger | ||
self.library = library | ||
} | ||
|
||
public func export() async throws { | ||
try await PHPhotoLibrary.shared().performChanges { [preparedURL] in | ||
try await library.performChanges { [preparedURL] in | ||
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: preparedURL) | ||
} | ||
|
||
Defaults.numberOfSaves = Defaults.numberOfSaves + 1 | ||
logger.log(ExportingEventFactory().event(style: .copy)) | ||
} | ||
} |
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,11 @@ | ||
// Created by Geoff Pado on 12/8/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Foundation | ||
import Redactions | ||
|
||
public enum Exporting { | ||
public static func outputFactory(preparedURL: URL, redactions: [Redaction]) -> any OutputFactory { | ||
return PhotoOutputFactory(preparedURL: preparedURL, redactions: redactions) | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
Modules/Capabilities/Exporting/Sources/ExportingEventFactory.swift
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,30 @@ | ||
// Created by Geoff Pado on 12/5/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Defaults | ||
import Logging | ||
|
||
struct ExportingEventFactory { | ||
enum OutputStyle { | ||
case inPlace | ||
case copy | ||
} | ||
|
||
private static let eventName: Event.Name = "Exporting.successfulExport" | ||
private static let styleKey = "style" | ||
private static let exportCountKey = "exportCount" | ||
func event(style: OutputStyle) -> Event { | ||
let styleValue = switch style { | ||
case .inPlace: "inPlace" | ||
case .copy: "copy" | ||
} | ||
|
||
return Event( | ||
name: Self.eventName, | ||
info: [ | ||
Self.styleKey: styleValue, | ||
Self.exportCountKey: String(Defaults.numberOfSaves), | ||
] | ||
) | ||
} | ||
} |
67 changes: 38 additions & 29 deletions
67
Modules/Capabilities/Exporting/Sources/InPlaceExporter.swift
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 |
---|---|---|
@@ -1,50 +1,59 @@ | ||
// Created by Geoff Pado on 7/12/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Defaults | ||
import ErrorHandling | ||
import Foundation | ||
import Logging | ||
import Photos | ||
import Redactions | ||
import UIKit | ||
|
||
public class InPlaceExporter: NSObject { | ||
private let preparedURL: URL | ||
private let asset: PHAsset | ||
private let redactions: [Redaction] | ||
public convenience init( | ||
preparedURL: URL, | ||
asset: PHAsset, | ||
redactions: [Redaction] | ||
) { | ||
self.init( | ||
asset: asset, | ||
outputFactory: PhotoOutputFactory(preparedURL: preparedURL, redactions: redactions), | ||
library: PHPhotoLibrary.shared() | ||
) | ||
} | ||
|
||
public init(preparedURL: URL, asset: PHAsset, redactions: [Redaction]) { | ||
self.preparedURL = preparedURL | ||
private let asset: any ExportableAsset | ||
private let outputFactory: any OutputFactory | ||
private let logger: any Logger | ||
private let library: any PhotoLibrary | ||
init( | ||
asset: any ExportableAsset, | ||
outputFactory: any OutputFactory, | ||
logger: any Logger = Logging.logger, | ||
library: any PhotoLibrary | ||
) { | ||
self.asset = asset | ||
self.redactions = redactions | ||
self.outputFactory = outputFactory | ||
self.logger = logger | ||
self.library = library | ||
} | ||
|
||
public func export() async throws { | ||
return try await withCheckedThrowingContinuation { continuation in | ||
asset.requestContentEditingInput(with: nil) { [weak self] contentEditingInput, _ in | ||
Task { [weak self] in | ||
do { | ||
guard let self, | ||
let input = contentEditingInput | ||
else { throw ExportingError.noInputProvided } | ||
|
||
let factory = OutputFactory(preparedURL: preparedURL, redactions: redactions) | ||
let output = try factory.output(from: input) | ||
let (contentEditingInput, _) = await asset.requestContentEditingInput(with: nil) | ||
do { | ||
guard let contentEditingInput else { throw ExportingError.noInputProvided } | ||
|
||
try await PHPhotoLibrary.shared().performChanges { [asset] in | ||
let changeRequest = PHAssetChangeRequest(for: asset) | ||
changeRequest.contentEditingOutput = output | ||
print("Change request created with contentEditingOutput: \(output)") | ||
} | ||
let output = try outputFactory.output(from: contentEditingInput) | ||
|
||
print("Changes successfully committed to the photo library.") | ||
continuation.resume() | ||
} catch { | ||
ErrorHandler().log(error) | ||
print("Error during performChanges: \(error)") | ||
continuation.resume(throwing: error) | ||
} | ||
} | ||
try await library.performChanges { [asset] in | ||
asset.changeRequest.contentEditingOutput = output | ||
} | ||
|
||
Defaults.numberOfSaves = Defaults.numberOfSaves + 1 | ||
logger.log(ExportingEventFactory().event(style: .inPlace)) | ||
} catch { | ||
ErrorHandler().log(error) | ||
throw error | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Modules/Capabilities/Exporting/Sources/Libraries/PhotoLibrary.swift
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,11 @@ | ||
// Created by Geoff Pado on 12/8/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Photos | ||
|
||
protocol PhotoLibrary { | ||
func performChanges( | ||
_ changeBlock: @escaping () -> Void) async throws | ||
} | ||
|
||
extension PHPhotoLibrary: PhotoLibrary {} |
8 changes: 8 additions & 0 deletions
8
Modules/Capabilities/Exporting/Sources/Output Factories/OutputFactory.swift
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,8 @@ | ||
// Created by Geoff Pado on 12/5/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Photos | ||
|
||
public protocol OutputFactory { | ||
func output(from input: PHContentEditingInput) throws -> PHContentEditingOutput | ||
} |
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
28 changes: 28 additions & 0 deletions
28
Modules/Capabilities/Exporting/Tests/CopyExporterTests.swift
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,28 @@ | ||
// Created by Geoff Pado on 12/8/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Defaults | ||
import LoggingDoubles | ||
import XCTest | ||
|
||
@testable import Exporting | ||
@testable import Logging | ||
|
||
class CopyExporterTests: XCTestCase { | ||
func testWhenExporterSucceedsThenEventLogged() async throws { | ||
let logger = SpyLogger() | ||
let exporter = CopyExporter( | ||
preparedURL: URL(fileURLWithPath: "/"), | ||
logger: logger, | ||
library: StubPhotoLibrary() | ||
) | ||
Defaults.numberOfSaves = 0 | ||
|
||
try await exporter.export() | ||
|
||
let loggedEvent = try XCTUnwrap(logger.loggedEvents.first) | ||
XCTAssertEqual(loggedEvent.name, "Exporting.successfulExport") | ||
XCTAssertEqual(loggedEvent.info["style"], "copy") | ||
XCTAssertEqual(loggedEvent.info["exportCount"], "1") | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
Modules/Capabilities/Exporting/Tests/Doubles/StubExportableAsset.swift
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,17 @@ | ||
// Created by Geoff Pado on 12/5/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Photos | ||
|
||
@testable import Exporting | ||
|
||
struct StubExportableAsset: ExportableAsset { | ||
func requestContentEditingInput(with options: PHContentEditingInputRequestOptions?) async -> (PHContentEditingInput?, [AnyHashable: Any]) { | ||
return (PHContentEditingInput(), [:]) | ||
} | ||
|
||
var changeRequest: PHAssetChangeRequest { | ||
let asset = PHAsset() | ||
return PHAssetChangeRequest(for: asset) | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
Modules/Capabilities/Exporting/Tests/Doubles/StubOutputFactory.swift
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,12 @@ | ||
// Created by Geoff Pado on 12/5/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Photos | ||
|
||
@testable import Exporting | ||
|
||
struct StubOutputFactory: OutputFactory { | ||
func output(from input: PHContentEditingInput) throws -> PHContentEditingOutput { | ||
PHContentEditingOutput() | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
Modules/Capabilities/Exporting/Tests/Doubles/StubPhotoLibrary.swift
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,8 @@ | ||
// Created by Geoff Pado on 12/8/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
@testable import Exporting | ||
|
||
struct StubPhotoLibrary: PhotoLibrary { | ||
func performChanges(_ changeBlock: @escaping () -> Void) async throws {} | ||
} |
This file was deleted.
Oops, something went wrong.
31 changes: 31 additions & 0 deletions
31
Modules/Capabilities/Exporting/Tests/InPlaceExporterTests.swift
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,31 @@ | ||
// Created by Geoff Pado on 12/5/24. | ||
// Copyright © 2024 Cocoatype, LLC. All rights reserved. | ||
|
||
import Defaults | ||
import LoggingDoubles | ||
import XCTest | ||
|
||
@testable import Exporting | ||
@testable import Logging | ||
|
||
class InPlaceExporterTests: XCTestCase { | ||
func testWhenExporterSucceedsThenEventLogged() async throws { | ||
let asset = StubExportableAsset() | ||
let outputFactory = StubOutputFactory() | ||
let logger = SpyLogger() | ||
let exporter = InPlaceExporter( | ||
asset: asset, | ||
outputFactory: outputFactory, | ||
logger: logger, | ||
library: StubPhotoLibrary() | ||
) | ||
Defaults.numberOfSaves = 0 | ||
|
||
try await exporter.export() | ||
|
||
let loggedEvent = try XCTUnwrap(logger.loggedEvents.first) | ||
XCTAssertEqual(loggedEvent.name, "Exporting.successfulExport") | ||
XCTAssertEqual(loggedEvent.info["style"], "inPlace") | ||
XCTAssertEqual(loggedEvent.info["exportCount"], "1") | ||
} | ||
} |
Oops, something went wrong.