Merge remote-tracking branch 'origin/feature/ci' into feature/updated-user-config-handling
This commit is contained in:
commit
970efbc3be
|
@ -0,0 +1,150 @@
|
||||||
|
// Intentionally doing a depth of 2 as libSession-util has it's own submodules (and libLokinet likely will as well)
|
||||||
|
local clone_submodules = {
|
||||||
|
name: 'Clone Submodules',
|
||||||
|
commands: ['git fetch --tags', 'git submodule update --init --recursive --depth=2']
|
||||||
|
};
|
||||||
|
|
||||||
|
// cmake options for static deps mirror
|
||||||
|
local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else '');
|
||||||
|
|
||||||
|
// Cocoapods
|
||||||
|
//
|
||||||
|
// Unfortunately Cocoapods has a dumb restriction which requires you to use UTF-8 for the
|
||||||
|
// 'LANG' env var so we need to work around the with https://github.com/CocoaPods/CocoaPods/issues/6333
|
||||||
|
local install_cocoapods = {
|
||||||
|
name: 'Install CocoaPods',
|
||||||
|
commands: ['LANG=en_US.UTF-8 pod install']
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load from the cached CocoaPods directory (to speed up the build)
|
||||||
|
local load_cocoapods_cache = {
|
||||||
|
name: 'Load CocoaPods Cache',
|
||||||
|
commands: [
|
||||||
|
|||
|
||||||
|
while test -e /Users/drone/.cocoapods_cache.lock; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|||,
|
||||||
|
'touch /Users/drone/.cocoapods_cache.lock',
|
||||||
|
|||
|
||||||
|
if [[ -d /Users/drone/.cocoapods_cache ]]; then
|
||||||
|
cp -r /Users/drone/.cocoapods_cache ./Pods
|
||||||
|
fi
|
||||||
|
|||,
|
||||||
|
'rm /Users/drone/.cocoapods_cache.lock'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override the cached CocoaPods directory (to speed up the next build)
|
||||||
|
local update_cocoapods_cache = {
|
||||||
|
name: 'Update CocoaPods Cache',
|
||||||
|
commands: [
|
||||||
|
|||
|
||||||
|
while test -e /Users/drone/.cocoapods_cache.lock; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|||,
|
||||||
|
'touch /Users/drone/.cocoapods_cache.lock',
|
||||||
|
|||
|
||||||
|
if [[ -d ./Pods ]]; then
|
||||||
|
rm -rf /Users/drone/.cocoapods_cache
|
||||||
|
cp -r ./Pods /Users/drone/.cocoapods_cache
|
||||||
|
fi
|
||||||
|
|||,
|
||||||
|
'rm /Users/drone/.cocoapods_cache.lock'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
[
|
||||||
|
// Unit tests
|
||||||
|
{
|
||||||
|
kind: 'pipeline',
|
||||||
|
type: 'exec',
|
||||||
|
name: 'Unit Tests',
|
||||||
|
platform: { os: 'darwin', arch: 'amd64' },
|
||||||
|
steps: [
|
||||||
|
clone_submodules,
|
||||||
|
load_cocoapods_cache,
|
||||||
|
install_cocoapods,
|
||||||
|
{
|
||||||
|
name: 'Run Unit Tests',
|
||||||
|
commands: [
|
||||||
|
'mkdir build',
|
||||||
|
|||
|
||||||
|
if command -v xcpretty >/dev/null 2>&1; then
|
||||||
|
xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro" | xcpretty
|
||||||
|
else
|
||||||
|
xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14 Pro"
|
||||||
|
fi
|
||||||
|
|||
|
||||||
|
],
|
||||||
|
},
|
||||||
|
update_cocoapods_cache
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Simulator build
|
||||||
|
{
|
||||||
|
kind: 'pipeline',
|
||||||
|
type: 'exec',
|
||||||
|
name: 'Simulator Build',
|
||||||
|
platform: { os: 'darwin', arch: 'amd64' },
|
||||||
|
steps: [
|
||||||
|
clone_submodules,
|
||||||
|
load_cocoapods_cache,
|
||||||
|
install_cocoapods,
|
||||||
|
{
|
||||||
|
name: 'Build',
|
||||||
|
commands: [
|
||||||
|
'mkdir build',
|
||||||
|
|||
|
||||||
|
if command -v xcpretty >/dev/null 2>&1; then
|
||||||
|
xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | xcpretty
|
||||||
|
else
|
||||||
|
xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator"
|
||||||
|
fi
|
||||||
|
|||
|
||||||
|
],
|
||||||
|
},
|
||||||
|
update_cocoapods_cache,
|
||||||
|
{
|
||||||
|
name: 'Upload artifacts',
|
||||||
|
commands: [
|
||||||
|
'./Scripts/drone-static-upload.sh'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// AppStore build (generate an archive to be signed later)
|
||||||
|
{
|
||||||
|
kind: 'pipeline',
|
||||||
|
type: 'exec',
|
||||||
|
name: 'AppStore Build',
|
||||||
|
platform: { os: 'darwin', arch: 'amd64' },
|
||||||
|
steps: [
|
||||||
|
clone_submodules,
|
||||||
|
load_cocoapods_cache,
|
||||||
|
install_cocoapods,
|
||||||
|
{
|
||||||
|
name: 'Build',
|
||||||
|
commands: [
|
||||||
|
'mkdir build',
|
||||||
|
|||
|
||||||
|
if command -v xcpretty >/dev/null 2>&1; then
|
||||||
|
xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates | xcpretty
|
||||||
|
else
|
||||||
|
xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration 'App Store Release' -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates
|
||||||
|
fi
|
||||||
|
|||
|
||||||
|
],
|
||||||
|
},
|
||||||
|
update_cocoapods_cache,
|
||||||
|
{
|
||||||
|
name: 'Upload artifacts',
|
||||||
|
commands: [
|
||||||
|
'./Scripts/drone-static-upload.sh'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
|
@ -1,3 +1,3 @@
|
||||||
[submodule "LibSession-Util"]
|
[submodule "LibSession-Util"]
|
||||||
path = LibSession-Util
|
path = LibSession-Util
|
||||||
url = git@github.com:oxen-io/libsession-util.git
|
url = https://github.com/oxen-io/libsession-util.git
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit e0b994201a016cc5bf9065526a0ceb4291f60d5a
|
Subproject commit d8f07fa92c12c5c2409774e03e03395d7847d1c2
|
14
Podfile
14
Podfile
|
@ -1,9 +1,10 @@
|
||||||
platform :ios, '13.0'
|
platform :ios, '13.0'
|
||||||
source 'https://github.com/CocoaPods/Specs.git'
|
|
||||||
|
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
inhibit_all_warnings!
|
inhibit_all_warnings!
|
||||||
|
|
||||||
|
install! 'cocoapods', :warn_for_unused_master_specs_repo => false
|
||||||
|
|
||||||
# Dependencies to be included in the app and all extensions/frameworks
|
# Dependencies to be included in the app and all extensions/frameworks
|
||||||
abstract_target 'GlobalDependencies' do
|
abstract_target 'GlobalDependencies' do
|
||||||
# FIXME: If https://github.com/jedisct1/swift-sodium/pull/249 gets resolved then revert this back to the standard pod
|
# FIXME: If https://github.com/jedisct1/swift-sodium/pull/249 gets resolved then revert this back to the standard pod
|
||||||
|
@ -22,7 +23,6 @@ abstract_target 'GlobalDependencies' do
|
||||||
pod 'PureLayout', '~> 3.1.8'
|
pod 'PureLayout', '~> 3.1.8'
|
||||||
pod 'NVActivityIndicatorView'
|
pod 'NVActivityIndicatorView'
|
||||||
pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage'
|
pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage'
|
||||||
pod 'ZXingObjC'
|
|
||||||
pod 'DifferenceKit'
|
pod 'DifferenceKit'
|
||||||
|
|
||||||
target 'SessionTests' do
|
target 'SessionTests' do
|
||||||
|
@ -101,7 +101,6 @@ end
|
||||||
# Actions to perform post-install
|
# Actions to perform post-install
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
set_minimum_deployment_target(installer)
|
set_minimum_deployment_target(installer)
|
||||||
avoid_rsync_webrtc_if_unchanged(installer)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_minimum_deployment_target(installer)
|
def set_minimum_deployment_target(installer)
|
||||||
|
@ -111,12 +110,3 @@ def set_minimum_deployment_target(installer)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# This function patches the Cocoapods 'Embed Frameworks' script to avoid running rsync
|
|
||||||
# for the WebRTC-lib framework in simulator builds if it has already been copied over
|
|
||||||
# because due to the size it can take over 10 seconds to embed, and gets embeded in
|
|
||||||
# each target on every build regardless of whether there were changes, drastically
|
|
||||||
# increasing the length of the build
|
|
||||||
def avoid_rsync_webrtc_if_unchanged(installer)
|
|
||||||
system('find "./Pods/Target Support Files" -name "*-frameworks.sh" -exec patch -p0 -i ./Scripts/skip_web_rtc_re_rsync.patch {} \;')
|
|
||||||
end
|
|
||||||
|
|
12
Podfile.lock
12
Podfile.lock
|
@ -41,7 +41,7 @@ PODS:
|
||||||
- SQLCipher/standard (4.5.3):
|
- SQLCipher/standard (4.5.3):
|
||||||
- SQLCipher/common
|
- SQLCipher/common
|
||||||
- SwiftProtobuf (1.5.0)
|
- SwiftProtobuf (1.5.0)
|
||||||
- WebRTC-lib (96.0.0)
|
- WebRTC-lib (114.0.0)
|
||||||
- YapDatabase/SQLCipher (3.1.1):
|
- YapDatabase/SQLCipher (3.1.1):
|
||||||
- YapDatabase/SQLCipher/Core (= 3.1.1)
|
- YapDatabase/SQLCipher/Core (= 3.1.1)
|
||||||
- YapDatabase/SQLCipher/Extensions (= 3.1.1)
|
- YapDatabase/SQLCipher/Extensions (= 3.1.1)
|
||||||
|
@ -108,9 +108,6 @@ PODS:
|
||||||
- YYImage/libwebp (1.0.4):
|
- YYImage/libwebp (1.0.4):
|
||||||
- libwebp
|
- libwebp
|
||||||
- YYImage/Core
|
- YYImage/Core
|
||||||
- ZXingObjC (3.6.5):
|
|
||||||
- ZXingObjC/All (= 3.6.5)
|
|
||||||
- ZXingObjC/All (3.6.5)
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`)
|
- Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`)
|
||||||
|
@ -129,7 +126,6 @@ DEPENDENCIES:
|
||||||
- WebRTC-lib
|
- WebRTC-lib
|
||||||
- YapDatabase/SQLCipher (from `https://github.com/oxen-io/session-ios-yap-database.git`, branch `signal-release`)
|
- YapDatabase/SQLCipher (from `https://github.com/oxen-io/session-ios-yap-database.git`, branch `signal-release`)
|
||||||
- YYImage/libwebp (from `https://github.com/signalapp/YYImage`)
|
- YYImage/libwebp (from `https://github.com/signalapp/YYImage`)
|
||||||
- ZXingObjC
|
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
https://github.com/CocoaPods/Specs.git:
|
https://github.com/CocoaPods/Specs.git:
|
||||||
|
@ -147,7 +143,6 @@ SPEC REPOS:
|
||||||
- SQLCipher
|
- SQLCipher
|
||||||
- SwiftProtobuf
|
- SwiftProtobuf
|
||||||
- WebRTC-lib
|
- WebRTC-lib
|
||||||
- ZXingObjC
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Curve25519Kit:
|
Curve25519Kit:
|
||||||
|
@ -199,11 +194,10 @@ SPEC CHECKSUMS:
|
||||||
Sodium: a7d42cb46e789d2630fa552d35870b416ed055ae
|
Sodium: a7d42cb46e789d2630fa552d35870b416ed055ae
|
||||||
SQLCipher: 57fa9f863fa4a3ed9dd3c90ace52315db8c0fdca
|
SQLCipher: 57fa9f863fa4a3ed9dd3c90ace52315db8c0fdca
|
||||||
SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2
|
SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2
|
||||||
WebRTC-lib: 508fe02efa0c1a3a8867082a77d24c9be5d29aeb
|
WebRTC-lib: d83df8976fa608b980f1d85796b3de66d60a1953
|
||||||
YapDatabase: b418a4baa6906e8028748938f9159807fd039af4
|
YapDatabase: b418a4baa6906e8028748938f9159807fd039af4
|
||||||
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
|
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
|
||||||
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: dcca0c4ad69b14cbc2d6ba49f9d690b239828e6d
|
PODFILE CHECKSUM: f56c28baefe3077effcb3a2ea5941b52c4cc6e86
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.12.1
|
||||||
|
|
|
@ -26,6 +26,7 @@ var pathFiles: [String] = {
|
||||||
return fileUrls
|
return fileUrls
|
||||||
.filter {
|
.filter {
|
||||||
((try? $0.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) && // No directories
|
((try? $0.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == false) && // No directories
|
||||||
|
!$0.path.contains("build/") && // Exclude files under the build folder (CI)
|
||||||
!$0.path.contains("Pods/") && // Exclude files under the pods folder
|
!$0.path.contains("Pods/") && // Exclude files under the pods folder
|
||||||
!$0.path.contains(".xcassets") && // Exclude asset bundles
|
!$0.path.contains(".xcassets") && // Exclude asset bundles
|
||||||
!$0.path.contains(".app/") && // Exclude files in the app build directories
|
!$0.path.contains(".app/") && // Exclude files in the app build directories
|
||||||
|
|
|
@ -27,37 +27,41 @@
|
||||||
|
|
||||||
# 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}
|
|
||||||
|
exec 3>&1 # Save original stdout
|
||||||
|
|
||||||
|
# Ensure the build directory exists (in case we need it before XCode creates it)
|
||||||
|
mkdir -p "${TARGET_BUILD_DIR}/libSessionUtil"
|
||||||
|
|
||||||
|
# Remove any old build errors
|
||||||
|
rm -rf "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log"
|
||||||
|
|
||||||
|
# Restore stdout and stderr and redirect it to the 'libsession_util_output.log' file
|
||||||
|
exec &> "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log"
|
||||||
|
|
||||||
|
# Define a function to echo a message.
|
||||||
|
function echo_message() {
|
||||||
|
exec 1>&3 # Restore stdout
|
||||||
|
echo "$1"
|
||||||
|
exec >> "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log" # Redirect all output to the log file
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_message "info: Validating build requirements"
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
# 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}"
|
||||||
|
|
||||||
# Remove any old build errors
|
|
||||||
rm -rf "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
|
||||||
|
|
||||||
# First ensure cmake is installed (store the error in a log and exit with a success status - xcode will output the error)
|
|
||||||
echo "info: Validating build requirements"
|
|
||||||
|
|
||||||
if ! which cmake > /dev/null; then
|
if ! which cmake > /dev/null; then
|
||||||
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
echo_message "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')."
|
||||||
echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')."
|
|
||||||
echo "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if we have the `LibSession-Util` submodule checked out and if not (depending on the 'SHOULD_AUTO_INIT_SUBMODULES' argument) perform the checkout
|
# Check if we have the `LibSession-Util` submodule checked out and if not (depending on the 'SHOULD_AUTO_INIT_SUBMODULES' argument) perform the checkout
|
||||||
if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ] || [ ! "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then
|
if [ ! -d "${SRCROOT}/LibSession-Util" ] || [ ! -d "${SRCROOT}/LibSession-Util/src" ] || [ ! "$(ls -A "${SRCROOT}/LibSession-Util")" ]; then
|
||||||
if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] & command -v git >/dev/null 2>&1; then
|
echo_message "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)."
|
||||||
echo "info: LibSession-Util submodule doesn't exist, resetting and checking out recusively now"
|
exit 0
|
||||||
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 0
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
are_submodules_valid() {
|
are_submodules_valid() {
|
||||||
local PARENT_PATH=$1
|
local PARENT_PATH=$1
|
||||||
|
@ -82,7 +86,7 @@ else
|
||||||
|
|
||||||
# If the child path doesn't exist then it's invalid
|
# If the child path doesn't exist then it's invalid
|
||||||
if [ ! -d "${PARENT_PATH}/${CHILD_PATH}" ]; then
|
if [ ! -d "${PARENT_PATH}/${CHILD_PATH}" ]; then
|
||||||
echo "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' doesn't exist."
|
echo_message "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' doesn't exist."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -90,7 +94,7 @@ else
|
||||||
local RESULT=$?
|
local RESULT=$?
|
||||||
|
|
||||||
if [ "${RESULT}" -eq 1 ]; then
|
if [ "${RESULT}" -eq 1 ]; then
|
||||||
echo "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' is in an invalid state."
|
echo_message "info: Submodule '${RELATIVE_PATH}/${CHILD_PATH}' is in an invalid state."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
@ -104,18 +108,8 @@ else
|
||||||
HAS_INVALID_SUBMODULE=$?
|
HAS_INVALID_SUBMODULE=$?
|
||||||
|
|
||||||
if [ "${HAS_INVALID_SUBMODULE}" -eq 1 ]; then
|
if [ "${HAS_INVALID_SUBMODULE}" -eq 1 ]; then
|
||||||
if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] && command -v git >/dev/null 2>&1; then
|
echo_message "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'."
|
||||||
echo "info: Submodules are in an invalid state, resetting and checking out recusively now"
|
exit 0
|
||||||
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 0
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -125,49 +119,143 @@ echo "info: Checking for changes to source"
|
||||||
NEW_SOURCE_HASH=$(find "${SRCROOT}/LibSession-Util/src" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}')
|
NEW_SOURCE_HASH=$(find "${SRCROOT}/LibSession-Util/src" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}')
|
||||||
NEW_HEADER_HASH=$(find "${SRCROOT}/LibSession-Util/include" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}')
|
NEW_HEADER_HASH=$(find "${SRCROOT}/LibSession-Util/include" -type f -exec md5 {} + | awk '{print $NF}' | sort | md5 | awk '{print $NF}')
|
||||||
|
|
||||||
if [ -f "${TARGET_BUILD_DIR}/libsession_util_source_hash.log" ]; then
|
if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log" ]; then
|
||||||
read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libsession_util_source_hash.log"
|
read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" ]; then
|
if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log" ]; then
|
||||||
read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libsession_util_header_hash.log"
|
read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "${TARGET_BUILD_DIR}/libsession_util_archs.log" ]; then
|
if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log" ]; then
|
||||||
read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libsession_util_archs.log"
|
read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start the libSession-util build if it doesn't already exists
|
# If all of the hashes match, the archs match and there is a library file then we can just stop here
|
||||||
if [ "${NEW_SOURCE_HASH}" != "${OLD_SOURCE_HASH}" ] || [ "${NEW_HEADER_HASH}" != "${OLD_HEADER_HASH}" ] || [ "${ARCHS[*]}" != "${OLD_ARCHS}" ] || [ ! -d "${TARGET_BUILD_DIR}/libsession-util.xcframework" ]; then
|
if [ "${NEW_SOURCE_HASH}" == "${OLD_SOURCE_HASH}" ] && [ "${NEW_HEADER_HASH}" == "${OLD_HEADER_HASH}" ] && [ "${ARCHS[*]}" == "${OLD_ARCHS}" ] && [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" ]; then
|
||||||
echo "info: Build is not up-to-date - creating new build"
|
echo_message "info: Build is up-to-date"
|
||||||
echo ""
|
exit 0
|
||||||
|
|
||||||
# Remove any existing build files (just to be safe)
|
|
||||||
rm -rf "${TARGET_BUILD_DIR}/libsession-util.a"
|
|
||||||
rm -rf "${TARGET_BUILD_DIR}/libsession-util.xcframework"
|
|
||||||
rm -rf "${BUILD_DIR}/libsession-util.xcframework"
|
|
||||||
|
|
||||||
# Trigger the new build
|
|
||||||
cd "${SRCROOT}/LibSession-Util"
|
|
||||||
result=$(./utils/ios.sh "libsession-util" false)
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
|
||||||
echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')."
|
|
||||||
echo "error: Failed to build libsession-util (See details in '${TARGET_BUILD_DIR}/pre-action-output.log')." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Save the updated source hash to disk to prevent rebuilds when there were no changes
|
|
||||||
echo "${NEW_SOURCE_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_source_hash.log"
|
|
||||||
echo "${NEW_HEADER_HASH}" > "${TARGET_BUILD_DIR}/libsession_util_header_hash.log"
|
|
||||||
echo "${ARCHS[*]}" > "${TARGET_BUILD_DIR}/libsession_util_archs.log"
|
|
||||||
echo ""
|
|
||||||
echo "info: Build complete"
|
|
||||||
else
|
|
||||||
echo "info: Build is up-to-date"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Move the target-specific libSession-util build to the parent build directory (so XCode can have a reference to a single build)
|
# If any of the above differ then we need to rebuild
|
||||||
rm -rf "${BUILD_DIR}/libsession-util.xcframework"
|
echo_message "info: Build is not up-to-date - creating new build"
|
||||||
cp -r "${TARGET_BUILD_DIR}/libsession-util.xcframework" "${BUILD_DIR}/libsession-util.xcframework"
|
|
||||||
|
# Import settings from XCode (defaulting values if not present)
|
||||||
|
VALID_SIM_ARCHS=(arm64 x86_64)
|
||||||
|
VALID_DEVICE_ARCHS=(arm64)
|
||||||
|
VALID_SIM_ARCH_PLATFORMS=(SIMULATORARM64 SIMULATOR64)
|
||||||
|
VALID_DEVICE_ARCH_PLATFORMS=(OS64)
|
||||||
|
|
||||||
|
OUTPUT_DIR="${TARGET_BUILD_DIR}"
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET=${IPHONEOS_DEPLOYMENT_TARGET}
|
||||||
|
ENABLE_BITCODE=${ENABLE_BITCODE}
|
||||||
|
|
||||||
|
# Generate the target architectures we want to build for
|
||||||
|
TARGET_ARCHS=()
|
||||||
|
TARGET_PLATFORMS=()
|
||||||
|
TARGET_SIM_ARCHS=()
|
||||||
|
TARGET_DEVICE_ARCHS=()
|
||||||
|
|
||||||
|
if [ -z $PLATFORM_NAME ] || [ $PLATFORM_NAME = "iphonesimulator" ]; then
|
||||||
|
for i in "${!VALID_SIM_ARCHS[@]}"; do
|
||||||
|
ARCH="${VALID_SIM_ARCHS[$i]}"
|
||||||
|
ARCH_PLATFORM="${VALID_SIM_ARCH_PLATFORMS[$i]}"
|
||||||
|
|
||||||
|
if [[ " ${ARCHS[*]} " =~ " ${ARCH} " ]]; then
|
||||||
|
TARGET_ARCHS+=("sim-${ARCH}")
|
||||||
|
TARGET_PLATFORMS+=("${ARCH_PLATFORM}")
|
||||||
|
TARGET_SIM_ARCHS+=("sim-${ARCH}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z $PLATFORM_NAME ] || [ $PLATFORM_NAME = "iphoneos" ]; then
|
||||||
|
for i in "${!VALID_DEVICE_ARCHS[@]}"; do
|
||||||
|
ARCH="${VALID_DEVICE_ARCHS[$i]}"
|
||||||
|
ARCH_PLATFORM="${VALID_DEVICE_ARCH_PLATFORMS[$i]}"
|
||||||
|
|
||||||
|
if [[ " ${ARCHS[*]} " =~ " ${ARCH} " ]]; then
|
||||||
|
TARGET_ARCHS+=("ios-${ARCH}")
|
||||||
|
TARGET_PLATFORMS+=("${ARCH_PLATFORM}")
|
||||||
|
TARGET_DEVICE_ARCHS+=("ios-${ARCH}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the individual architectures
|
||||||
|
for i in "${!TARGET_ARCHS[@]}"; do
|
||||||
|
build="${TARGET_BUILD_DIR}/libSessionUtil/${TARGET_ARCHS[$i]}"
|
||||||
|
platform="${TARGET_PLATFORMS[$i]}"
|
||||||
|
echo_message "Building ${TARGET_ARCHS[$i]} for $platform in $build"
|
||||||
|
|
||||||
|
cd "${SRCROOT}/LibSession-Util"
|
||||||
|
./utils/static-bundle.sh "$build" "" \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="${SRCROOT}/LibSession-Util/external/ios-cmake/ios.toolchain.cmake" \
|
||||||
|
-DPLATFORM=$platform \
|
||||||
|
-DDEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET \
|
||||||
|
-DENABLE_BITCODE=$ENABLE_BITCODE
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
LAST_OUTPUT=$(tail -n 4 "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_output.log" | head -n 1)
|
||||||
|
echo_message "error: $LAST_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Remove the old static library file
|
||||||
|
rm -rf "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a"
|
||||||
|
rm -rf "${TARGET_BUILD_DIR}/libSessionUtil/Headers"
|
||||||
|
|
||||||
|
# If needed combine simulator builds into a multi-arch lib
|
||||||
|
if [ "${#TARGET_SIM_ARCHS[@]}" -eq "1" ]; then
|
||||||
|
# Single device build
|
||||||
|
cp "${TARGET_BUILD_DIR}/libSessionUtil/${TARGET_SIM_ARCHS[0]}/libsession-util.a" "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a"
|
||||||
|
elif [ "${#TARGET_SIM_ARCHS[@]}" -gt "1" ]; then
|
||||||
|
# Combine multiple device builds into a multi-arch lib
|
||||||
|
echo_message "info: Built multiple architectures, merging into single static library"
|
||||||
|
lipo -create "${TARGET_BUILD_DIR}/libSessionUtil"/sim-*/libsession-util.a -output "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If needed combine device builds into a multi-arch lib
|
||||||
|
if [ "${#TARGET_DEVICE_ARCHS[@]}" -eq "1" ]; then
|
||||||
|
cp "${TARGET_BUILD_DIR}/libSessionUtil/${TARGET_DEVICE_ARCHS[0]}/libsession-util.a" "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a"
|
||||||
|
elif [ "${#TARGET_DEVICE_ARCHS[@]}" -gt "1" ]; then
|
||||||
|
# Combine multiple device builds into a multi-arch lib
|
||||||
|
echo_message "info: Built multiple architectures, merging into single static library"
|
||||||
|
lipo -create "${TARGET_BUILD_DIR}/libSessionUtil"/ios-*/libsession-util.a -output "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Save the updated hashes to disk to prevent rebuilds when there were no changes
|
||||||
|
echo "${NEW_SOURCE_HASH}" > "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log"
|
||||||
|
echo "${NEW_HEADER_HASH}" > "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log"
|
||||||
|
echo "${ARCHS[*]}" > "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log"
|
||||||
|
echo_message "info: Build complete"
|
||||||
|
|
||||||
|
# Copy the headers across
|
||||||
|
echo_message "info: Copy headers and prepare modulemap"
|
||||||
|
mkdir -p "${TARGET_BUILD_DIR}/libSessionUtil/Headers"
|
||||||
|
cp -r "${SRCROOT}/LibSession-Util/include/session" "${TARGET_BUILD_DIR}/libSessionUtil/Headers"
|
||||||
|
|
||||||
|
# The 'module.modulemap' is needed for XCode to be able to find the headers
|
||||||
|
modmap="${TARGET_BUILD_DIR}/libSessionUtil/Headers/module.modulemap"
|
||||||
|
echo "module SessionUtil {" >"$modmap"
|
||||||
|
echo " module capi {" >>"$modmap"
|
||||||
|
for x in $(cd include && find session -name '*.h'); do
|
||||||
|
echo " header \"$x\"" >>"$modmap"
|
||||||
|
done
|
||||||
|
echo -e " export *\n }" >>"$modmap"
|
||||||
|
if false; then
|
||||||
|
# If we include the cpp headers like this then Xcode will try to load them as C headers (which
|
||||||
|
# of course breaks) and doesn't provide any way to only load the ones you need (because this is
|
||||||
|
# Apple land, why would anything useful be available?). So we include the headers in the
|
||||||
|
# archive but can't let xcode discover them because it will do it wrong.
|
||||||
|
echo -e "\n module cppapi {" >>"$modmap"
|
||||||
|
for x in $(cd include && find session -name '*.hpp'); do
|
||||||
|
echo " header \"$x\"" >>"$modmap"
|
||||||
|
done
|
||||||
|
echo -e " export *\n }" >>"$modmap"
|
||||||
|
fi
|
||||||
|
echo "}" >>"$modmap"
|
||||||
|
|
||||||
|
# Output to XCode just so the output is good
|
||||||
|
echo_message "info: libSessionUtil Ready"
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Script used with Drone CI to upload build artifacts (because specifying all this in
|
||||||
|
# .drone.jsonnet is too painful).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
|
||||||
|
if [ -z "$SSH_KEY" ]; then
|
||||||
|
echo -e "\n\n\n\e[31;1mUnable to upload artifact: SSH_KEY not set\e[0m"
|
||||||
|
# Just warn but don't fail, so that this doesn't trigger a build failure for untrusted builds
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$SSH_KEY" >ssh_key
|
||||||
|
|
||||||
|
set -o xtrace # Don't start tracing until *after* we write the ssh key
|
||||||
|
|
||||||
|
chmod 600 ssh_key
|
||||||
|
|
||||||
|
if [ -n "$DRONE_TAG" ]; then
|
||||||
|
# For a tag build use something like `session-ios-v1.2.3`
|
||||||
|
base="session-ios-$DRONE_TAG"
|
||||||
|
else
|
||||||
|
# Otherwise build a length name from the datetime and commit hash, such as:
|
||||||
|
# session-ios-20200522T212342Z-04d7dcc54
|
||||||
|
base="session-ios-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -v "$base"
|
||||||
|
|
||||||
|
# Copy over the build products
|
||||||
|
prod_path="build/Session.xcarchive"
|
||||||
|
sim_path="build/Session_sim.xcarchive/Products/Applications/Session.app"
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
echo "Test" > "build/test.txt"
|
||||||
|
|
||||||
|
if [ ! -d $prod_path ]; then
|
||||||
|
cp -av $prod_path "$base"
|
||||||
|
else if [ ! -d $sim_path ]; then
|
||||||
|
cp -av $sim_path "$base"
|
||||||
|
else
|
||||||
|
echo "Expected a file to upload, found none" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# tar dat shiz up yo
|
||||||
|
archive="$base.tar.xz"
|
||||||
|
tar cJvf "$archive" "$base"
|
||||||
|
|
||||||
|
upload_to="oxen.rocks/${DRONE_REPO// /_}/${DRONE_BRANCH// /_}"
|
||||||
|
|
||||||
|
# sftp doesn't have any equivalent to mkdir -p, so we have to split the above up into a chain of
|
||||||
|
# -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail
|
||||||
|
# without error.
|
||||||
|
upload_dirs=(${upload_to//\// })
|
||||||
|
put_debug=
|
||||||
|
mkdirs=
|
||||||
|
dir_tmp=""
|
||||||
|
for p in "${upload_dirs[@]}"; do
|
||||||
|
dir_tmp="$dir_tmp$p/"
|
||||||
|
mkdirs="$mkdirs
|
||||||
|
-mkdir $dir_tmp"
|
||||||
|
done
|
||||||
|
|
||||||
|
sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks <<SFTP
|
||||||
|
$mkdirs
|
||||||
|
put $archive $upload_to
|
||||||
|
$put_debug
|
||||||
|
SFTP
|
||||||
|
|
||||||
|
set +o xtrace
|
||||||
|
|
||||||
|
echo -e "\n\n\n\n\e[32;1mUploaded to https://${upload_to}/${archive}\e[0m\n\n\n"
|
|
@ -1,12 +0,0 @@
|
||||||
@@ -41,0 +41,11 @@
|
|
||||||
+ # Skip the rsync step for the WebRTC-lib in simulator builds
|
|
||||||
+ if [[ "$PLATFORM_NAME" == "iphonesimulator" ]] && [[ "$source" == *WebRTC.framework* ]]; then
|
|
||||||
+ if [[ -f "${source}/../already_rsynced.nonce" ]]; then
|
|
||||||
+ echo "Already rsynced WebRTC, skipping"
|
|
||||||
+ return 0
|
|
||||||
+ fi
|
|
||||||
+
|
|
||||||
+ echo "About to rsync a simulator WebRTC, creating nonce to prevent future rsyncing"
|
|
||||||
+ touch "${source}/../already_rsynced.nonce"
|
|
||||||
+ fi
|
|
||||||
+
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,24 +5,6 @@
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<PreActions>
|
|
||||||
<ExecutionAction
|
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
|
||||||
<ActionContent
|
|
||||||
title = "Build libSession"
|
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
|
||||||
<EnvironmentBuildable>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
|
|
||||||
BuildableName = "Session.app"
|
|
||||||
BlueprintName = "Session"
|
|
||||||
ReferencedContainer = "container:Session.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</EnvironmentBuildable>
|
|
||||||
</ActionContent>
|
|
||||||
</ExecutionAction>
|
|
||||||
</PreActions>
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
@ -1,28 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1400"
|
LastUpgradeVersion = "1400"
|
||||||
version = "1.7">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<PreActions>
|
|
||||||
<ExecutionAction
|
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
|
||||||
<ActionContent
|
|
||||||
title = "Build libSession"
|
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
|
||||||
<EnvironmentBuildable>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "C3C2A6EF25539DE700C340D1"
|
|
||||||
BuildableName = "SessionMessagingKit.framework"
|
|
||||||
BlueprintName = "SessionMessagingKit"
|
|
||||||
ReferencedContainer = "container:Session.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</EnvironmentBuildable>
|
|
||||||
</ActionContent>
|
|
||||||
</ExecutionAction>
|
|
||||||
</PreActions>
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
@ -6,24 +6,6 @@
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<PreActions>
|
|
||||||
<ExecutionAction
|
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
|
||||||
<ActionContent
|
|
||||||
title = "Build libSession"
|
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
|
||||||
<EnvironmentBuildable>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "7BC01A3A241F40AB00BC7C55"
|
|
||||||
BuildableName = "SessionNotificationServiceExtension.appex"
|
|
||||||
BlueprintName = "SessionNotificationServiceExtension"
|
|
||||||
ReferencedContainer = "container:Session.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</EnvironmentBuildable>
|
|
||||||
</ActionContent>
|
|
||||||
</ExecutionAction>
|
|
||||||
</PreActions>
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
@ -6,24 +6,6 @@
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<PreActions>
|
|
||||||
<ExecutionAction
|
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
|
||||||
<ActionContent
|
|
||||||
title = "Build libSession"
|
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
|
||||||
<EnvironmentBuildable>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "453518671FC635DD00210559"
|
|
||||||
BuildableName = "SessionShareExtension.appex"
|
|
||||||
BlueprintName = "SessionShareExtension"
|
|
||||||
ReferencedContainer = "container:Session.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</EnvironmentBuildable>
|
|
||||||
</ActionContent>
|
|
||||||
</ExecutionAction>
|
|
||||||
</PreActions>
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
@ -1,28 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1400"
|
LastUpgradeVersion = "1400"
|
||||||
version = "1.7">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<PreActions>
|
|
||||||
<ExecutionAction
|
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
|
||||||
<ActionContent
|
|
||||||
title = "Build libSession"
|
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
|
||||||
<EnvironmentBuildable>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "C3C2A678255388CC00C340D1"
|
|
||||||
BuildableName = "SessionUtilitiesKit.framework"
|
|
||||||
BlueprintName = "SessionUtilitiesKit"
|
|
||||||
ReferencedContainer = "container:Session.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</EnvironmentBuildable>
|
|
||||||
</ActionContent>
|
|
||||||
</ExecutionAction>
|
|
||||||
</PreActions>
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
@ -1,28 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1400"
|
LastUpgradeVersion = "1400"
|
||||||
version = "1.7">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
<PreActions>
|
|
||||||
<ExecutionAction
|
|
||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
|
||||||
<ActionContent
|
|
||||||
title = "Build libSession"
|
|
||||||
scriptText = ""${SRCROOT}/Scripts/build_libSession_util.sh" ">
|
|
||||||
<EnvironmentBuildable>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "C33FD9AA255A548A00E217F9"
|
|
||||||
BuildableName = "SignalUtilitiesKit.framework"
|
|
||||||
BlueprintName = "SignalUtilitiesKit"
|
|
||||||
ReferencedContainer = "container:Session.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</EnvironmentBuildable>
|
|
||||||
</ActionContent>
|
|
||||||
</ExecutionAction>
|
|
||||||
</PreActions>
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629" : 9223372036854775807,
|
|
||||||
"01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" : 0,
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB" : 0,
|
|
||||||
"ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3" : 0,
|
|
||||||
"90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : 0,
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : 0,
|
|
||||||
"D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : 0,
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++692B8E4" : 9223372036854775807,
|
|
||||||
"37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : 0,
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE" : 0,
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : 9223372036854775807,
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758" : 0,
|
|
||||||
"37054CE35CE656680D6FFFA9EE19249E0D149C5E+++3F8B703" : 9223372036854775807,
|
|
||||||
"37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A" : 0,
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : 9223372036854775807
|
|
||||||
},
|
|
||||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "D0F297E7-A82D-4657-A941-96B268F80ABC",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629" : "Signal-iOS-2\/Carthage\/",
|
|
||||||
"01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC" : "SignalProtocolKit\/",
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB" : "Signal-iOS-2\/",
|
|
||||||
"ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3" : "SocketRocket\/",
|
|
||||||
"90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3" : "JSQMessagesViewController\/",
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A" : "Signal-iOS\/",
|
|
||||||
"D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249" : "Signal-iOS\/Pods\/",
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++692B8E4" : "Signal-iOS-4\/Carthage\/",
|
|
||||||
"37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4" : "SignalServiceKit\/",
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE" : "Signal-iOS-4\/",
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3" : "Signal-iOS\/Carthage\/",
|
|
||||||
"5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758" : "Signal-iOS-5\/",
|
|
||||||
"37054CE35CE656680D6FFFA9EE19249E0D149C5E+++3F8B703" : "SignalServiceKit-2\/",
|
|
||||||
"37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A" : "SignalServiceKit\/",
|
|
||||||
"8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255" : "Signal-iOS-5\/Carthage\/"
|
|
||||||
},
|
|
||||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "Signal",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Signal.xcworkspace",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalProtocolKit.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "01DE8628B025BC69C8C7D8B4612D57BE2C08B62C+++6A1C9FC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalServiceKit.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++3F8B703"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SignalProtocolKit.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++901E7D4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:FredericJacobs\/TextSecureKit.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37054CE35CE656680D6FFFA9EE19249E0D149C5E+++E57A04A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++03D0758"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++0BB03DB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++2D5CBAE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-iOS.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5D79A077E31B3FE97A3C6613CBFFDD71C314D14C+++ED4C31A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:michaelkirk\/Signal-Carthage.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++31C7255"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/WhisperSystems\/Signal-Carthage.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++692B8E4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/WhisperSystems\/Signal-Carthage.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++72E8629"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/Signal-Carthage.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8176314449001F06FB0E5B588C62133EAA2FE911+++E19D6E3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/JSQMessagesViewController.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "90530B99EB0008E7A50951FDFBE02169118FA649+++EF2C0B3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:WhisperSystems\/SocketRocket.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "ABB939127996C66F7E852A780552ADEEF03C6B13+++69179A3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/FredericJacobs\/Precompiled-Signal-Dependencies.git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
|
||||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D74FB800F048CB516BB4BC70047F7CC676D291B9+++375B249"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -453,6 +453,7 @@ extension ConversationVC:
|
||||||
self?.snInputView.quoteDraftInfo = nil
|
self?.snInputView.quoteDraftInfo = nil
|
||||||
|
|
||||||
self?.resetMentions()
|
self?.resetMentions()
|
||||||
|
self?.scrollToBottom(isAnimated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can
|
// Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can
|
||||||
|
@ -481,64 +482,70 @@ extension ConversationVC:
|
||||||
quoteModel: quoteModel
|
quoteModel: quoteModel
|
||||||
)
|
)
|
||||||
|
|
||||||
// Actually send the message
|
DispatchQueue.global(qos:.userInitiated).async {
|
||||||
Storage.shared
|
// Generate the quote thumbnail if needed (want this to happen outside of the DBWrite thread as
|
||||||
.writePublisher { [weak self] db in
|
// this can take up to 0.5s
|
||||||
// Update the thread to be visible (if it isn't already)
|
let quoteThumbnailAttachment: Attachment? = quoteModel?.attachment?.cloneAsQuoteThumbnail()
|
||||||
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
|
||||||
_ = try SessionThread
|
// Actually send the message
|
||||||
.filter(id: threadId)
|
Storage.shared
|
||||||
.updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true))
|
.writePublisher { [weak self] db in
|
||||||
|
// Update the thread to be visible (if it isn't already)
|
||||||
|
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
||||||
|
_ = try SessionThread
|
||||||
|
.filter(id: threadId)
|
||||||
|
.updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the interaction and associated it with the optimistically inserted message so
|
||||||
|
// we can remove it once the database triggers a UI update
|
||||||
|
let insertedInteraction: Interaction = try optimisticData.interaction.inserted(db)
|
||||||
|
self?.viewModel.associate(optimisticMessageId: optimisticData.id, to: insertedInteraction.id)
|
||||||
|
|
||||||
|
// If there is a LinkPreview and it doesn't match an existing one then add it now
|
||||||
|
if
|
||||||
|
let linkPreviewDraft: LinkPreviewDraft = linkPreviewDraft,
|
||||||
|
(try? insertedInteraction.linkPreview.isEmpty(db)) == true
|
||||||
|
{
|
||||||
|
try LinkPreview(
|
||||||
|
url: linkPreviewDraft.urlString,
|
||||||
|
title: linkPreviewDraft.title,
|
||||||
|
attachmentId: try optimisticData.linkPreviewAttachment?.inserted(db).id
|
||||||
|
).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a Quote the insert it now
|
||||||
|
if let interactionId: Int64 = insertedInteraction.id, let quoteModel: QuotedReplyModel = quoteModel {
|
||||||
|
try Quote(
|
||||||
|
interactionId: interactionId,
|
||||||
|
authorId: quoteModel.authorId,
|
||||||
|
timestampMs: quoteModel.timestampMs,
|
||||||
|
body: quoteModel.body,
|
||||||
|
attachmentId: try quoteThumbnailAttachment?.inserted(db).id
|
||||||
|
).insert(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any attachments
|
||||||
|
try Attachment.process(
|
||||||
|
db,
|
||||||
|
data: optimisticData.attachmentData,
|
||||||
|
for: insertedInteraction.id
|
||||||
|
)
|
||||||
|
|
||||||
|
try MessageSender.send(
|
||||||
|
db,
|
||||||
|
interaction: insertedInteraction,
|
||||||
|
threadId: threadId,
|
||||||
|
threadVariant: threadVariant
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
// Insert the interaction and associated it with the optimistically inserted message so
|
.sinkUntilComplete(
|
||||||
// we can remove it once the database triggers a UI update
|
receiveCompletion: { [weak self] _ in
|
||||||
let insertedInteraction: Interaction = try optimisticData.interaction.inserted(db)
|
self?.handleMessageSent()
|
||||||
self?.viewModel.associate(optimisticMessageId: optimisticData.id, to: insertedInteraction.id)
|
}
|
||||||
|
|
||||||
// If there is a LinkPreview and it doesn't match an existing one then add it now
|
|
||||||
if
|
|
||||||
let linkPreviewDraft: LinkPreviewDraft = linkPreviewDraft,
|
|
||||||
(try? insertedInteraction.linkPreview.isEmpty(db)) == true
|
|
||||||
{
|
|
||||||
try LinkPreview(
|
|
||||||
url: linkPreviewDraft.urlString,
|
|
||||||
title: linkPreviewDraft.title,
|
|
||||||
attachmentId: try optimisticData.linkPreviewAttachment?.inserted(db).id
|
|
||||||
).insert(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a Quote the insert it now
|
|
||||||
if let interactionId: Int64 = insertedInteraction.id, let quoteModel: QuotedReplyModel = quoteModel {
|
|
||||||
try Quote(
|
|
||||||
interactionId: interactionId,
|
|
||||||
authorId: quoteModel.authorId,
|
|
||||||
timestampMs: quoteModel.timestampMs,
|
|
||||||
body: quoteModel.body,
|
|
||||||
attachmentId: quoteModel.generateAttachmentThumbnailIfNeeded(db)
|
|
||||||
).insert(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process any attachments
|
|
||||||
try Attachment.process(
|
|
||||||
db,
|
|
||||||
data: optimisticData.attachmentData,
|
|
||||||
for: insertedInteraction.id
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
try MessageSender.send(
|
|
||||||
db,
|
|
||||||
interaction: insertedInteraction,
|
|
||||||
threadId: threadId,
|
|
||||||
threadVariant: threadVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
|
||||||
.sinkUntilComplete(
|
|
||||||
receiveCompletion: { [weak self] _ in
|
|
||||||
self?.handleMessageSent()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMessageSent() {
|
func handleMessageSent() {
|
||||||
|
|
|
@ -899,9 +899,34 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
|
||||||
|
|
||||||
// Store the 'sentMessageBeforeUpdate' state locally
|
// Store the 'sentMessageBeforeUpdate' state locally
|
||||||
let didSendMessageBeforeUpdate: Bool = self.viewModel.sentMessageBeforeUpdate
|
let didSendMessageBeforeUpdate: Bool = self.viewModel.sentMessageBeforeUpdate
|
||||||
|
let onlyReplacedOptimisticUpdate: Bool = {
|
||||||
|
// Replacing an optimistic update means making a delete and an insert, which will be done
|
||||||
|
// as separate changes at the same positions
|
||||||
|
guard
|
||||||
|
changeset.count > 1 &&
|
||||||
|
changeset[changeset.count - 2].elementDeleted == changeset[changeset.count - 1].elementInserted
|
||||||
|
else { return false }
|
||||||
|
|
||||||
|
let deletedModels: [MessageViewModel] = changeset[changeset.count - 2]
|
||||||
|
.elementDeleted
|
||||||
|
.map { self.viewModel.interactionData[$0.section].elements[$0.element] }
|
||||||
|
let insertedModels: [MessageViewModel] = changeset[changeset.count - 1]
|
||||||
|
.elementInserted
|
||||||
|
.map { updatedData[$0.section].elements[$0.element] }
|
||||||
|
|
||||||
|
// Make sure all the deleted models were optimistic updates, the inserted models were not
|
||||||
|
// optimistic updates and they have the same timestamps
|
||||||
|
return (
|
||||||
|
deletedModels.map { $0.id }.asSet() == [MessageViewModel.optimisticUpdateId] &&
|
||||||
|
insertedModels.map { $0.id }.asSet() != [MessageViewModel.optimisticUpdateId] &&
|
||||||
|
deletedModels.map { $0.timestampMs }.asSet() == insertedModels.map { $0.timestampMs }.asSet()
|
||||||
|
)
|
||||||
|
}()
|
||||||
let wasOnlyUpdates: Bool = (
|
let wasOnlyUpdates: Bool = (
|
||||||
changeset.count == 1 &&
|
onlyReplacedOptimisticUpdate || (
|
||||||
changeset[0].elementUpdated.count == changeset[0].changeCount
|
changeset.count == 1 &&
|
||||||
|
changeset[0].elementUpdated.count == changeset[0].changeCount
|
||||||
|
)
|
||||||
)
|
)
|
||||||
self.viewModel.sentMessageBeforeUpdate = false
|
self.viewModel.sentMessageBeforeUpdate = false
|
||||||
|
|
||||||
|
@ -912,13 +937,13 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
|
||||||
guard !didSendMessageBeforeUpdate && !wasOnlyUpdates else {
|
guard !didSendMessageBeforeUpdate && !wasOnlyUpdates else {
|
||||||
self.viewModel.updateInteractionData(updatedData)
|
self.viewModel.updateInteractionData(updatedData)
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
self.tableView.layoutIfNeeded()
|
|
||||||
|
|
||||||
// If we just sent a message then we want to jump to the bottom of the conversation instantly
|
// If we just sent a message then we want to jump to the bottom of the conversation instantly
|
||||||
if didSendMessageBeforeUpdate {
|
if didSendMessageBeforeUpdate {
|
||||||
// We need to dispatch to the next run loop because it seems trying to scroll immediately after
|
// We need to dispatch to the next run loop because it seems trying to scroll immediately after
|
||||||
// triggering a 'reloadData' doesn't work
|
// triggering a 'reloadData' doesn't work
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.tableView.layoutIfNeeded()
|
||||||
self?.scrollToBottom(isAnimated: false)
|
self?.scrollToBottom(isAnimated: false)
|
||||||
|
|
||||||
// Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to
|
// Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to
|
||||||
|
|
|
@ -165,12 +165,12 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) {
|
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) {
|
||||||
let hexEncodedPublicKey = string
|
let hexEncodedPublicKey = string
|
||||||
startNewDMIfPossible(with: hexEncodedPublicKey)
|
startNewDMIfPossible(with: hexEncodedPublicKey, onError: onError)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String) {
|
fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String, onError: (() -> ())?) {
|
||||||
let maybeSessionId: SessionId? = SessionId(from: onsNameOrPublicKey)
|
let maybeSessionId: SessionId? = SessionId(from: onsNameOrPublicKey)
|
||||||
|
|
||||||
if KeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) {
|
if KeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) {
|
||||||
|
@ -185,7 +185,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
|
||||||
title: "ALERT_ERROR_TITLE".localized(),
|
title: "ALERT_ERROR_TITLE".localized(),
|
||||||
body: .text("DM_ERROR_DIRECT_BLINDED_ID".localized()),
|
body: .text("DM_ERROR_DIRECT_BLINDED_ID".localized()),
|
||||||
cancelTitle: "BUTTON_OK".localized(),
|
cancelTitle: "BUTTON_OK".localized(),
|
||||||
cancelStyle: .alert_text
|
cancelStyle: .alert_text,
|
||||||
|
afterClosed: onError
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.present(modal, animated: true)
|
self.present(modal, animated: true)
|
||||||
|
@ -197,7 +198,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
|
||||||
title: "ALERT_ERROR_TITLE".localized(),
|
title: "ALERT_ERROR_TITLE".localized(),
|
||||||
body: .text("DM_ERROR_INVALID".localized()),
|
body: .text("DM_ERROR_INVALID".localized()),
|
||||||
cancelTitle: "BUTTON_OK".localized(),
|
cancelTitle: "BUTTON_OK".localized(),
|
||||||
cancelStyle: .alert_text
|
cancelStyle: .alert_text,
|
||||||
|
afterClosed: onError
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.present(modal, animated: true)
|
self.present(modal, animated: true)
|
||||||
|
@ -243,7 +245,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
|
||||||
title: "ALERT_ERROR_TITLE".localized(),
|
title: "ALERT_ERROR_TITLE".localized(),
|
||||||
body: .text(message),
|
body: .text(message),
|
||||||
cancelTitle: "BUTTON_OK".localized(),
|
cancelTitle: "BUTTON_OK".localized(),
|
||||||
cancelStyle: .alert_text
|
cancelStyle: .alert_text,
|
||||||
|
afterClosed: onError
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self?.present(modal, animated: true)
|
self?.present(modal, animated: true)
|
||||||
|
@ -663,7 +666,7 @@ private final class EnterPublicKeyVC: UIViewController {
|
||||||
|
|
||||||
@objc fileprivate func startNewDMIfPossible() {
|
@objc fileprivate func startNewDMIfPossible() {
|
||||||
let text = publicKeyTextView.text?.trimmingCharacters(in: .whitespaces) ?? ""
|
let text = publicKeyTextView.text?.trimmingCharacters(in: .whitespaces) ?? ""
|
||||||
NewDMVC.startNewDMIfPossible(with: text)
|
NewDMVC.startNewDMIfPossible(with: text, onError: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -299,7 +299,7 @@ enum GiphyAPI {
|
||||||
return HTTPError.generic
|
return HTTPError.generic
|
||||||
}
|
}
|
||||||
.map { data, _ in
|
.map { data, _ in
|
||||||
Logger.error("search request succeeded")
|
Logger.debug("search request succeeded")
|
||||||
|
|
||||||
guard let imageInfos = self.parseGiphyImages(responseData: data) else {
|
guard let imageInfos = self.parseGiphyImages(responseData: data) else {
|
||||||
Logger.error("unable to parse trending images")
|
Logger.error("unable to parse trending images")
|
||||||
|
@ -347,7 +347,7 @@ enum GiphyAPI {
|
||||||
return HTTPError.generic
|
return HTTPError.generic
|
||||||
}
|
}
|
||||||
.tryMap { data, _ -> [GiphyImageInfo] in
|
.tryMap { data, _ -> [GiphyImageInfo] in
|
||||||
Logger.error("search request succeeded")
|
Logger.debug("search request succeeded")
|
||||||
|
|
||||||
guard let imageInfos = self.parseGiphyImages(responseData: data) else {
|
guard let imageInfos = self.parseGiphyImages(responseData: data) else {
|
||||||
throw HTTPError.invalidResponse
|
throw HTTPError.invalidResponse
|
||||||
|
|
|
@ -2,13 +2,6 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>BuildDetails</key>
|
|
||||||
<dict>
|
|
||||||
<key>CarthageVersion</key>
|
|
||||||
<string>0.36.0</string>
|
|
||||||
<key>OSXVersion</key>
|
|
||||||
<string>10.15.6</string>
|
|
||||||
</dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
|
@ -88,7 +81,7 @@
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Session needs camera access to take pictures and scan QR codes.</string>
|
<string>Session needs camera access to take pictures and scan QR codes.</string>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<string>Session's Screen Lock feature uses Face ID.</string>
|
<string>Session's Screen Lock feature uses Face ID.</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>com.loki-project.loki-messenger</string>
|
<string>com.loki-project.loki-messenger</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
|
|
@ -134,12 +134,12 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) {
|
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) {
|
||||||
let seed = Data(hex: string)
|
let seed = Data(hex: string)
|
||||||
continueWithSeed(seed)
|
continueWithSeed(seed, onError: onError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func continueWithSeed(_ seed: Data) {
|
func continueWithSeed(_ seed: Data, onError: (() -> ())?) {
|
||||||
if (seed.count != 16) {
|
if (seed.count != 16) {
|
||||||
let modal: ConfirmationModal = ConfirmationModal(
|
let modal: ConfirmationModal = ConfirmationModal(
|
||||||
info: ConfirmationModal.Info(
|
info: ConfirmationModal.Info(
|
||||||
|
@ -147,9 +147,7 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont
|
||||||
body: .text("INVALID_RECOVERY_PHRASE_MESSAGE".localized()),
|
body: .text("INVALID_RECOVERY_PHRASE_MESSAGE".localized()),
|
||||||
cancelTitle: "BUTTON_OK".localized(),
|
cancelTitle: "BUTTON_OK".localized(),
|
||||||
cancelStyle: .alert_text,
|
cancelStyle: .alert_text,
|
||||||
afterClosed: { [weak self] in
|
afterClosed: onError
|
||||||
self?.scanQRCodeWrapperVC.startCapture()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
present(modal, animated: true)
|
present(modal, animated: true)
|
||||||
|
@ -319,7 +317,7 @@ private final class RecoveryPhraseVC: UIViewController {
|
||||||
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
|
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
|
||||||
let seed = Data(hex: hexEncodedSeed)
|
let seed = Data(hex: hexEncodedSeed)
|
||||||
mnemonicTextView.resignFirstResponder()
|
mnemonicTextView.resignFirstResponder()
|
||||||
linkDeviceVC.continueWithSeed(seed)
|
linkDeviceVC.continueWithSeed(seed, onError: nil)
|
||||||
} catch let error {
|
} catch let error {
|
||||||
let error = error as? Mnemonic.DecodingError ?? Mnemonic.DecodingError.generic
|
let error = error as? Mnemonic.DecodingError ?? Mnemonic.DecodingError.generic
|
||||||
showError(title: error.errorDescription!)
|
showError(title: error.errorDescription!)
|
||||||
|
|
|
@ -144,25 +144,26 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) {
|
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) {
|
||||||
joinOpenGroup(with: string)
|
joinOpenGroup(with: string, onError: onError)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func joinOpenGroup(with urlString: String) {
|
fileprivate func joinOpenGroup(with urlString: String, onError: (() -> ())?) {
|
||||||
// A V2 open group URL will look like: <optional scheme> + <host> + <optional port> + <room> + <public key>
|
// A V2 open group URL will look like: <optional scheme> + <host> + <optional port> + <room> + <public key>
|
||||||
// The host doesn't parse if no explicit scheme is provided
|
// The host doesn't parse if no explicit scheme is provided
|
||||||
guard let (room, server, publicKey) = SessionUtil.parseCommunity(url: urlString) else {
|
guard let (room, server, publicKey) = SessionUtil.parseCommunity(url: urlString) else {
|
||||||
showError(
|
showError(
|
||||||
title: "invalid_url".localized(),
|
title: "invalid_url".localized(),
|
||||||
message: "COMMUNITY_ERROR_INVALID_URL".localized()
|
message: "COMMUNITY_ERROR_INVALID_URL".localized(),
|
||||||
|
onError: onError
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
joinOpenGroup(roomToken: room, server: server, publicKey: publicKey, shouldOpenCommunity: true)
|
joinOpenGroup(roomToken: room, server: server, publicKey: publicKey, shouldOpenCommunity: true, onError: onError)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool) {
|
fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool, onError: (() -> ())?) {
|
||||||
guard !isJoining, let navigationController: UINavigationController = navigationController else { return }
|
guard !isJoining, let navigationController: UINavigationController = navigationController else { return }
|
||||||
|
|
||||||
isJoining = true
|
isJoining = true
|
||||||
|
@ -209,7 +210,8 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
|
||||||
self?.dismiss(animated: true) { // Dismiss the loader
|
self?.dismiss(animated: true) { // Dismiss the loader
|
||||||
self?.showError(
|
self?.showError(
|
||||||
title: "COMMUNITY_ERROR_GENERIC".localized(),
|
title: "COMMUNITY_ERROR_GENERIC".localized(),
|
||||||
message: error.localizedDescription
|
message: error.localizedDescription,
|
||||||
|
onError: onError
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,13 +234,14 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
||||||
private func showError(title: String, message: String = "") {
|
private func showError(title: String, message: String = "", onError: (() -> ())?) {
|
||||||
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
let confirmationModal: ConfirmationModal = ConfirmationModal(
|
||||||
info: ConfirmationModal.Info(
|
info: ConfirmationModal.Info(
|
||||||
title: title,
|
title: title,
|
||||||
body: .text(message),
|
body: .text(message),
|
||||||
cancelTitle: "BUTTON_OK".localized(),
|
cancelTitle: "BUTTON_OK".localized(),
|
||||||
cancelStyle: .alert_text
|
cancelStyle: .alert_text,
|
||||||
|
afterClosed: onError
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.navigationController?.present(confirmationModal, animated: true, completion: nil)
|
self.navigationController?.present(confirmationModal, animated: true, completion: nil)
|
||||||
|
@ -399,13 +402,14 @@ private final class EnterURLVC: UIViewController, UIGestureRecognizerDelegate, O
|
||||||
roomToken: room.token,
|
roomToken: room.token,
|
||||||
server: OpenGroupAPI.defaultServer,
|
server: OpenGroupAPI.defaultServer,
|
||||||
publicKey: OpenGroupAPI.defaultServerPublicKey,
|
publicKey: OpenGroupAPI.defaultServerPublicKey,
|
||||||
shouldOpenCommunity: true
|
shouldOpenCommunity: true,
|
||||||
|
onError: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func joinOpenGroup() {
|
@objc private func joinOpenGroup() {
|
||||||
let url = urlTextView.text?.trimmingCharacters(in: .whitespaces) ?? ""
|
let url = urlTextView.text?.trimmingCharacters(in: .whitespaces) ?? ""
|
||||||
joinOpenGroupVC?.joinOpenGroup(with: url)
|
joinOpenGroupVC?.joinOpenGroup(with: url, onError: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Updating
|
// MARK: - Updating
|
||||||
|
|
|
@ -119,12 +119,12 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) {
|
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) {
|
||||||
let hexEncodedPublicKey = string
|
let hexEncodedPublicKey = string
|
||||||
startNewPrivateChatIfPossible(with: hexEncodedPublicKey)
|
startNewPrivateChatIfPossible(with: hexEncodedPublicKey, onError: onError)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String) {
|
fileprivate func startNewPrivateChatIfPossible(with hexEncodedPublicKey: String, onError: (() -> ())?) {
|
||||||
if !KeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
|
if !KeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
|
||||||
let modal: ConfirmationModal = ConfirmationModal(
|
let modal: ConfirmationModal = ConfirmationModal(
|
||||||
targetView: self.view,
|
targetView: self.view,
|
||||||
|
@ -132,7 +132,8 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl
|
||||||
title: "invalid_session_id".localized(),
|
title: "invalid_session_id".localized(),
|
||||||
body: .text("INVALID_SESSION_ID_MESSAGE".localized()),
|
body: .text("INVALID_SESSION_ID_MESSAGE".localized()),
|
||||||
cancelTitle: "BUTTON_OK".localized(),
|
cancelTitle: "BUTTON_OK".localized(),
|
||||||
cancelStyle: .alert_text
|
cancelStyle: .alert_text,
|
||||||
|
afterClosed: onError
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.present(modal, animated: true)
|
self.present(modal, animated: true)
|
||||||
|
|
|
@ -2,55 +2,36 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
import ZXingObjC
|
|
||||||
import SessionUIKit
|
import SessionUIKit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
protocol QRScannerDelegate: AnyObject {
|
protocol QRScannerDelegate: AnyObject {
|
||||||
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String)
|
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ZXCaptureDelegate {
|
class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
||||||
public weak var scanDelegate: QRScannerDelegate?
|
public weak var scanDelegate: QRScannerDelegate?
|
||||||
|
|
||||||
private let captureQueue: DispatchQueue = DispatchQueue.global(qos: .default)
|
private let captureQueue: DispatchQueue = DispatchQueue.global(qos: .default)
|
||||||
private var capture: ZXCapture?
|
private var capture: AVCaptureSession?
|
||||||
|
private var captureLayer: AVCaptureVideoPreviewLayer?
|
||||||
private var captureEnabled: Bool = false
|
private var captureEnabled: Bool = false
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.capture?.layer.removeFromSuperlayer()
|
self.captureLayer?.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Components
|
// MARK: - Components
|
||||||
|
|
||||||
private let maskingView: UIView = {
|
private let maskingView: UIView = UIView()
|
||||||
let result: OWSBezierPathView = OWSBezierPathView()
|
|
||||||
result.configureShapeLayerBlock = { layer, bounds in
|
private lazy var maskLayer: CAShapeLayer = {
|
||||||
// Add a circular mask
|
let result: CAShapeLayer = CAShapeLayer()
|
||||||
let path: UIBezierPath = UIBezierPath(rect: bounds)
|
result.fillRule = .evenOdd
|
||||||
let margin: CGFloat = ScaleFromIPhone5To7Plus(24, 48)
|
result.themeFillColor = .black
|
||||||
let radius: CGFloat = ((min(bounds.size.width, bounds.size.height) * 0.5) - margin)
|
result.opacity = 0.32
|
||||||
|
|
||||||
// Center the circle's bounding rectangle
|
|
||||||
let circleRect: CGRect = CGRect(
|
|
||||||
x: ((bounds.size.width * 0.5) - radius),
|
|
||||||
y: ((bounds.size.height * 0.5) - radius),
|
|
||||||
width: (radius * 2),
|
|
||||||
height: (radius * 2)
|
|
||||||
)
|
|
||||||
let circlePath: UIBezierPath = UIBezierPath.init(
|
|
||||||
roundedRect: circleRect,
|
|
||||||
cornerRadius: 16
|
|
||||||
)
|
|
||||||
path.append(circlePath)
|
|
||||||
path.usesEvenOddFillRule = true
|
|
||||||
|
|
||||||
layer.path = path.cgPath
|
|
||||||
layer.fillRule = .evenOdd
|
|
||||||
layer.themeFillColor = .black
|
|
||||||
layer.opacity = 0.32
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}()
|
}()
|
||||||
|
@ -61,7 +42,8 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj
|
||||||
super.loadView()
|
super.loadView()
|
||||||
|
|
||||||
self.view.addSubview(maskingView)
|
self.view.addSubview(maskingView)
|
||||||
maskingView.pin(to: self.view)
|
|
||||||
|
maskingView.layer.addSublayer(maskLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
@ -81,11 +63,28 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj
|
||||||
override func viewWillLayoutSubviews() {
|
override func viewWillLayoutSubviews() {
|
||||||
super.viewWillLayoutSubviews()
|
super.viewWillLayoutSubviews()
|
||||||
|
|
||||||
// Note: When accessing 'capture.layer' if the setup hasn't been completed it
|
captureLayer?.frame = self.view.bounds
|
||||||
// will result in a layout being triggered which creates an infinite loop, this
|
|
||||||
// check prevents that case
|
if maskingView.frame != self.view.bounds {
|
||||||
if let capture: ZXCapture = self.capture {
|
// Add a circular mask
|
||||||
capture.layer.frame = self.view.bounds
|
let path: UIBezierPath = UIBezierPath(rect: self.view.bounds)
|
||||||
|
let radius: CGFloat = ((min(self.view.bounds.size.width, self.view.bounds.size.height) * 0.5) - Values.largeSpacing)
|
||||||
|
|
||||||
|
// Center the circle's bounding rectangle
|
||||||
|
let circleRect: CGRect = CGRect(
|
||||||
|
x: ((self.view.bounds.size.width * 0.5) - radius),
|
||||||
|
y: ((self.view.bounds.size.height * 0.5) - radius),
|
||||||
|
width: (radius * 2),
|
||||||
|
height: (radius * 2)
|
||||||
|
)
|
||||||
|
let clippingPath: UIBezierPath = UIBezierPath.init(
|
||||||
|
roundedRect: circleRect,
|
||||||
|
cornerRadius: 16
|
||||||
|
)
|
||||||
|
path.append(clippingPath)
|
||||||
|
path.usesEvenOddFillRule = true
|
||||||
|
|
||||||
|
maskLayer.path = path.cgPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,31 +100,76 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj
|
||||||
#else
|
#else
|
||||||
if self.capture == nil {
|
if self.capture == nil {
|
||||||
self.captureQueue.async { [weak self] in
|
self.captureQueue.async { [weak self] in
|
||||||
let capture: ZXCapture = ZXCapture()
|
let maybeDevice: AVCaptureDevice? = {
|
||||||
capture.camera = capture.back()
|
if let result = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back) {
|
||||||
capture.focusMode = .autoFocus
|
return result
|
||||||
capture.delegate = self
|
}
|
||||||
capture.start()
|
|
||||||
|
return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
|
||||||
|
}()
|
||||||
|
|
||||||
// Note: When accessing the 'layer' for the first time it will create
|
// Set the input device to autoFocus (since we don't have the interaction setup for
|
||||||
// an instance of 'AVCaptureVideoPreviewLayer', this can hang a little
|
// doing it manually)
|
||||||
// so we do this on the background thread first
|
maybeDevice?.focusMode = .continuousAutoFocus
|
||||||
if capture.layer != nil {}
|
|
||||||
|
// Device input
|
||||||
|
guard
|
||||||
|
let device: AVCaptureDevice = maybeDevice,
|
||||||
|
let input: AVCaptureInput = try? AVCaptureDeviceInput(device: device)
|
||||||
|
else {
|
||||||
|
return SNLog("Failed to retrieve the device for enabling the QRCode scanning camera")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image output
|
||||||
|
let output: AVCaptureVideoDataOutput = AVCaptureVideoDataOutput()
|
||||||
|
output.alwaysDiscardsLateVideoFrames = true
|
||||||
|
|
||||||
|
// Metadata output the session
|
||||||
|
let metadataOutput: AVCaptureMetadataOutput = AVCaptureMetadataOutput()
|
||||||
|
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
||||||
|
|
||||||
|
let capture: AVCaptureSession = AVCaptureSession()
|
||||||
|
capture.beginConfiguration()
|
||||||
|
if capture.canAddInput(input) { capture.addInput(input) }
|
||||||
|
if capture.canAddOutput(output) { capture.addOutput(output) }
|
||||||
|
if capture.canAddOutput(metadataOutput) { capture.addOutput(metadataOutput) }
|
||||||
|
|
||||||
|
guard !capture.inputs.isEmpty && capture.outputs.count == 2 else {
|
||||||
|
return SNLog("Failed to attach the input/output to the capture session")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard metadataOutput.availableMetadataObjectTypes.contains(.qr) else {
|
||||||
|
return SNLog("The output is unable to process QR codes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify that we want to capture QR Codes (Needs to be done after being added
|
||||||
|
// to the session, 'availableMetadataObjectTypes' is empty beforehand)
|
||||||
|
metadataOutput.metadataObjectTypes = [.qr]
|
||||||
|
|
||||||
|
capture.commitConfiguration()
|
||||||
|
|
||||||
|
// Create the layer for rendering the camera video
|
||||||
|
let layer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: capture)
|
||||||
|
layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||||
|
|
||||||
|
// Start running the capture session
|
||||||
|
capture.startRunning()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
capture.layer.frame = (self?.view.bounds ?? .zero)
|
layer.frame = (self?.view.bounds ?? .zero)
|
||||||
self?.view.layer.addSublayer(capture.layer)
|
self?.view.layer.addSublayer(layer)
|
||||||
|
|
||||||
if let maskingView: UIView = self?.maskingView {
|
if let maskingView: UIView = self?.maskingView {
|
||||||
self?.view.bringSubviewToFront(maskingView)
|
self?.view.bringSubviewToFront(maskingView)
|
||||||
}
|
}
|
||||||
|
|
||||||
self?.capture = capture
|
self?.capture = capture
|
||||||
|
self?.captureLayer = layer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.capture?.start()
|
self.capture?.startRunning()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -133,18 +177,25 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj
|
||||||
private func stopCapture() {
|
private func stopCapture() {
|
||||||
self.captureEnabled = false
|
self.captureEnabled = false
|
||||||
self.captureQueue.async { [weak self] in
|
self.captureQueue.async { [weak self] in
|
||||||
self?.capture?.stop()
|
self?.capture?.stopRunning()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func captureResult(_ capture: ZXCapture, result: ZXResult) {
|
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
||||||
guard self.captureEnabled else { return }
|
guard
|
||||||
|
self.captureEnabled,
|
||||||
|
let metadata: AVMetadataObject = metadataObjects.first(where: { ($0 as? AVMetadataMachineReadableCodeObject)?.type == .qr }),
|
||||||
|
let qrCodeInfo: AVMetadataMachineReadableCodeObject = metadata as? AVMetadataMachineReadableCodeObject,
|
||||||
|
let qrCode: String = qrCodeInfo.stringValue
|
||||||
|
else { return }
|
||||||
|
|
||||||
self.stopCapture()
|
self.stopCapture()
|
||||||
|
|
||||||
// Vibrate
|
// Vibrate
|
||||||
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
||||||
|
|
||||||
self.scanDelegate?.controller(self, didDetectQRCodeWith: result.text)
|
self.scanDelegate?.controller(self, didDetectQRCodeWith: qrCode) { [weak self] in
|
||||||
|
self?.startCapture()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionUtil
|
|
||||||
import SessionSnodeKit
|
import SessionSnodeKit
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,7 @@ public enum MessageSendJob: JobExecutor {
|
||||||
/// already have attachments in a valid state
|
/// already have attachments in a valid state
|
||||||
if
|
if
|
||||||
details.message is VisibleMessage,
|
details.message is VisibleMessage,
|
||||||
(details.message as? VisibleMessage)?.reaction == nil &&
|
(details.message as? VisibleMessage)?.reaction == nil
|
||||||
details.isSyncMessage == false
|
|
||||||
{
|
{
|
||||||
guard
|
guard
|
||||||
let jobId: Int64 = job.id,
|
let jobId: Int64 = job.id,
|
||||||
|
@ -51,122 +50,111 @@ public enum MessageSendJob: JobExecutor {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the original interaction no longer exists then don't bother sending the message (ie. the
|
// Retrieve the current attachment state
|
||||||
// message was deleted before it even got sent)
|
typealias AttachmentState = (error: Error?, pendingUploadAttachmentIds: [String], preparedFileIds: [String])
|
||||||
guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
|
|
||||||
SNLog("[MessageSendJob] Failing due to missing interaction")
|
let attachmentState: AttachmentState = Storage.shared
|
||||||
failure(job, StorageError.objectNotFound, true)
|
.read { db in
|
||||||
return
|
// If the original interaction no longer exists then don't bother sending the message (ie. the
|
||||||
}
|
// message was deleted before it even got sent)
|
||||||
|
guard try Interaction.exists(db, id: interactionId) else {
|
||||||
// Check if there are any attachments associated to this message, and if so
|
SNLog("[MessageSendJob] Failing due to missing interaction")
|
||||||
// upload them now
|
return (StorageError.objectNotFound, [], [])
|
||||||
//
|
}
|
||||||
// Note: Normal attachments should be sent in a non-durable way but any
|
|
||||||
// attachments for LinkPreviews and Quotes will be processed through this mechanism
|
// Get the current state of the attachments
|
||||||
let attachmentState: (shouldFail: Bool, shouldDefer: Bool, fileIds: [String])? = Storage.shared.write { db in
|
let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment
|
||||||
let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment
|
.stateInfo(interactionId: interactionId)
|
||||||
.stateInfo(interactionId: interactionId)
|
.fetchAll(db)
|
||||||
.fetchAll(db)
|
let maybeFileIds: [String?] = allAttachmentStateInfo
|
||||||
let maybeFileIds: [String?] = allAttachmentStateInfo
|
.sorted { lhs, rhs in lhs.albumIndex < rhs.albumIndex }
|
||||||
.sorted { lhs, rhs in lhs.albumIndex < rhs.albumIndex }
|
.map { Attachment.fileId(for: $0.downloadUrl) }
|
||||||
.map { Attachment.fileId(for: $0.downloadUrl) }
|
let fileIds: [String] = maybeFileIds.compactMap { $0 }
|
||||||
let fileIds: [String] = maybeFileIds.compactMap { $0 }
|
|
||||||
|
// If there were failed attachments then this job should fail (can't send a
|
||||||
// If there were failed attachments then this job should fail (can't send a
|
// message which has associated attachments if the attachments fail to upload)
|
||||||
// message which has associated attachments if the attachments fail to upload)
|
guard !allAttachmentStateInfo.contains(where: { $0.state == .failedDownload }) else {
|
||||||
guard !allAttachmentStateInfo.contains(where: { $0.state == .failedDownload }) else {
|
SNLog("[MessageSendJob] Failing due to failed attachment upload")
|
||||||
return (true, false, fileIds)
|
return (AttachmentError.notUploaded, [], fileIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create jobs for any pending (or failed) attachment jobs and insert them into the
|
/// Find all attachmentIds for attachments which need to be uploaded
|
||||||
// queue before the current job (this will mean the current job will re-run
|
///
|
||||||
// after these inserted jobs complete)
|
/// **Note:** If there are any 'downloaded' attachments then they also need to be uploaded (as a
|
||||||
//
|
/// 'downloaded' attachment will be on the current users device but not on the message recipients
|
||||||
// Note: If there are any 'downloaded' attachments then they also need to be
|
/// device - both `LinkPreview` and `Quote` can have this case)
|
||||||
// uploaded (as a 'downloaded' attachment will be on the current users device
|
let pendingUploadAttachmentIds: [String] = allAttachmentStateInfo
|
||||||
// but not on the message recipients device - both LinkPreview and Quote can
|
.filter { attachment -> Bool in
|
||||||
// have this case)
|
// Non-media quotes won't have thumbnails so so don't try to upload them
|
||||||
try allAttachmentStateInfo
|
guard attachment.downloadUrl != Attachment.nonMediaQuoteFileId else { return false }
|
||||||
.filter { attachment -> Bool in
|
|
||||||
// Non-media quotes won't have thumbnails so so don't try to upload them
|
switch attachment.state {
|
||||||
guard attachment.downloadUrl != Attachment.nonMediaQuoteFileId else { return false }
|
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
|
||||||
|
return true
|
||||||
switch attachment.state {
|
|
||||||
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
|
default: return false
|
||||||
return true
|
}
|
||||||
|
|
||||||
default: return false
|
|
||||||
}
|
}
|
||||||
}
|
.map { $0.attachmentId }
|
||||||
.filter { stateInfo in
|
|
||||||
// Don't add a new job if there is one already in the queue
|
return (nil, pendingUploadAttachmentIds, fileIds)
|
||||||
!JobRunner.hasPendingOrRunningJob(
|
}
|
||||||
with: .attachmentUpload,
|
.defaulting(to: (MessageSenderError.invalidMessage, [], []))
|
||||||
details: AttachmentUploadJob.Details(
|
|
||||||
messageSendJobId: jobId,
|
/// If we got an error when trying to retrieve the attachment state then this job is actually invalid so it
|
||||||
attachmentId: stateInfo.attachmentId
|
/// should permanently fail
|
||||||
)
|
guard attachmentState.error == nil else {
|
||||||
)
|
return failure(job, (attachmentState.error ?? MessageSenderError.invalidMessage), true)
|
||||||
}
|
|
||||||
.compactMap { stateInfo -> (jobId: Int64, job: Job)? in
|
|
||||||
JobRunner
|
|
||||||
.insert(
|
|
||||||
db,
|
|
||||||
job: Job(
|
|
||||||
variant: .attachmentUpload,
|
|
||||||
behaviour: .runOnce,
|
|
||||||
threadId: job.threadId,
|
|
||||||
interactionId: interactionId,
|
|
||||||
details: AttachmentUploadJob.Details(
|
|
||||||
messageSendJobId: jobId,
|
|
||||||
attachmentId: stateInfo.attachmentId
|
|
||||||
)
|
|
||||||
),
|
|
||||||
before: job
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.forEach { otherJobId, _ in
|
|
||||||
// Create the dependency between the jobs
|
|
||||||
try JobDependencies(
|
|
||||||
jobId: jobId,
|
|
||||||
dependantId: otherJobId
|
|
||||||
)
|
|
||||||
.insert(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there were pending or uploading attachments then stop here (we want to
|
|
||||||
// upload them first and then re-run this send job - the 'JobRunner.insert'
|
|
||||||
// method will take care of this)
|
|
||||||
let isMissingFileIds: Bool = (maybeFileIds.count != fileIds.count)
|
|
||||||
let hasPendingUploads: Bool = allAttachmentStateInfo.contains(where: { $0.state != .uploaded })
|
|
||||||
|
|
||||||
return (
|
|
||||||
(isMissingFileIds && !hasPendingUploads),
|
|
||||||
hasPendingUploads,
|
|
||||||
fileIds
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't send messages with failed attachment uploads
|
|
||||||
//
|
|
||||||
// Note: If we have gotten to this point then any dependant attachment upload
|
|
||||||
// jobs will have permanently failed so this message send should also do so
|
|
||||||
guard attachmentState?.shouldFail == false else {
|
|
||||||
SNLog("[MessageSendJob] Failing due to failed attachment upload")
|
|
||||||
failure(job, AttachmentError.notUploaded, true)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer the job if we found incomplete uploads
|
/// If we have any pending (or failed) attachment uploads then we should create jobs for them and insert them into the
|
||||||
guard attachmentState?.shouldDefer == false else {
|
/// queue before the current job and defer it (this will mean the current job will re-run after these inserted jobs complete)
|
||||||
SNLog("[MessageSendJob] Deferring pending attachment uploads")
|
guard attachmentState.pendingUploadAttachmentIds.isEmpty else {
|
||||||
deferred(job)
|
Storage.shared.write { db in
|
||||||
return
|
try attachmentState.pendingUploadAttachmentIds
|
||||||
|
.filter { attachmentId in
|
||||||
|
// Don't add a new job if there is one already in the queue
|
||||||
|
!JobRunner.hasPendingOrRunningJob(
|
||||||
|
with: .attachmentUpload,
|
||||||
|
details: AttachmentUploadJob.Details(
|
||||||
|
messageSendJobId: jobId,
|
||||||
|
attachmentId: attachmentId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.compactMap { attachmentId -> (jobId: Int64, job: Job)? in
|
||||||
|
JobRunner
|
||||||
|
.insert(
|
||||||
|
db,
|
||||||
|
job: Job(
|
||||||
|
variant: .attachmentUpload,
|
||||||
|
behaviour: .runOnce,
|
||||||
|
threadId: job.threadId,
|
||||||
|
interactionId: interactionId,
|
||||||
|
details: AttachmentUploadJob.Details(
|
||||||
|
messageSendJobId: jobId,
|
||||||
|
attachmentId: attachmentId
|
||||||
|
)
|
||||||
|
),
|
||||||
|
before: job
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.forEach { otherJobId, _ in
|
||||||
|
// Create the dependency between the jobs
|
||||||
|
try JobDependencies(
|
||||||
|
jobId: jobId,
|
||||||
|
dependantId: otherJobId
|
||||||
|
)
|
||||||
|
.insert(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SNLog("[MessageSendJob] Deferring due to pending attachment uploads")
|
||||||
|
return deferred(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the fileIds so they can be sent with the open group message content
|
// Store the fileIds so they can be sent with the open group message content
|
||||||
messageFileIds = (attachmentState?.fileIds ?? [])
|
messageFileIds = attachmentState.preparedFileIds
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error
|
// Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error
|
||||||
|
|
|
@ -606,6 +606,21 @@ public final class MessageSender {
|
||||||
)
|
)
|
||||||
|
|
||||||
guard expectedAttachmentUploadCount == preparedSendData.totalAttachmentsUploaded else {
|
guard expectedAttachmentUploadCount == preparedSendData.totalAttachmentsUploaded else {
|
||||||
|
// Make sure to actually handle this as a failure (if we don't then the message
|
||||||
|
// won't go into an error state correctly)
|
||||||
|
if let message: Message = preparedSendData.message {
|
||||||
|
dependencies.storage.read { db in
|
||||||
|
MessageSender.handleFailedMessageSend(
|
||||||
|
db,
|
||||||
|
message: message,
|
||||||
|
with: .attachmentsNotUploaded,
|
||||||
|
interactionId: preparedSendData.interactionId,
|
||||||
|
isSyncMessage: (preparedSendData.isSyncMessage == true),
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Fail(error: MessageSenderError.attachmentsNotUploaded)
|
return Fail(error: MessageSenderError.attachmentsNotUploaded)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
@ -992,7 +1007,6 @@ public final class MessageSender {
|
||||||
isSyncMessage: Bool = false,
|
isSyncMessage: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: SMKDependencies = SMKDependencies()
|
||||||
) -> Error {
|
) -> Error {
|
||||||
// TODO: Revert the local database change
|
|
||||||
// If the message was a reaction then we don't want to do anything to the original
|
// If the message was a reaction then we don't want to do anything to the original
|
||||||
// interaciton (which the 'interactionId' is pointing to
|
// interaciton (which the 'interactionId' is pointing to
|
||||||
guard (message as? VisibleMessage)?.reaction == nil else { return error }
|
guard (message as? VisibleMessage)?.reaction == nil else { return error }
|
||||||
|
|
|
@ -14,7 +14,12 @@ public final class CurrentUserPoller: Poller {
|
||||||
|
|
||||||
// MARK: - Settings
|
// MARK: - Settings
|
||||||
|
|
||||||
override var namespaces: [SnodeAPI.Namespace] { CurrentUserPoller.namespaces }
|
override var namespaces: [SnodeAPI.Namespace] {
|
||||||
|
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||||
|
guard SessionUtil.userConfigsEnabled else { return [.default] }
|
||||||
|
|
||||||
|
return CurrentUserPoller.namespaces
|
||||||
|
}
|
||||||
|
|
||||||
/// After polling a given snode this many times we always switch to a new one.
|
/// After polling a given snode this many times we always switch to a new one.
|
||||||
///
|
///
|
||||||
|
|
|
@ -71,16 +71,3 @@ public struct QuotedReplyModel {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
|
||||||
|
|
||||||
public extension QuotedReplyModel {
|
|
||||||
func generateAttachmentThumbnailIfNeeded(_ db: Database) throws -> String? {
|
|
||||||
guard let sourceAttachment: Attachment = self.attachment else { return nil }
|
|
||||||
|
|
||||||
return try sourceAttachment
|
|
||||||
.cloneAsQuoteThumbnail()?
|
|
||||||
.inserted(db)
|
|
||||||
.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,9 +10,7 @@ import SessionUtilitiesKit
|
||||||
|
|
||||||
public extension Features {
|
public extension Features {
|
||||||
static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool {
|
static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool {
|
||||||
return true
|
guard Date().timeIntervalSince1970 < 1690761600 else { return true }
|
||||||
// TODO: Need to set this timestamp to the correct date (currently start of 2030)
|
|
||||||
// guard Date().timeIntervalSince1970 < 1893456000 else { return true }
|
|
||||||
guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else {
|
guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else {
|
||||||
return SessionUtil.userConfigsEnabledIgnoringFeatureFlag
|
return SessionUtil.userConfigsEnabledIgnoringFeatureFlag
|
||||||
}
|
}
|
||||||
|
|
|
@ -527,6 +527,7 @@ public extension MessageViewModel {
|
||||||
public extension MessageViewModel {
|
public extension MessageViewModel {
|
||||||
static let genericId: Int64 = -1
|
static let genericId: Int64 = -1
|
||||||
static let typingIndicatorId: Int64 = -2
|
static let typingIndicatorId: Int64 = -2
|
||||||
|
static let optimisticUpdateId: Int64 = -3
|
||||||
|
|
||||||
/// This init method is only used for system-created cells or empty states
|
/// This init method is only used for system-created cells or empty states
|
||||||
init(
|
init(
|
||||||
|
@ -634,8 +635,8 @@ public extension MessageViewModel {
|
||||||
|
|
||||||
// Interaction Info
|
// Interaction Info
|
||||||
|
|
||||||
self.rowId = -1
|
self.rowId = MessageViewModel.optimisticUpdateId
|
||||||
self.id = -1
|
self.id = MessageViewModel.optimisticUpdateId
|
||||||
self.openGroupServerMessageId = nil
|
self.openGroupServerMessageId = nil
|
||||||
self.variant = .standardOutgoing
|
self.variant = .standardOutgoing
|
||||||
self.timestampMs = timestampMs
|
self.timestampMs = timestampMs
|
||||||
|
|
|
@ -3581,7 +3581,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
||||||
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
|
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
timeout: .milliseconds(50)
|
timeout: .milliseconds(100)
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
mockStorage.read { db -> Data? in
|
mockStorage.read { db -> Data? in
|
||||||
|
|
Loading…
Reference in New Issue