Skip to content

Commit

Permalink
Merge pull request #1197 from TortugaPower/rework-launch
Browse files Browse the repository at this point in the history
Optimize launch process
  • Loading branch information
GianniCarlo authored Oct 6, 2024
2 parents 8d6af63 + ef8d36d commit 73a84f8
Show file tree
Hide file tree
Showing 35 changed files with 679 additions and 738 deletions.
40 changes: 28 additions & 12 deletions BookPlayer.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

256 changes: 71 additions & 185 deletions BookPlayer/AppDelegate.swift

Large diffs are not rendered by default.

36 changes: 14 additions & 22 deletions BookPlayer/AppIntents/CustomRewindIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
// Copyright © 2024 Tortuga Power. All rights reserved.
//

import AppIntents
import BookPlayerKit
import Foundation
import AppIntents

@available(iOS 16.4, macOS 14.0, watchOS 10.0, tvOS 16.0, *)
struct CustomRewindIntent: AudioStartingIntent, ForegroundContinuableIntent {
@available(iOS 16.0, macOS 14.0, watchOS 10.0, tvOS 16.0, *)
struct CustomRewindIntent: AudioStartingIntent {
static var title: LocalizedStringResource = "intent_custom_skiprewind_title"

@Parameter(
Expand All @@ -24,30 +24,22 @@ struct CustomRewindIntent: AudioStartingIntent, ForegroundContinuableIntent {
Summary("Rewind \(\.$interval)")
}

func perform() async throws -> some IntentResult {
let seconds = interval.converted(to: .seconds).value
let stack = try await DatabaseInitializer().loadCoreDataStack()

let continuation: (@MainActor () async throws -> Void) = {
let actionString = CommandParser.createActionString(
from: .skipRewind,
parameters: [URLQueryItem(name: "interval", value: "\(seconds)")]
)
let actionURL = URL(string: actionString)!
UIApplication.shared.open(actionURL)
}
@Dependency
var playerLoaderService: PlayerLoaderService

guard let appDelegate = await AppDelegate.shared else {
throw needsToContinueInForegroundError(continuation: continuation)
}
@Dependency
var libraryService: LibraryService

let coreServices = await appDelegate.createCoreServicesIfNeeded(from: stack)
func perform() async throws -> some IntentResult {
let seconds = interval.converted(to: .seconds).value

guard coreServices.playerManager.hasLoadedBook() else {
throw needsToContinueInForegroundError(continuation: continuation)
if !playerLoaderService.playerManager.hasLoadedBook(),
let book = libraryService.getLastPlayedItems(limit: 1)?.first
{
try await playerLoaderService.loadPlayer(book.relativePath, autoplay: false)
}

coreServices.playerManager.skip(-seconds)
playerLoaderService.playerManager.skip(-seconds)

return .result()
}
Expand Down
36 changes: 14 additions & 22 deletions BookPlayer/AppIntents/CustomSkipForwardIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
// Copyright © 2024 Tortuga Power. All rights reserved.
//

import AppIntents
import BookPlayerKit
import Foundation
import AppIntents

@available(iOS 16.4, macOS 14.0, watchOS 10.0, tvOS 16.0, *)
struct CustomSkipForwardIntent: AudioStartingIntent, ForegroundContinuableIntent {
@available(iOS 16.0, macOS 14.0, watchOS 10.0, tvOS 16.0, *)
struct CustomSkipForwardIntent: AudioStartingIntent {
static var title: LocalizedStringResource = "intent_custom_skipforward_title"

@Parameter(
Expand All @@ -24,30 +24,22 @@ struct CustomSkipForwardIntent: AudioStartingIntent, ForegroundContinuableIntent
Summary("Skip forward \(\.$interval)")
}

func perform() async throws -> some IntentResult {
let seconds = interval.converted(to: .seconds).value
let stack = try await DatabaseInitializer().loadCoreDataStack()

let continuation: (@MainActor () async throws -> Void) = {
let actionString = CommandParser.createActionString(
from: .skipForward,
parameters: [URLQueryItem(name: "interval", value: "\(seconds)")]
)
let actionURL = URL(string: actionString)!
UIApplication.shared.open(actionURL)
}
@Dependency
var playerLoaderService: PlayerLoaderService

guard let appDelegate = await AppDelegate.shared else {
throw needsToContinueInForegroundError(continuation: continuation)
}
@Dependency
var libraryService: LibraryService

let coreServices = await appDelegate.createCoreServicesIfNeeded(from: stack)
func perform() async throws -> some IntentResult {
let seconds = interval.converted(to: .seconds).value

guard coreServices.playerManager.hasLoadedBook() else {
throw needsToContinueInForegroundError(continuation: continuation)
if !playerLoaderService.playerManager.hasLoadedBook(),
let book = libraryService.getLastPlayedItems(limit: 1)?.first
{
try await playerLoaderService.loadPlayer(book.relativePath, autoplay: false)
}

coreServices.playerManager.skip(seconds)
playerLoaderService.playerManager.skip(seconds)

return .result()
}
Expand Down
34 changes: 10 additions & 24 deletions BookPlayer/AppIntents/LastBookStartPlaybackIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,26 @@
// Copyright © 2023 Tortuga Power. All rights reserved.
//

import Foundation
import AppIntents
import BookPlayerKit
import Foundation

@available(iOS 16.4, macOS 14.0, watchOS 10.0, *)
struct LastBookStartPlaybackIntent: AudioStartingIntent, ForegroundContinuableIntent {
@available(iOS 16.0, macOS 14.0, watchOS 10.0, *)
struct LastBookStartPlaybackIntent: AudioStartingIntent {
static var title: LocalizedStringResource = "intent_lastbook_play_title"

func perform() async throws -> some IntentResult {
let stack = try await DatabaseInitializer().loadCoreDataStack()

guard let appDelegate = await AppDelegate.shared else {
throw needsToContinueInForegroundError {
let actionString = CommandParser.createActionString(
from: .play,
parameters: [URLQueryItem(name: "autoplay", value: "true")]
)
let actionURL = URL(string: actionString)!
UIApplication.shared.open(actionURL)
}
}
@Dependency
var playerLoaderService: PlayerLoaderService

let coreServices = await appDelegate.createCoreServicesIfNeeded(from: stack)
@Dependency
var libraryService: LibraryService

guard let book = coreServices.libraryService.getLastPlayedItems(limit: 1)?.first else {
func perform() async throws -> some IntentResult {
guard let book = libraryService.getLastPlayedItems(limit: 1)?.first else {
throw "intent_lastbook_empty_error".localized
}

await appDelegate.loadPlayer(
book.relativePath,
autoplay: true,
showPlayer: nil,
alertPresenter: VoidAlertPresenter()
)
try await playerLoaderService.loadPlayer(book.relativePath, autoplay: true)

return .result()
}
Expand Down
23 changes: 7 additions & 16 deletions BookPlayer/AppIntents/PausePlaybackIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,19 @@
// Copyright © 2023 Tortuga Power. All rights reserved.
//

import Foundation
import AppIntents
import BookPlayerKit
import Foundation

@available(iOS 16.4, macOS 14.0, watchOS 10.0, *)
struct PausePlaybackIntent: AudioStartingIntent, ForegroundContinuableIntent {
@available(iOS 16.0, macOS 14.0, watchOS 10.0, *)
struct PausePlaybackIntent: AudioStartingIntent {
static var title: LocalizedStringResource = "intent_playback_pause_title"

func perform() async throws -> some IntentResult {
let stack = try await DatabaseInitializer().loadCoreDataStack()

guard let appDelegate = await AppDelegate.shared else {
throw needsToContinueInForegroundError {
let actionString = CommandParser.createActionString(from: .pause, parameters: [])
let actionURL = URL(string: actionString)!
UIApplication.shared.open(actionURL)
}
}

let coreServices = await appDelegate.createCoreServicesIfNeeded(from: stack)
@Dependency
var playerLoaderService: PlayerLoaderService

coreServices.playerManager.pause()
func perform() async throws -> some IntentResult {
playerLoaderService.playerManager.pause()

return .result()
}
Expand Down
75 changes: 39 additions & 36 deletions BookPlayer/Coordinators/DataInitializerCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class DataInitializerCoordinator: BPLogger {
let databaseInitializer: DatabaseInitializer = DatabaseInitializer()
let alertPresenter: AlertPresenter

var onFinish: ((CoreDataStack) -> Void)?
var onFinish: (() -> Void)?

init(alertPresenter: AlertPresenter) {
self.alertPresenter = alertPresenter
Expand All @@ -28,13 +28,22 @@ class DataInitializerCoordinator: BPLogger {
}

func initializeLibrary(isRecoveryAttempt: Bool) async {
do {
let stack = try await databaseInitializer.loadCoreDataStack()
finishLibrarySetup(stack, fromRecovery: isRecoveryAttempt)
} catch let error as NSError where error.domain == NSPOSIXErrorDomain && (
error.code == ENOSPC
|| error.code == NSFileWriteOutOfSpaceError
) {
let appDelegate = await AppDelegate.shared!
_ = await appDelegate.setupCoreServicesTask?.result

if let errorCoreServicesSetup = await appDelegate.errorCoreServicesSetup {
await handleError(errorCoreServicesSetup as NSError)
return
}

await finishLibrarySetup(fromRecovery: isRecoveryAttempt)
}

func handleError(_ error: NSError) async {
if error.domain == NSPOSIXErrorDomain
&& (error.code == ENOSPC
|| error.code == NSFileWriteOutOfSpaceError)
{
// CoreData may fail if device doesn't have space
await MainActor.run {
alertPresenter.showAlert(
Expand All @@ -43,19 +52,12 @@ class DataInitializerCoordinator: BPLogger {
completion: nil
)
}
} catch let error as NSError where (
error.code == NSMigrationError ||
error.code == NSMigrationConstraintViolationError ||
error.code == NSMigrationCancelledError ||
error.code == NSMigrationMissingSourceModelError ||
error.code == NSMigrationMissingMappingModelError ||
error.code == NSMigrationManagerSourceStoreError ||
error.code == NSMigrationManagerDestinationStoreError ||
error.code == NSEntityMigrationPolicyError ||
error.code == NSValidationMultipleErrorsError ||
error.code == NSValidationMissingMandatoryPropertyError
) {
// TODO: We can handle `isRecoveryAttempt` to show a different error message
} else if error.code == NSMigrationError || error.code == NSMigrationConstraintViolationError
|| error.code == NSMigrationCancelledError || error.code == NSMigrationMissingSourceModelError
|| error.code == NSMigrationMissingMappingModelError || error.code == NSMigrationManagerSourceStoreError
|| error.code == NSMigrationManagerDestinationStoreError || error.code == NSEntityMigrationPolicyError
|| error.code == NSValidationMultipleErrorsError || error.code == NSValidationMissingMandatoryPropertyError
{
Self.logger.warning("Failed to perform migration, attempting recovery with the loading library sequence")
await MainActor.run {
alertPresenter.showAlert(
Expand All @@ -65,46 +67,46 @@ class DataInitializerCoordinator: BPLogger {
recoverLibraryFromFailedMigration()
}
}
} catch {
let error = error as NSError
} else {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}

func recoverLibraryFromFailedMigration() {
Task {
databaseInitializer.cleanupStoreFiles()
await AppDelegate.shared?.resetCoreServices()
await initializeLibrary(isRecoveryAttempt: true)
}
}

func finishLibrarySetup(_ stack: CoreDataStack, fromRecovery: Bool) {
let dataManager = DataManager(coreDataStack: stack)
let libraryService = LibraryService(dataManager: dataManager)
func finishLibrarySetup(fromRecovery: Bool) async {
let coreServices = await AppDelegate.shared!.coreServices!

setupDefaultState(
libraryService: libraryService,
dataManager: dataManager
libraryService: coreServices.libraryService,
dataManager: coreServices.dataManager
)

if fromRecovery {
let files = getLibraryFiles()
libraryService.insertItems(from: files)
coreServices.libraryService.insertItems(from: files)
}

DispatchQueue.main.async {
self.onFinish?(stack)
await MainActor.run {
self.onFinish?()
}
}

private func getLibraryFiles() -> [URL] {
let enumerator = FileManager.default.enumerator(
at: DataManager.getProcessedFolderURL(),
includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: { (url, error) -> Bool in
options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants],
errorHandler: { (url, error) -> Bool in
print("directoryEnumerator error at \(url): ", error)
return true
})!
}
)!
var files = [URL]()
for case let fileURL as URL in enumerator {
files.append(fileURL)
Expand All @@ -124,8 +126,9 @@ class DataInitializerCoordinator: BPLogger {
let storedIconId = UserDefaults.standard.string(forKey: Constants.UserDefaults.appIcon)
sharedDefaults.set(storedIconId, forKey: Constants.UserDefaults.appIcon)
} else if let sharedAppIcon = sharedDefaults.string(forKey: Constants.UserDefaults.appIcon),
let localAppIcon = UserDefaults.standard.string(forKey: Constants.UserDefaults.appIcon),
sharedAppIcon != localAppIcon {
let localAppIcon = UserDefaults.standard.string(forKey: Constants.UserDefaults.appIcon),
sharedAppIcon != localAppIcon
{
sharedDefaults.set(localAppIcon, forKey: Constants.UserDefaults.appIcon)
UserDefaults.standard.removeObject(forKey: Constants.UserDefaults.appIcon)
}
Expand Down
Loading

0 comments on commit 73a84f8

Please sign in to comment.