Merge remote-tracking branch 'origin/feature/ci' into feature/updated-user-config-handling

This commit is contained in:
Morgan Pretty 2023-07-13 16:08:03 +10:00
commit 970efbc3be
34 changed files with 995 additions and 935 deletions

150
.drone.jsonnet Normal file
View File

@ -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'
]
},
],
},
]

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "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
View File

@ -1,9 +1,10 @@
platform :ios, '13.0'
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
inhibit_all_warnings!
install! 'cocoapods', :warn_for_unused_master_specs_repo => false
# Dependencies to be included in the app and all extensions/frameworks
abstract_target 'GlobalDependencies' do
# 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 'NVActivityIndicatorView'
pod 'YYImage/libwebp', git: 'https://github.com/signalapp/YYImage'
pod 'ZXingObjC'
pod 'DifferenceKit'
target 'SessionTests' do
@ -101,7 +101,6 @@ end
# Actions to perform post-install
post_install do |installer|
set_minimum_deployment_target(installer)
avoid_rsync_webrtc_if_unchanged(installer)
end
def set_minimum_deployment_target(installer)
@ -111,12 +110,3 @@ def set_minimum_deployment_target(installer)
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

View File

@ -41,7 +41,7 @@ PODS:
- SQLCipher/standard (4.5.3):
- SQLCipher/common
- SwiftProtobuf (1.5.0)
- WebRTC-lib (96.0.0)
- WebRTC-lib (114.0.0)
- YapDatabase/SQLCipher (3.1.1):
- YapDatabase/SQLCipher/Core (= 3.1.1)
- YapDatabase/SQLCipher/Extensions (= 3.1.1)
@ -108,9 +108,6 @@ PODS:
- YYImage/libwebp (1.0.4):
- libwebp
- YYImage/Core
- ZXingObjC (3.6.5):
- ZXingObjC/All (= 3.6.5)
- ZXingObjC/All (3.6.5)
DEPENDENCIES:
- Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`)
@ -129,7 +126,6 @@ DEPENDENCIES:
- WebRTC-lib
- YapDatabase/SQLCipher (from `https://github.com/oxen-io/session-ios-yap-database.git`, branch `signal-release`)
- YYImage/libwebp (from `https://github.com/signalapp/YYImage`)
- ZXingObjC
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
@ -147,7 +143,6 @@ SPEC REPOS:
- SQLCipher
- SwiftProtobuf
- WebRTC-lib
- ZXingObjC
EXTERNAL SOURCES:
Curve25519Kit:
@ -199,11 +194,10 @@ SPEC CHECKSUMS:
Sodium: a7d42cb46e789d2630fa552d35870b416ed055ae
SQLCipher: 57fa9f863fa4a3ed9dd3c90ace52315db8c0fdca
SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2
WebRTC-lib: 508fe02efa0c1a3a8867082a77d24c9be5d29aeb
WebRTC-lib: d83df8976fa608b980f1d85796b3de66d60a1953
YapDatabase: b418a4baa6906e8028748938f9159807fd039af4
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: dcca0c4ad69b14cbc2d6ba49f9d690b239828e6d
PODFILE CHECKSUM: f56c28baefe3077effcb3a2ea5941b52c4cc6e86
COCOAPODS: 1.12.1

View File

@ -26,6 +26,7 @@ var pathFiles: [String] = {
return fileUrls
.filter {
((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(".xcassets") && // Exclude asset bundles
!$0.path.contains(".app/") && // Exclude files in the app build directories

View File

@ -27,37 +27,41 @@
# Need to set the path or we won't find cmake
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)
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
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
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"
echo_message "error: cmake is required to build, please install (can install via homebrew with 'brew install cmake')."
exit 0
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
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 "info: LibSession-Util submodule doesn't exist, resetting and checking out recusively now"
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
echo_message "error: Need to fetch LibSession-Util submodule (git submodule update --init --recursive)."
exit 0
else
are_submodules_valid() {
local PARENT_PATH=$1
@ -82,7 +86,7 @@ else
# If the child path doesn't exist then it's invalid
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
fi
@ -90,7 +94,7 @@ else
local RESULT=$?
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
fi
done
@ -104,18 +108,8 @@ else
HAS_INVALID_SUBMODULE=$?
if [ "${HAS_INVALID_SUBMODULE}" -eq 1 ]; then
if [ "${SHOULD_AUTO_INIT_SUBMODULES}" != "false" ] && command -v git >/dev/null 2>&1; then
echo "info: Submodules are in an invalid state, resetting and checking out recusively now"
cd "${SRCROOT}/LibSession-Util"
git submodule foreach --recursive git reset --hard
git submodule update --init --recursive
echo "info: Checkout complete"
else
touch "${TARGET_BUILD_DIR}/libsession_util_error.log"
echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'."
echo "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'." > "${TARGET_BUILD_DIR}/libsession_util_error.log"
exit 0
fi
echo_message "error: Submodules are in an invalid state, please delete 'LibSession-Util' and run 'git submodule update --init --recursive'."
exit 0
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_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
read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libsession_util_source_hash.log"
if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log" ]; then
read -r OLD_SOURCE_HASH < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_source_hash.log"
fi
if [ -f "${TARGET_BUILD_DIR}/libsession_util_header_hash.log" ]; then
read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libsession_util_header_hash.log"
if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log" ]; then
read -r OLD_HEADER_HASH < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_header_hash.log"
fi
if [ -f "${TARGET_BUILD_DIR}/libsession_util_archs.log" ]; then
read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libsession_util_archs.log"
if [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log" ]; then
read -r OLD_ARCHS < "${TARGET_BUILD_DIR}/libSessionUtil/libsession_util_archs.log"
fi
# Start the libSession-util build if it doesn't already exists
if [ "${NEW_SOURCE_HASH}" != "${OLD_SOURCE_HASH}" ] || [ "${NEW_HEADER_HASH}" != "${OLD_HEADER_HASH}" ] || [ "${ARCHS[*]}" != "${OLD_ARCHS}" ] || [ ! -d "${TARGET_BUILD_DIR}/libsession-util.xcframework" ]; then
echo "info: Build is not up-to-date - creating new build"
echo ""
# 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"
# 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}" ] && [ -f "${TARGET_BUILD_DIR}/libSessionUtil/libSessionUtil.a" ]; then
echo_message "info: Build is up-to-date"
exit 0
fi
# Move the target-specific libSession-util build to the parent build directory (so XCode can have a reference to a single build)
rm -rf "${BUILD_DIR}/libsession-util.xcframework"
cp -r "${TARGET_BUILD_DIR}/libsession-util.xcframework" "${BUILD_DIR}/libsession-util.xcframework"
# If any of the above differ then we need to rebuild
echo_message "info: Build is not up-to-date - creating new build"
# 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"

76
Scripts/drone-static-upload.sh Executable file
View File

@ -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"

View File

@ -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

View File

@ -5,24 +5,6 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D221A088169C9E5E00537ABF"
BuildableName = "Session.app"
BlueprintName = "Session"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

View File

@ -1,28 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.7">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3C2A6EF25539DE700C340D1"
BuildableName = "SessionMessagingKit.framework"
BlueprintName = "SessionMessagingKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

View File

@ -6,24 +6,6 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7BC01A3A241F40AB00BC7C55"
BuildableName = "SessionNotificationServiceExtension.appex"
BlueprintName = "SessionNotificationServiceExtension"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

View File

@ -6,24 +6,6 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "453518671FC635DD00210559"
BuildableName = "SessionShareExtension.appex"
BlueprintName = "SessionShareExtension"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

View File

@ -1,28 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.7">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3C2A678255388CC00C340D1"
BuildableName = "SessionUtilitiesKit.framework"
BlueprintName = "SessionUtilitiesKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

View File

@ -1,28 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.7">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Build libSession"
scriptText = "&quot;${SRCROOT}/Scripts/build_libSession_util.sh&quot;&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C33FD9AA255A548A00E217F9"
BuildableName = "SignalUtilitiesKit.framework"
BlueprintName = "SignalUtilitiesKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

View File

@ -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"
}
]
}

View File

@ -453,6 +453,7 @@ extension ConversationVC:
self?.snInputView.quoteDraftInfo = nil
self?.resetMentions()
self?.scrollToBottom(isAnimated: false)
}
// Note: 'shouldBeVisible' is set to true the first time a thread is saved so we can
@ -481,64 +482,70 @@ extension ConversationVC:
quoteModel: quoteModel
)
// Actually send the message
Storage.shared
.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))
DispatchQueue.global(qos:.userInitiated).async {
// Generate the quote thumbnail if needed (want this to happen outside of the DBWrite thread as
// this can take up to 0.5s
let quoteThumbnailAttachment: Attachment? = quoteModel?.attachment?.cloneAsQuoteThumbnail()
// Actually send the message
Storage.shared
.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
)
}
// 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: quoteModel.generateAttachmentThumbnailIfNeeded(db)
).insert(db)
}
// Process any attachments
try Attachment.process(
db,
data: optimisticData.attachmentData,
for: insertedInteraction.id
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.sinkUntilComplete(
receiveCompletion: { [weak self] _ in
self?.handleMessageSent()
}
)
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() {

View File

@ -899,9 +899,34 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
// Store the 'sentMessageBeforeUpdate' state locally
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 = (
changeset.count == 1 &&
changeset[0].elementUpdated.count == changeset[0].changeCount
onlyReplacedOptimisticUpdate || (
changeset.count == 1 &&
changeset[0].elementUpdated.count == changeset[0].changeCount
)
)
self.viewModel.sentMessageBeforeUpdate = false
@ -912,13 +937,13 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
guard !didSendMessageBeforeUpdate && !wasOnlyUpdates else {
self.viewModel.updateInteractionData(updatedData)
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 didSendMessageBeforeUpdate {
// We need to dispatch to the next run loop because it seems trying to scroll immediately after
// triggering a 'reloadData' doesn't work
DispatchQueue.main.async { [weak self] in
self?.tableView.layoutIfNeeded()
self?.scrollToBottom(isAnimated: false)
// Note: The scroll button alpha won't get set correctly in this case so we forcibly set it to

View File

@ -165,12 +165,12 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
dismiss(animated: true, completion: nil)
}
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) {
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) {
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)
if KeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) {
@ -185,7 +185,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
title: "ALERT_ERROR_TITLE".localized(),
body: .text("DM_ERROR_DIRECT_BLINDED_ID".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
cancelStyle: .alert_text,
afterClosed: onError
)
)
self.present(modal, animated: true)
@ -197,7 +198,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
title: "ALERT_ERROR_TITLE".localized(),
body: .text("DM_ERROR_INVALID".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
cancelStyle: .alert_text,
afterClosed: onError
)
)
self.present(modal, animated: true)
@ -243,7 +245,8 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle
title: "ALERT_ERROR_TITLE".localized(),
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
cancelStyle: .alert_text,
afterClosed: onError
)
)
self?.present(modal, animated: true)
@ -663,7 +666,7 @@ private final class EnterPublicKeyVC: UIViewController {
@objc fileprivate func startNewDMIfPossible() {
let text = publicKeyTextView.text?.trimmingCharacters(in: .whitespaces) ?? ""
NewDMVC.startNewDMIfPossible(with: text)
NewDMVC.startNewDMIfPossible(with: text, onError: nil)
}
}

View File

@ -299,7 +299,7 @@ enum GiphyAPI {
return HTTPError.generic
}
.map { data, _ in
Logger.error("search request succeeded")
Logger.debug("search request succeeded")
guard let imageInfos = self.parseGiphyImages(responseData: data) else {
Logger.error("unable to parse trending images")
@ -347,7 +347,7 @@ enum GiphyAPI {
return HTTPError.generic
}
.tryMap { data, _ -> [GiphyImageInfo] in
Logger.error("search request succeeded")
Logger.debug("search request succeeded")
guard let imageInfos = self.parseGiphyImages(responseData: data) else {
throw HTTPError.invalidResponse

View File

@ -2,13 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<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>
<string>en</string>
<key>CFBundleDisplayName</key>
@ -88,7 +81,7 @@
<key>NSCameraUsageDescription</key>
<string>Session needs camera access to take pictures and scan QR codes.</string>
<key>NSFaceIDUsageDescription</key>
<string>Session's Screen Lock feature uses Face ID.</string>
<string>Session&apos;s Screen Lock feature uses Face ID.</string>
<key>NSHumanReadableCopyright</key>
<string>com.loki-project.loki-messenger</string>
<key>NSMicrophoneUsageDescription</key>

View File

@ -134,12 +134,12 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont
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)
continueWithSeed(seed)
continueWithSeed(seed, onError: onError)
}
func continueWithSeed(_ seed: Data) {
func continueWithSeed(_ seed: Data, onError: (() -> ())?) {
if (seed.count != 16) {
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
@ -147,9 +147,7 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont
body: .text("INVALID_RECOVERY_PHRASE_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text,
afterClosed: { [weak self] in
self?.scanQRCodeWrapperVC.startCapture()
}
afterClosed: onError
)
)
present(modal, animated: true)
@ -319,7 +317,7 @@ private final class RecoveryPhraseVC: UIViewController {
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
let seed = Data(hex: hexEncodedSeed)
mnemonicTextView.resignFirstResponder()
linkDeviceVC.continueWithSeed(seed)
linkDeviceVC.continueWithSeed(seed, onError: nil)
} catch let error {
let error = error as? Mnemonic.DecodingError ?? Mnemonic.DecodingError.generic
showError(title: error.errorDescription!)

View File

@ -144,25 +144,26 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
dismiss(animated: true, completion: nil)
}
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) {
joinOpenGroup(with: string)
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) {
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>
// The host doesn't parse if no explicit scheme is provided
guard let (room, server, publicKey) = SessionUtil.parseCommunity(url: urlString) else {
showError(
title: "invalid_url".localized(),
message: "COMMUNITY_ERROR_INVALID_URL".localized()
message: "COMMUNITY_ERROR_INVALID_URL".localized(),
onError: onError
)
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 }
isJoining = true
@ -209,7 +210,8 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
self?.dismiss(animated: true) { // Dismiss the loader
self?.showError(
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
private func showError(title: String, message: String = "") {
private func showError(title: String, message: String = "", onError: (() -> ())?) {
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: title,
body: .text(message),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
cancelStyle: .alert_text,
afterClosed: onError
)
)
self.navigationController?.present(confirmationModal, animated: true, completion: nil)
@ -399,13 +402,14 @@ private final class EnterURLVC: UIViewController, UIGestureRecognizerDelegate, O
roomToken: room.token,
server: OpenGroupAPI.defaultServer,
publicKey: OpenGroupAPI.defaultServerPublicKey,
shouldOpenCommunity: true
shouldOpenCommunity: true,
onError: nil
)
}
@objc private func joinOpenGroup() {
let url = urlTextView.text?.trimmingCharacters(in: .whitespaces) ?? ""
joinOpenGroupVC?.joinOpenGroup(with: url)
joinOpenGroupVC?.joinOpenGroup(with: url, onError: nil)
}
// MARK: - Updating

View File

@ -119,12 +119,12 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl
dismiss(animated: true, completion: nil)
}
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) {
func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) {
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) {
let modal: ConfirmationModal = ConfirmationModal(
targetView: self.view,
@ -132,7 +132,8 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl
title: "invalid_session_id".localized(),
body: .text("INVALID_SESSION_ID_MESSAGE".localized()),
cancelTitle: "BUTTON_OK".localized(),
cancelStyle: .alert_text
cancelStyle: .alert_text,
afterClosed: onError
)
)
self.present(modal, animated: true)

View File

@ -2,55 +2,36 @@
import UIKit
import AVFoundation
import ZXingObjC
import SessionUIKit
import SessionUtilitiesKit
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?
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
// MARK: - Initialization
deinit {
self.capture?.layer.removeFromSuperlayer()
self.captureLayer?.removeFromSuperlayer()
}
// MARK: - Components
private let maskingView: UIView = {
let result: OWSBezierPathView = OWSBezierPathView()
result.configureShapeLayerBlock = { layer, bounds in
// Add a circular mask
let path: UIBezierPath = UIBezierPath(rect: bounds)
let margin: CGFloat = ScaleFromIPhone5To7Plus(24, 48)
let radius: CGFloat = ((min(bounds.size.width, bounds.size.height) * 0.5) - margin)
// 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
}
private let maskingView: UIView = UIView()
private lazy var maskLayer: CAShapeLayer = {
let result: CAShapeLayer = CAShapeLayer()
result.fillRule = .evenOdd
result.themeFillColor = .black
result.opacity = 0.32
return result
}()
@ -61,7 +42,8 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj
super.loadView()
self.view.addSubview(maskingView)
maskingView.pin(to: self.view)
maskingView.layer.addSublayer(maskLayer)
}
override func viewWillAppear(_ animated: Bool) {
@ -81,11 +63,28 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Note: When accessing 'capture.layer' if the setup hasn't been completed it
// will result in a layout being triggered which creates an infinite loop, this
// check prevents that case
if let capture: ZXCapture = self.capture {
capture.layer.frame = self.view.bounds
captureLayer?.frame = self.view.bounds
if maskingView.frame != self.view.bounds {
// Add a circular mask
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
if self.capture == nil {
self.captureQueue.async { [weak self] in
let capture: ZXCapture = ZXCapture()
capture.camera = capture.back()
capture.focusMode = .autoFocus
capture.delegate = self
capture.start()
let maybeDevice: AVCaptureDevice? = {
if let result = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back) {
return result
}
return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
}()
// Note: When accessing the 'layer' for the first time it will create
// an instance of 'AVCaptureVideoPreviewLayer', this can hang a little
// so we do this on the background thread first
if capture.layer != nil {}
// Set the input device to autoFocus (since we don't have the interaction setup for
// doing it manually)
maybeDevice?.focusMode = .continuousAutoFocus
// 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 {
capture.layer.frame = (self?.view.bounds ?? .zero)
self?.view.layer.addSublayer(capture.layer)
layer.frame = (self?.view.bounds ?? .zero)
self?.view.layer.addSublayer(layer)
if let maskingView: UIView = self?.maskingView {
self?.view.bringSubviewToFront(maskingView)
}
self?.capture = capture
self?.captureLayer = layer
}
}
}
else {
self.capture?.start()
self.capture?.startRunning()
}
#endif
}
@ -133,18 +177,25 @@ class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObj
private func stopCapture() {
self.captureEnabled = false
self.captureQueue.async { [weak self] in
self?.capture?.stop()
self?.capture?.stopRunning()
}
}
internal func captureResult(_ capture: ZXCapture, result: ZXResult) {
guard self.captureEnabled else { return }
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
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()
// Vibrate
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
self.scanDelegate?.controller(self, didDetectQRCodeWith: result.text)
self.scanDelegate?.controller(self, didDetectQRCodeWith: qrCode) { [weak self] in
self?.startCapture()
}
}
}

View File

@ -3,7 +3,6 @@
import Foundation
import Combine
import GRDB
import SessionUtil
import SessionSnodeKit
import SessionUtilitiesKit

View File

@ -39,8 +39,7 @@ public enum MessageSendJob: JobExecutor {
/// already have attachments in a valid state
if
details.message is VisibleMessage,
(details.message as? VisibleMessage)?.reaction == nil &&
details.isSyncMessage == false
(details.message as? VisibleMessage)?.reaction == nil
{
guard
let jobId: Int64 = job.id,
@ -51,122 +50,111 @@ public enum MessageSendJob: JobExecutor {
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 Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
SNLog("[MessageSendJob] Failing due to missing interaction")
failure(job, StorageError.objectNotFound, true)
return
}
// Check if there are any attachments associated to this message, and if so
// upload them now
//
// Note: Normal attachments should be sent in a non-durable way but any
// attachments for LinkPreviews and Quotes will be processed through this mechanism
let attachmentState: (shouldFail: Bool, shouldDefer: Bool, fileIds: [String])? = Storage.shared.write { db in
let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment
.stateInfo(interactionId: interactionId)
.fetchAll(db)
let maybeFileIds: [String?] = allAttachmentStateInfo
.sorted { lhs, rhs in lhs.albumIndex < rhs.albumIndex }
.map { Attachment.fileId(for: $0.downloadUrl) }
let fileIds: [String] = maybeFileIds.compactMap { $0 }
// 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)
guard !allAttachmentStateInfo.contains(where: { $0.state == .failedDownload }) else {
return (true, false, fileIds)
}
// Create jobs for any pending (or failed) attachment jobs and insert them into the
// 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 device - both LinkPreview and Quote can
// have this case)
try allAttachmentStateInfo
.filter { attachment -> Bool in
// Non-media quotes won't have thumbnails so so don't try to upload them
guard attachment.downloadUrl != Attachment.nonMediaQuoteFileId else { return false }
switch attachment.state {
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
return true
default: return false
// Retrieve the current attachment state
typealias AttachmentState = (error: Error?, pendingUploadAttachmentIds: [String], preparedFileIds: [String])
let attachmentState: AttachmentState = Storage.shared
.read { db in
// 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 {
SNLog("[MessageSendJob] Failing due to missing interaction")
return (StorageError.objectNotFound, [], [])
}
// Get the current state of the attachments
let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment
.stateInfo(interactionId: interactionId)
.fetchAll(db)
let maybeFileIds: [String?] = allAttachmentStateInfo
.sorted { lhs, rhs in lhs.albumIndex < rhs.albumIndex }
.map { Attachment.fileId(for: $0.downloadUrl) }
let fileIds: [String] = maybeFileIds.compactMap { $0 }
// 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)
guard !allAttachmentStateInfo.contains(where: { $0.state == .failedDownload }) else {
SNLog("[MessageSendJob] Failing due to failed attachment upload")
return (AttachmentError.notUploaded, [], fileIds)
}
/// Find all attachmentIds for attachments which need to be uploaded
///
/// **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
/// device - both `LinkPreview` and `Quote` can have this case)
let pendingUploadAttachmentIds: [String] = allAttachmentStateInfo
.filter { attachment -> Bool in
// Non-media quotes won't have thumbnails so so don't try to upload them
guard attachment.downloadUrl != Attachment.nonMediaQuoteFileId else { return false }
switch attachment.state {
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
return true
default: return false
}
}
}
.filter { stateInfo 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: stateInfo.attachmentId
)
)
}
.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
.map { $0.attachmentId }
return (nil, pendingUploadAttachmentIds, fileIds)
}
.defaulting(to: (MessageSenderError.invalidMessage, [], []))
/// If we got an error when trying to retrieve the attachment state then this job is actually invalid so it
/// should permanently fail
guard attachmentState.error == nil else {
return failure(job, (attachmentState.error ?? MessageSenderError.invalidMessage), true)
}
// Defer the job if we found incomplete uploads
guard attachmentState?.shouldDefer == false else {
SNLog("[MessageSendJob] Deferring pending attachment uploads")
deferred(job)
return
/// If we have any pending (or failed) attachment uploads then we should create jobs for them and insert them into the
/// queue before the current job and defer it (this will mean the current job will re-run after these inserted jobs complete)
guard attachmentState.pendingUploadAttachmentIds.isEmpty else {
Storage.shared.write { db in
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
messageFileIds = (attachmentState?.fileIds ?? [])
messageFileIds = attachmentState.preparedFileIds
}
// Store the sentTimestamp from the message in case it fails due to a clockOutOfSync error

View File

@ -606,6 +606,21 @@ public final class MessageSender {
)
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)
.eraseToAnyPublisher()
}
@ -992,7 +1007,6 @@ public final class MessageSender {
isSyncMessage: Bool = false,
using dependencies: SMKDependencies = SMKDependencies()
) -> Error {
// TODO: Revert the local database change
// If the message was a reaction then we don't want to do anything to the original
// interaciton (which the 'interactionId' is pointing to
guard (message as? VisibleMessage)?.reaction == nil else { return error }

View File

@ -14,7 +14,12 @@ public final class CurrentUserPoller: Poller {
// 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.
///

View File

@ -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
}
}

View File

@ -10,9 +10,7 @@ import SessionUtilitiesKit
public extension Features {
static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool {
return true
// TODO: Need to set this timestamp to the correct date (currently start of 2030)
// guard Date().timeIntervalSince1970 < 1893456000 else { return true }
guard Date().timeIntervalSince1970 < 1690761600 else { return true }
guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else {
return SessionUtil.userConfigsEnabledIgnoringFeatureFlag
}

View File

@ -527,6 +527,7 @@ public extension MessageViewModel {
public extension MessageViewModel {
static let genericId: Int64 = -1
static let typingIndicatorId: Int64 = -2
static let optimisticUpdateId: Int64 = -3
/// This init method is only used for system-created cells or empty states
init(
@ -634,8 +635,8 @@ public extension MessageViewModel {
// Interaction Info
self.rowId = -1
self.id = -1
self.rowId = MessageViewModel.optimisticUpdateId
self.id = MessageViewModel.optimisticUpdateId
self.openGroupServerMessageId = nil
self.variant = .standardOutgoing
self.timestampMs = timestampMs

View File

@ -3581,7 +3581,7 @@ class OpenGroupManagerSpec: QuickSpec {
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
)
},
timeout: .milliseconds(50)
timeout: .milliseconds(100)
)
expect(
mockStorage.read { db -> Data? in