Fixed a few more threading-related bugs
Updated the libSession build script to reset and checkout submodules based on a flag (simplify the process) Updated the share and notification extensions to generate the DSYM on debug builds to allow for better debugging Added additional startup logging for debugging purposes Tweaked the SNLog function to indicate when the log happens on the main thread (slightly less efficient but should help track down logic incorrectly running on the main thread) Fixed a bug where we weren't recording the last PN server registration (so would re-register every time) Fixed a bug where if the user sent the app to the background too quickly after launching the app wouldn't successfully startup when re-opening Fixed a bug where the Notification and Share extensions would assert because they were dispatching the post-migration logic to the main thread (due to the threading changes in a previous release) Fixed a bug where the Notification extension would incorrectly poll open groups on the main thread
This commit is contained in:
parent
d0be7f786c
commit
ec81236615
|
@ -21,9 +21,13 @@
|
||||||
# path = "{FRAMEWORK NAME GOES HERE}";
|
# path = "{FRAMEWORK NAME GOES HERE}";
|
||||||
# sourceTree = BUILD_DIR;
|
# sourceTree = BUILD_DIR;
|
||||||
# };
|
# };
|
||||||
|
#
|
||||||
|
# Note: We might one day be able to replace this with a local podspec if this GitHub feature
|
||||||
|
# request ever gets implemented: https://github.com/CocoaPods/CocoaPods/issues/8464
|
||||||
|
|
||||||
# Need to set the path or we won't find cmake
|
# Need to set the path or we won't find cmake
|
||||||
PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5
|
PATH=${PATH}:/usr/local/bin:/opt/homebrew/bin:/sbin/md5
|
||||||
|
SHOULD_AUTO_INIT_SUBMODULES=${1:-false}
|
||||||
|
|
||||||
# Ensure the build directory exists (in case we need it before XCode creates it)
|
# Ensure the build directory exists (in case we need it before XCode creates it)
|
||||||
mkdir -p "${TARGET_BUILD_DIR}"
|
mkdir -p "${TARGET_BUILD_DIR}"
|
||||||
|
@ -41,11 +45,76 @@ if ! which cmake > /dev/null; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ]; then
|
# Check if we have the `LibSession-Util` submodule checked out and if not (depending on the 'SHOULD_AUTO_INIT_SUBMODULES' argument) perform the checkout
|
||||||
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ] || [ ! "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then
|
||||||
echo "error: Need to fetch LibSession-Util submodule."
|
if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] & command -v git >/dev/null 2>&1; then
|
||||||
echo "error: Need to fetch LibSession-Util submodule." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
echo "info: LibSession-Util submodule doesn't exist, resetting and checking out recusively now"
|
||||||
exit 1
|
git submodule foreach --recursive git reset --hard
|
||||||
|
git submodule update --init --recursive
|
||||||
|
echo "info: Checkout complete"
|
||||||
|
else
|
||||||
|
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
||||||
|
echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)."
|
||||||
|
echo "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
are_submodules_valid() {
|
||||||
|
local PARENT_PATH=$1
|
||||||
|
|
||||||
|
# Change into the path to check for it's submodules
|
||||||
|
cd "${PARENT_PATH}"
|
||||||
|
local SUB_MODULE_PATHS=($(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'))
|
||||||
|
|
||||||
|
# If there are no submodules then return success based on whether the folder has any content
|
||||||
|
if [ ${#SUB_MODULE_PATHS[@]} -eq 0 ]; then
|
||||||
|
if [ "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Loop through the child submodules and check if they are valid
|
||||||
|
for i in "${!SUB_MODULE_PATHS[@]}"; do
|
||||||
|
local CHILD_PATH="${SUB_MODULE_PATHS[$i]}"
|
||||||
|
|
||||||
|
# If the child path doesn't exist then it's invalid
|
||||||
|
if [ ! -d "${CHILD_PATH}" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
are_submodules_valid "${PARENT_PATH}/${CHILD_PATH}"
|
||||||
|
local RESULT=$?
|
||||||
|
|
||||||
|
if [ "${RESULT}" -eq 1 ]; then
|
||||||
|
echo "info: Submodule ${CHILD_PATH} is in an invalid state."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate the state of the submodules
|
||||||
|
are_submodules_valid "${SRCROOT}/LibSession-Util"
|
||||||
|
|
||||||
|
HAS_INVALID_SUBMODULE=$?
|
||||||
|
|
||||||
|
if [ "${HAS_INVALID_SUBMODULE}" -eq 1 ]; then
|
||||||
|
if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] && command -v git >/dev/null 2>&1; then
|
||||||
|
echo "info: Submodules are in an invalid state, resetting and checking out recusively now"
|
||||||
|
cd "${SRCROOT}/LibSession-Util"
|
||||||
|
git submodule foreach --recursive git reset --hard
|
||||||
|
git submodule update --init --recursive
|
||||||
|
echo "info: Checkout complete"
|
||||||
|
else
|
||||||
|
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
||||||
|
echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'."
|
||||||
|
echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate a hash of the libSession-util source files and check if they differ from the last hash
|
# Generate a hash of the libSession-util source files and check if they differ from the last hash
|
||||||
|
|
|
@ -6377,8 +6377,8 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 412;
|
CURRENT_PROJECT_VERSION = 413;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
@ -6449,7 +6449,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 412;
|
CURRENT_PROJECT_VERSION = 413;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -6514,8 +6514,8 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 412;
|
CURRENT_PROJECT_VERSION = 413;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
@ -6588,7 +6588,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 412;
|
CURRENT_PROJECT_VERSION = 413;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
@ -7496,7 +7496,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 412;
|
CURRENT_PROJECT_VERSION = 413;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -7567,7 +7567,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 412;
|
CURRENT_PROJECT_VERSION = 413;
|
||||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
<ActionContent
|
<ActionContent
|
||||||
title = "Build libSession"
|
title = "Build libSession"
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true ">
|
||||||
<EnvironmentBuildable>
|
<EnvironmentBuildable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
<ActionContent
|
<ActionContent
|
||||||
title = "Build libSession"
|
title = "Build libSession"
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true ">
|
||||||
<EnvironmentBuildable>
|
<EnvironmentBuildable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
<ActionContent
|
<ActionContent
|
||||||
title = "Build libSession"
|
title = "Build libSession"
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true ">
|
||||||
<EnvironmentBuildable>
|
<EnvironmentBuildable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
<ActionContent
|
<ActionContent
|
||||||
title = "Build libSession"
|
title = "Build libSession"
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true ">
|
||||||
<EnvironmentBuildable>
|
<EnvironmentBuildable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
<ActionContent
|
<ActionContent
|
||||||
title = "Build libSession"
|
title = "Build libSession"
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true ">
|
||||||
<EnvironmentBuildable>
|
<EnvironmentBuildable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
<ActionContent
|
<ActionContent
|
||||||
title = "Build libSession"
|
title = "Build libSession"
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" true ">
|
||||||
<EnvironmentBuildable>
|
<EnvironmentBuildable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
|
|
|
@ -17,6 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
var backgroundSnapshotBlockerWindow: UIWindow?
|
var backgroundSnapshotBlockerWindow: UIWindow?
|
||||||
var appStartupWindow: UIWindow?
|
var appStartupWindow: UIWindow?
|
||||||
|
var initialLaunchFailed: Bool = false
|
||||||
var hasInitialRootViewController: Bool = false
|
var hasInitialRootViewController: Bool = false
|
||||||
var startTime: CFTimeInterval = 0
|
var startTime: CFTimeInterval = 0
|
||||||
private var loadingViewController: LoadingViewController?
|
private var loadingViewController: LoadingViewController?
|
||||||
|
@ -71,6 +72,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
migrationsCompletion: { [weak self] result, needsConfigSync in
|
migrationsCompletion: { [weak self] result, needsConfigSync in
|
||||||
if case .failure(let error) = result {
|
if case .failure(let error) = result {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
self?.initialLaunchFailed = true
|
||||||
self?.showFailedStartupAlert(calledFrom: .finishLaunching, error: .databaseError(error))
|
self?.showFailedStartupAlert(calledFrom: .finishLaunching, error: .databaseError(error))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -135,10 +137,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
// Resume database
|
// Resume database
|
||||||
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
|
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
|
||||||
|
|
||||||
|
// Reset the 'startTime' (since it would be invalid from the last launch)
|
||||||
|
startTime = CACurrentMediaTime()
|
||||||
|
|
||||||
// If we've already completed migrations at least once this launch then check
|
// If we've already completed migrations at least once this launch then check
|
||||||
// to see if any "delayed" migrations now need to run
|
// to see if any "delayed" migrations now need to run
|
||||||
if Storage.shared.hasCompletedMigrations {
|
if Storage.shared.hasCompletedMigrations {
|
||||||
|
let initialLaunchFailed: Bool = self.initialLaunchFailed
|
||||||
|
|
||||||
AppReadiness.invalidate()
|
AppReadiness.invalidate()
|
||||||
|
|
||||||
|
// If the user went to the background too quickly then the database can be suspended before
|
||||||
|
// properly starting up, in this case an alert will be shown but we can recover from it so
|
||||||
|
// dismiss any alerts that were shown
|
||||||
|
if initialLaunchFailed {
|
||||||
|
self.window?.rootViewController?.dismiss(animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
AppSetup.runPostSetupMigrations(
|
AppSetup.runPostSetupMigrations(
|
||||||
migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in
|
migrationProgressChanged: { [weak self] progress, minEstimatedTotalTime in
|
||||||
self?.loadingViewController?.updateProgress(
|
self?.loadingViewController?.updateProgress(
|
||||||
|
@ -149,18 +164,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
migrationsCompletion: { [weak self] result, needsConfigSync in
|
migrationsCompletion: { [weak self] result, needsConfigSync in
|
||||||
if case .failure(let error) = result {
|
if case .failure(let error) = result {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self?.showFailedStartupAlert(calledFrom: .enterForeground, error: .databaseError(error))
|
self?.showFailedStartupAlert(
|
||||||
|
calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed),
|
||||||
|
error: .databaseError(error)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self?.completePostMigrationSetup(calledFrom: .enterForeground, needsConfigSync: needsConfigSync)
|
self?.completePostMigrationSetup(
|
||||||
|
calledFrom: .enterForeground(initialLaunchFailed: initialLaunchFailed),
|
||||||
|
needsConfigSync: needsConfigSync
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||||
|
if !hasInitialRootViewController { SNLog("Entered background before startup was completed") }
|
||||||
|
|
||||||
DDLog.flushLog()
|
DDLog.flushLog()
|
||||||
|
|
||||||
// NOTE: Fix an edge case where user taps on the callkit notification
|
// NOTE: Fix an edge case where user taps on the callkit notification
|
||||||
|
@ -264,6 +287,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
BackgroundPoller.isValid = true
|
BackgroundPoller.isValid = true
|
||||||
|
|
||||||
AppReadiness.runNowOrWhenAppDidBecomeReady {
|
AppReadiness.runNowOrWhenAppDidBecomeReady {
|
||||||
|
// If the 'AppReadiness' process takes too long then it's possible for the user to open
|
||||||
|
// the app after this closure is registered but before it's actually triggered - this can
|
||||||
|
// result in the `BackgroundPoller` incorrectly getting called in the foreground, this check
|
||||||
|
// is here to prevent that
|
||||||
|
guard CurrentAppContext().isInBackground() else { return }
|
||||||
|
|
||||||
BackgroundPoller.poll { result in
|
BackgroundPoller.poll { result in
|
||||||
guard BackgroundPoller.isValid else { return }
|
guard BackgroundPoller.isValid else { return }
|
||||||
|
|
||||||
|
@ -283,6 +312,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
// MARK: - App Readiness
|
// MARK: - App Readiness
|
||||||
|
|
||||||
private func completePostMigrationSetup(calledFrom lifecycleMethod: LifecycleMethod, needsConfigSync: Bool) {
|
private func completePostMigrationSetup(calledFrom lifecycleMethod: LifecycleMethod, needsConfigSync: Bool) {
|
||||||
|
SNLog("Migrations completed, performing setup and ensuring rootViewController")
|
||||||
Configuration.performMainSetup()
|
Configuration.performMainSetup()
|
||||||
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
|
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
|
||||||
|
|
||||||
|
@ -292,6 +322,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
// the user is in an invalid state (and should have already been shown a modal)
|
// the user is in an invalid state (and should have already been shown a modal)
|
||||||
guard success else { return }
|
guard success else { return }
|
||||||
|
|
||||||
|
self?.initialLaunchFailed = false
|
||||||
|
SNLog("Migrations completed, performing setup and ensuring rootViewController")
|
||||||
|
|
||||||
/// Trigger any launch-specific jobs and start the JobRunner with `JobRunner.appDidFinishLaunching()` some
|
/// Trigger any launch-specific jobs and start the JobRunner with `JobRunner.appDidFinishLaunching()` some
|
||||||
/// of these jobs (eg. DisappearingMessages job) can impact the interactions which get fetched to display on the home
|
/// of these jobs (eg. DisappearingMessages job) can impact the interactions which get fetched to display on the home
|
||||||
/// screen, if the PagedDatabaseObserver hasn't been setup yet then the home screen can show stale (ie. deleted)
|
/// screen, if the PagedDatabaseObserver hasn't been setup yet then the home screen can show stale (ie. deleted)
|
||||||
|
@ -341,7 +374,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
// Add a log to track the proper startup time of the app so we know whether we need to
|
// Add a log to track the proper startup time of the app so we know whether we need to
|
||||||
// improve it in the future from user logs
|
// improve it in the future from user logs
|
||||||
let endTime: CFTimeInterval = CACurrentMediaTime()
|
let endTime: CFTimeInterval = CACurrentMediaTime()
|
||||||
SNLog("Launch completed in \((self?.startTime).map { ceil((endTime - $0) * 1000) } ?? -1)ms")
|
SNLog("\(lifecycleMethod.timingName) completed in \((self?.startTime).map { ceil((endTime - $0) * 1000) } ?? -1)ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
// May as well run these on the background thread
|
// May as well run these on the background thread
|
||||||
|
@ -429,6 +462,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
exit(0)
|
exit(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
SNLog("Showing startup alert due to error: \(error.name)")
|
||||||
self.window?.rootViewController?.present(alert, animated: animated, completion: presentationCompletion)
|
self.window?.rootViewController?.present(alert, animated: animated, completion: presentationCompletion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,7 +504,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleActivation() {
|
private func handleActivation() {
|
||||||
guard Identity.userExists() else { return }
|
/// There is a _fun_ behaviour here where if the user launches the app, sends it to the background at the right time and then
|
||||||
|
/// opens it again the `AppReadiness` closures can be triggered before `applicationDidBecomeActive` has been
|
||||||
|
/// called again - this can result in odd behaviours so hold off on running this logic until it's properly called again
|
||||||
|
guard
|
||||||
|
Identity.userExists() &&
|
||||||
|
UserDefaults.sharedLokiProject?[.isMainAppActive] == true
|
||||||
|
else { return }
|
||||||
|
|
||||||
enableBackgroundRefreshIfNecessary()
|
enableBackgroundRefreshIfNecessary()
|
||||||
JobRunner.appDidBecomeActive()
|
JobRunner.appDidBecomeActive()
|
||||||
|
@ -492,7 +532,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
// Always call the completion block and indicate whether we successfully created the UI
|
// Always call the completion block and indicate whether we successfully created the UI
|
||||||
guard
|
guard
|
||||||
Storage.shared.isValid &&
|
Storage.shared.isValid &&
|
||||||
(AppReadiness.isAppReady() || lifecycleMethod == .finishLaunching) &&
|
(
|
||||||
|
AppReadiness.isAppReady() ||
|
||||||
|
lifecycleMethod == .finishLaunching ||
|
||||||
|
lifecycleMethod == .enterForeground(initialLaunchFailed: true)
|
||||||
|
) &&
|
||||||
!hasInitialRootViewController
|
!hasInitialRootViewController
|
||||||
else { return DispatchQueue.main.async { onComplete(hasInitialRootViewController) } }
|
else { return DispatchQueue.main.async { onComplete(hasInitialRootViewController) } }
|
||||||
|
|
||||||
|
@ -854,10 +898,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||||
|
|
||||||
// MARK: - LifecycleMethod
|
// MARK: - LifecycleMethod
|
||||||
|
|
||||||
private enum LifecycleMethod {
|
private enum LifecycleMethod: Equatable {
|
||||||
case finishLaunching
|
case finishLaunching
|
||||||
case enterForeground
|
case enterForeground(initialLaunchFailed: Bool)
|
||||||
case didBecomeActive
|
case didBecomeActive
|
||||||
|
|
||||||
|
var timingName: String {
|
||||||
|
switch self {
|
||||||
|
case .finishLaunching: return "Launch"
|
||||||
|
case .enterForeground: return "EnterForeground"
|
||||||
|
case .didBecomeActive: return "BecomeActive"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: LifecycleMethod, rhs: LifecycleMethod) -> Bool {
|
||||||
|
switch (lhs, rhs) {
|
||||||
|
case (.finishLaunching, .finishLaunching): return true
|
||||||
|
case (.enterForeground(let lhsFailed), .enterForeground(let rhsFailed)): return (lhsFailed == rhsFailed)
|
||||||
|
case (.didBecomeActive, .didBecomeActive): return true
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - StartupError
|
// MARK: - StartupError
|
||||||
|
@ -867,6 +928,15 @@ private enum StartupError: Error {
|
||||||
case failedToRestore
|
case failedToRestore
|
||||||
case startupTimeout
|
case startupTimeout
|
||||||
|
|
||||||
|
var name: String {
|
||||||
|
switch self {
|
||||||
|
case .databaseError(StorageError.startupFailed): return "Database startup failed"
|
||||||
|
case .failedToRestore: return "Failed to restore"
|
||||||
|
case .databaseError: return "Database error"
|
||||||
|
case .startupTimeout: return "Startup timeout"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var message: String {
|
var message: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .databaseError(StorageError.startupFailed): return "DATABASE_STARTUP_FAILED".localized()
|
case .databaseError(StorageError.startupFailed): return "DATABASE_STARTUP_FAILED".localized()
|
||||||
|
|
|
@ -10,6 +10,8 @@ extern NSString *const ReportedApplicationStateDidChangeNotification;
|
||||||
|
|
||||||
@interface MainAppContext : NSObject <AppContext>
|
@interface MainAppContext : NSObject <AppContext>
|
||||||
|
|
||||||
|
- (instancetype)init;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
|
@ -22,23 +22,25 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job) -> ()
|
||||||
) {
|
) {
|
||||||
// Don't run when inactive or not in main app or if the user doesn't exist yet
|
// Don't run when inactive or not in main app or if the user doesn't exist yet
|
||||||
guard
|
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||||
(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false),
|
return deferred(job) // Don't need to do anything if it's not the main app
|
||||||
Identity.userCompletedRequiredOnboarding()
|
}
|
||||||
else {
|
guard Identity.userCompletedRequiredOnboarding() else {
|
||||||
SNLog("[SyncPushTokensJob] Deferred due to incomplete registration")
|
SNLog("[SyncPushTokensJob] Deferred due to incomplete registration")
|
||||||
deferred(job) // Don't need to do anything if it's not the main app
|
return deferred(job)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to check a UIApplication setting which needs to run on the main thread so if we aren't on
|
// We need to check a UIApplication setting which needs to run on the main thread so synchronously
|
||||||
// the main thread then swap to it
|
// retrieve the value so we can continue
|
||||||
guard Thread.isMainThread else {
|
let isRegisteredForRemoteNotifications: Bool = {
|
||||||
DispatchQueue.main.async {
|
guard !Thread.isMainThread else {
|
||||||
run(job, queue: queue, success: success, failure: failure, deferred: deferred)
|
return UIApplication.shared.isRegisteredForRemoteNotifications
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
return DispatchQueue.main.sync {
|
||||||
|
return UIApplication.shared.isRegisteredForRemoteNotifications
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Push tokens don't normally change while the app is launched, so you would assume checking once
|
// Push tokens don't normally change while the app is launched, so you would assume checking once
|
||||||
// during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications"
|
// during launch is sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications"
|
||||||
|
@ -60,7 +62,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
|
|
||||||
guard
|
guard
|
||||||
job.behaviour == .runOnce ||
|
job.behaviour == .runOnce ||
|
||||||
!UIApplication.shared.isRegisteredForRemoteNotifications ||
|
!isRegisteredForRemoteNotifications ||
|
||||||
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
|
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
|
||||||
else {
|
else {
|
||||||
SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled or recent-enough registration")
|
SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled or recent-enough registration")
|
||||||
|
@ -94,6 +96,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
case .finished:
|
case .finished:
|
||||||
Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
|
Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
|
||||||
SNLog("[SyncPushTokensJob] Completed")
|
SNLog("[SyncPushTokensJob] Completed")
|
||||||
|
UserDefaults.standard[.lastPushNotificationSync] = Date()
|
||||||
|
|
||||||
Storage.shared.write { db in
|
Storage.shared.write { db in
|
||||||
db[.lastRecordedPushToken] = pushToken
|
db[.lastRecordedPushToken] = pushToken
|
||||||
|
|
|
@ -12,7 +12,6 @@ import SignalCoreKit
|
||||||
|
|
||||||
public final class NotificationServiceExtension: UNNotificationServiceExtension {
|
public final class NotificationServiceExtension: UNNotificationServiceExtension {
|
||||||
private var didPerformSetup = false
|
private var didPerformSetup = false
|
||||||
private var areVersionMigrationsComplete = false
|
|
||||||
private var contentHandler: ((UNNotificationContent) -> Void)?
|
private var contentHandler: ((UNNotificationContent) -> Void)?
|
||||||
private var request: UNNotificationRequest?
|
private var request: UNNotificationRequest?
|
||||||
|
|
||||||
|
@ -50,6 +49,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
||||||
defer {
|
defer {
|
||||||
Publishers
|
Publishers
|
||||||
.MergeMany(openGroupPollingPublishers)
|
.MergeMany(openGroupPollingPublishers)
|
||||||
|
.subscribe(on: DispatchQueue.global(qos: .background))
|
||||||
|
.subscribe(on: DispatchQueue.main)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { _ in
|
receiveCompletion: { _ in
|
||||||
self.completeSilenty()
|
self.completeSilenty()
|
||||||
|
@ -234,19 +235,23 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
||||||
$0 = NSENotificationPresenter()
|
$0 = NSENotificationPresenter()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
migrationsCompletion: { [weak self] _, needsConfigSync in
|
migrationsCompletion: { [weak self] result, needsConfigSync in
|
||||||
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
|
switch result {
|
||||||
|
case .failure: SNLog("[NotificationServiceExtension] Failed to complete migrations")
|
||||||
|
case .success:
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
|
||||||
private func versionMigrationsDidComplete(needsConfigSync: Bool) {
|
private func versionMigrationsDidComplete(needsConfigSync: Bool) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
areVersionMigrationsComplete = true
|
|
||||||
|
|
||||||
// If we need a config sync then trigger it now
|
// If we need a config sync then trigger it now
|
||||||
if needsConfigSync {
|
if needsConfigSync {
|
||||||
Storage.shared.write { db in
|
Storage.shared.write { db in
|
||||||
|
@ -254,18 +259,17 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIsAppReady()
|
checkIsAppReady(migrationsCompleted: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
private func checkIsAppReady(migrationsCompleted: Bool) {
|
||||||
private func checkIsAppReady() {
|
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
// Only mark the app as ready once.
|
// Only mark the app as ready once.
|
||||||
guard !AppReadiness.isAppReady() else { return }
|
guard !AppReadiness.isAppReady() else { return }
|
||||||
|
|
||||||
// App isn't ready until storage is ready AND all version migrations are complete.
|
// App isn't ready until storage is ready AND all version migrations are complete.
|
||||||
guard Storage.shared.isValid && areVersionMigrationsComplete else { return }
|
guard Storage.shared.isValid && migrationsCompleted else { return }
|
||||||
|
|
||||||
SignalUtilitiesKit.Configuration.performMainSetup()
|
SignalUtilitiesKit.Configuration.performMainSetup()
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import SessionUIKit
|
||||||
import SignalCoreKit
|
import SignalCoreKit
|
||||||
|
|
||||||
final class ShareNavController: UINavigationController, ShareViewDelegate {
|
final class ShareNavController: UINavigationController, ShareViewDelegate {
|
||||||
private var areVersionMigrationsComplete = false
|
|
||||||
public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>?
|
public static var attachmentPrepPublisher: AnyPublisher<[SignalAttachment], Error>?
|
||||||
|
|
||||||
// MARK: - Error
|
// MARK: - Error
|
||||||
|
@ -58,10 +57,16 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
|
||||||
$0 = NoopNotificationsManager()
|
$0 = NoopNotificationsManager()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
migrationsCompletion: { [weak self] _, needsConfigSync in
|
migrationsCompletion: { [weak self] result, needsConfigSync in
|
||||||
// performUpdateCheck must be invoked after Environment has been initialized because
|
switch result {
|
||||||
// upgrade process may depend on Environment.
|
case .failure: SNLog("[SessionShareExtension] Failed to complete migrations")
|
||||||
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
|
case .success:
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
// performUpdateCheck must be invoked after Environment has been initialized because
|
||||||
|
// upgrade process may depend on Environment.
|
||||||
|
self?.versionMigrationsDidComplete(needsConfigSync: needsConfigSync)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -82,14 +87,11 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
|
||||||
ThemeManager.traitCollectionDidChange(previousTraitCollection)
|
ThemeManager.traitCollectionDidChange(previousTraitCollection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
|
||||||
func versionMigrationsDidComplete(needsConfigSync: Bool) {
|
func versionMigrationsDidComplete(needsConfigSync: Bool) {
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
Logger.debug("")
|
Logger.debug("")
|
||||||
|
|
||||||
areVersionMigrationsComplete = true
|
|
||||||
|
|
||||||
// If we need a config sync then trigger it now
|
// If we need a config sync then trigger it now
|
||||||
if needsConfigSync {
|
if needsConfigSync {
|
||||||
Storage.shared.write { db in
|
Storage.shared.write { db in
|
||||||
|
@ -97,15 +99,14 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIsAppReady()
|
checkIsAppReady(migrationsCompleted: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
func checkIsAppReady(migrationsCompleted: Bool) {
|
||||||
func checkIsAppReady() {
|
|
||||||
AssertIsOnMainThread()
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
// App isn't ready until storage is ready AND all version migrations are complete.
|
// App isn't ready until storage is ready AND all version migrations are complete.
|
||||||
guard areVersionMigrationsComplete else { return }
|
guard migrationsCompleted else { return }
|
||||||
guard Storage.shared.isValid else { return }
|
guard Storage.shared.isValid else { return }
|
||||||
guard !AppReadiness.isAppReady() else {
|
guard !AppReadiness.isAppReady() else {
|
||||||
// Only mark the app as ready once.
|
// Only mark the app as ready once.
|
||||||
|
@ -195,6 +196,8 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
|
||||||
message: "vc_share_loading_message".localized()
|
message: "vc_share_loading_message".localized()
|
||||||
) { activityIndicator in
|
) { activityIndicator in
|
||||||
publisher
|
publisher
|
||||||
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { _ in activityIndicator.dismiss { } }
|
receiveCompletion: { _ in activityIndicator.dismiss { } }
|
||||||
)
|
)
|
||||||
|
|
|
@ -214,8 +214,14 @@ open class Storage {
|
||||||
self?.migrationProgressUpdater = nil
|
self?.migrationProgressUpdater = nil
|
||||||
SUKLegacy.clearLegacyDatabaseInstance()
|
SUKLegacy.clearLegacyDatabaseInstance()
|
||||||
|
|
||||||
if case .failure(let error) = result {
|
// Don't log anything in the case of a 'success' or if the database is suspended (the
|
||||||
SNLog("[Migration Error] Migration failed with error: \(error)")
|
// latter will happen if the user happens to return to the background too quickly on
|
||||||
|
// launch so is unnecessarily alarming, it also gets caught and logged separately by
|
||||||
|
// the 'write' functions anyway)
|
||||||
|
switch result {
|
||||||
|
case .success: break
|
||||||
|
case .failure(DatabaseError.SQLITE_ABORT): break
|
||||||
|
case .failure(let error): SNLog("[Migration Error] Migration failed with error: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
onComplete(result, needsConfigSync)
|
onComplete(result, needsConfigSync)
|
||||||
|
|
|
@ -4,10 +4,12 @@ import Foundation
|
||||||
import SignalCoreKit
|
import SignalCoreKit
|
||||||
|
|
||||||
public func SNLog(_ message: String) {
|
public func SNLog(_ message: String) {
|
||||||
|
let threadString: String = (Thread.isMainThread ? " Main" : "")
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
print("[Session] \(message)")
|
print("[Session\(threadString)] \(message)")
|
||||||
#endif
|
#endif
|
||||||
OWSLogger.info("[Session] \(message)")
|
OWSLogger.info("[Session\(threadString)] \(message)")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func SNLogNotTests(_ message: String) {
|
public func SNLogNotTests(_ message: String) {
|
||||||
|
|
Loading…
Reference in New Issue