mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
b740522e3c
139
.github/workflows/build.yml
vendored
Normal file
139
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-10.15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Set active Xcode path
|
||||
run: sudo xcode-select -s /Applications/Xcode_12.3.app/Contents/Developer
|
||||
|
||||
- name: Create canonical source directory
|
||||
run: |
|
||||
set -x
|
||||
sudo mkdir /Users/telegram
|
||||
sudo chown -R $(whoami) /Users/telegram
|
||||
cp -R $GITHUB_WORKSPACE /Users/telegram/
|
||||
mv /Users/telegram/$(basename $GITHUB_WORKSPACE) /Users/telegram/telegram-ios
|
||||
|
||||
- name: Build the App
|
||||
run: |
|
||||
set -x
|
||||
|
||||
# source code paths are included in the final binary, so we need to make them stable across builds
|
||||
SOURCE_DIR=/Users/telegram/telegram-ios
|
||||
|
||||
# use canonical bazel root
|
||||
BAZEL_USER_ROOT="/private/var/tmp/_bazel_telegram"
|
||||
|
||||
# download bazel
|
||||
mkdir -p $HOME/bazel-dist
|
||||
pushd $HOME/bazel-dist
|
||||
curl -O -L https://github.com/bazelbuild/bazel/releases/download/3.7.0/bazel-3.7.0-darwin-x86_64
|
||||
mv bazel-3.7.0* bazel
|
||||
chmod +x bazel
|
||||
./bazel --version
|
||||
popd
|
||||
|
||||
cd $SOURCE_DIR
|
||||
|
||||
export APP_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["app"]);')
|
||||
export COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
export COMMIT_COUNT="$(($COMMIT_COUNT+2000))"
|
||||
export BUILD_NUMBER="$COMMIT_COUNT"
|
||||
echo "BUILD_NUMBER=$(echo $BUILD_NUMBER)" >> $GITHUB_ENV
|
||||
echo "APP_VERSION=$(echo $APP_VERSION)" >> $GITHUB_ENV
|
||||
|
||||
# prepare temporary keychain
|
||||
export MY_KEYCHAIN="temp.keychain"
|
||||
export MY_KEYCHAIN_PASSWORD="secret"
|
||||
security create-keychain -p "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
|
||||
security list-keychains -d user -s "$MY_KEYCHAIN" $(security list-keychains -d user | sed s/\"//g)
|
||||
security set-keychain-settings "$MY_KEYCHAIN"
|
||||
security unlock-keychain -p "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
|
||||
|
||||
# install fake certificates
|
||||
export CERTS_PATH="build-system/fake-codesigning/certs/distribution"
|
||||
for f in "$CERTS_PATH"/*.p12; do
|
||||
security import "$f" -k "$MY_KEYCHAIN" -P "" -T /usr/bin/codesign -T /usr/bin/security
|
||||
done
|
||||
# fake certificates are self-signed, so we need to manually mark them as trusted (otherwise bazel will not pick them up)
|
||||
for f in "$CERTS_PATH"/*.cer; do
|
||||
sudo security add-trusted-cert -d -r trustRoot -p codeSign -k "$MY_KEYCHAIN" "$f"
|
||||
done
|
||||
security set-key-partition-list -S apple-tool:,apple: -k "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
|
||||
|
||||
# use the official release configuration
|
||||
rm -rf $HOME/telegram-configuration
|
||||
mkdir -p $HOME/telegram-configuration
|
||||
cp -R build-system/example-configuration/* $HOME/telegram-configuration/
|
||||
|
||||
# build the app
|
||||
python3 build-system/Make/Make.py \
|
||||
--bazel="$HOME/bazel-dist/bazel" \
|
||||
--bazelUserRoot="$BAZEL_USER_ROOT" \
|
||||
build \
|
||||
--configurationPath="$HOME/telegram-configuration" \
|
||||
--buildNumber=$BUILD_NUMBER \
|
||||
--configuration=release_universal
|
||||
|
||||
# collect ipa
|
||||
OUTPUT_PATH="build/artifacts"
|
||||
rm -rf "$OUTPUT_PATH"
|
||||
mkdir -p "$OUTPUT_PATH"
|
||||
for f in bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa; do
|
||||
cp "$f" $OUTPUT_PATH/
|
||||
done
|
||||
|
||||
# collect dsym
|
||||
mkdir -p build/DSYMs
|
||||
for f in bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/*.dSYM; do
|
||||
cp -R "$f" build/DSYMs/
|
||||
done
|
||||
zip -r "./$OUTPUT_PATH/Telegram.DSYMs.zip" build/DSYMs 1>/dev/null
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: build-${{ env.BUILD_NUMBER }}
|
||||
release_name: Telegram ${{ env.APP_VERSION }} (${{ env.BUILD_NUMBER }})
|
||||
body: |
|
||||
An unsigned build of Telegram for iOS ${{ env.APP_VERSION }} (${{ env.BUILD_NUMBER }})
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Release IPA
|
||||
id: upload-release-ipa
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: /Users/telegram/telegram-ios/build/artifacts/Telegram.ipa
|
||||
asset_name: Telegram.ipa
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Release DSYM
|
||||
id: upload-release-dsym
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: /Users/telegram/telegram-ios/build/artifacts/Telegram.DSYMs.zip
|
||||
asset_name: Telegram.DSYMs.zip
|
||||
asset_content_type: application/zip
|
@ -5959,6 +5959,7 @@ Sorry for the inconvenience.";
|
||||
"ChatImportActivity.ErrorInvalidChatType" = "Wrong type of chat for the messages you are trying to import.";
|
||||
"ChatImportActivity.ErrorUserBlocked" = "Unable to import messages due to privacy settings.";
|
||||
"ChatImportActivity.ErrorGeneric" = "An error occurred.";
|
||||
"ChatImportActivity.ErrorLimitExceeded" = "Daily maximum reached,\nplease come back tomorrow.";
|
||||
"ChatImportActivity.Success" = "Chat imported\nsuccessfully.";
|
||||
|
||||
"VoiceOver.Chat.GoToOriginalMessage" = "Go to message";
|
||||
|
@ -12,7 +12,7 @@ from ProjectGeneration import generate
|
||||
|
||||
|
||||
class BazelCommandLine:
|
||||
def __init__(self, bazel_path, bazel_x86_64_path, override_bazel_version, override_xcode_version):
|
||||
def __init__(self, bazel_path, bazel_x86_64_path, override_bazel_version, override_xcode_version, bazel_user_root):
|
||||
self.build_environment = BuildEnvironment(
|
||||
base_path=os.getcwd(),
|
||||
bazel_path=bazel_path,
|
||||
@ -20,6 +20,7 @@ class BazelCommandLine:
|
||||
override_bazel_version=override_bazel_version,
|
||||
override_xcode_version=override_xcode_version
|
||||
)
|
||||
self.bazel_user_root = bazel_user_root
|
||||
self.remote_cache = None
|
||||
self.cache_dir = None
|
||||
self.additional_args = None
|
||||
@ -173,9 +174,18 @@ class BazelCommandLine:
|
||||
else:
|
||||
raise Exception('Unknown configuration {}'.format(configuration))
|
||||
|
||||
def get_startup_bazel_arguments(self):
|
||||
combined_arguments = []
|
||||
if self.bazel_user_root is not None:
|
||||
combined_arguments += ['--output_user_root={}'.format(self.bazel_user_root)]
|
||||
return combined_arguments
|
||||
|
||||
def invoke_clean(self):
|
||||
combined_arguments = [
|
||||
self.build_environment.bazel_path,
|
||||
self.build_environment.bazel_path
|
||||
]
|
||||
combined_arguments += self.get_startup_bazel_arguments()
|
||||
combined_arguments += [
|
||||
'clean',
|
||||
'--expunge'
|
||||
]
|
||||
@ -209,7 +219,10 @@ class BazelCommandLine:
|
||||
|
||||
def invoke_build(self):
|
||||
combined_arguments = [
|
||||
self.build_environment.bazel_path,
|
||||
self.build_environment.bazel_path
|
||||
]
|
||||
combined_arguments += self.get_startup_bazel_arguments()
|
||||
combined_arguments += [
|
||||
'build',
|
||||
'Telegram/Telegram'
|
||||
]
|
||||
@ -247,7 +260,8 @@ def clean(arguments):
|
||||
bazel_path=arguments.bazel,
|
||||
bazel_x86_64_path=None,
|
||||
override_bazel_version=arguments.overrideBazelVersion,
|
||||
override_xcode_version=arguments.overrideXcodeVersion
|
||||
override_xcode_version=arguments.overrideXcodeVersion,
|
||||
bazel_user_root=arguments.bazelUserRoot
|
||||
)
|
||||
|
||||
bazel_command_line.invoke_clean()
|
||||
@ -292,7 +306,8 @@ def generate_project(arguments):
|
||||
bazel_path=arguments.bazel,
|
||||
bazel_x86_64_path=bazel_x86_64_path,
|
||||
override_bazel_version=arguments.overrideBazelVersion,
|
||||
override_xcode_version=arguments.overrideXcodeVersion
|
||||
override_xcode_version=arguments.overrideXcodeVersion,
|
||||
bazel_user_root=arguments.bazelUserRoot
|
||||
)
|
||||
|
||||
if arguments.cacheDir is not None:
|
||||
@ -326,7 +341,8 @@ def build(arguments):
|
||||
bazel_path=arguments.bazel,
|
||||
bazel_x86_64_path=None,
|
||||
override_bazel_version=arguments.overrideBazelVersion,
|
||||
override_xcode_version=arguments.overrideXcodeVersion
|
||||
override_xcode_version=arguments.overrideXcodeVersion,
|
||||
bazel_user_root=arguments.bazelUserRoot
|
||||
)
|
||||
|
||||
if arguments.cacheDir is not None:
|
||||
@ -383,6 +399,13 @@ if __name__ == '__main__':
|
||||
metavar='path'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--bazelUserRoot',
|
||||
required=False,
|
||||
help='Use custom bazel user root (useful when reproducing a build)',
|
||||
metavar='path'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--overrideBazelVersion',
|
||||
action='store_true',
|
||||
|
@ -57,6 +57,7 @@ private final class ImportManager {
|
||||
case chatAdminRequired
|
||||
case invalidChatType
|
||||
case userBlocked
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
enum State {
|
||||
@ -108,6 +109,11 @@ private final class ImportManager {
|
||||
|
||||
self.stateValue = .progress(totalBytes: self.totalBytes, totalUploadedBytes: 0, totalMediaBytes: self.totalMediaBytes, totalUploadedMediaBytes: 0)
|
||||
|
||||
Logger.shared.log("ChatImportScreen", "Requesting import session for \(peerId), media count: \(entries.count) with pending entries:")
|
||||
for entry in entries {
|
||||
Logger.shared.log("ChatImportScreen", " \(entry.1)")
|
||||
}
|
||||
|
||||
self.disposable.set((ChatHistoryImport.initSession(account: self.account, peerId: peerId, file: mainFile, mediaCount: Int32(entries.count))
|
||||
|> mapError { error -> ImportError in
|
||||
switch error {
|
||||
@ -119,6 +125,8 @@ private final class ImportManager {
|
||||
return .generic
|
||||
case .userBlocked:
|
||||
return .userBlocked
|
||||
case .limitExceeded:
|
||||
return .limitExceeded
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] session in
|
||||
@ -188,28 +196,37 @@ private final class ImportManager {
|
||||
|
||||
private func updateState() {
|
||||
guard let session = self.session else {
|
||||
Logger.shared.log("ChatImportScreen", "updateState called with no session, ignoring")
|
||||
return
|
||||
}
|
||||
if self.pendingEntries.isEmpty && self.activeEntries.isEmpty {
|
||||
Logger.shared.log("ChatImportScreen", "updateState called with no pending and no active entries, completing")
|
||||
self.complete()
|
||||
return
|
||||
}
|
||||
if case .error = self.stateValue {
|
||||
Logger.shared.log("ChatImportScreen", "updateState called after error, ignoring")
|
||||
return
|
||||
}
|
||||
guard let archivePath = self.archivePath else {
|
||||
Logger.shared.log("ChatImportScreen", "updateState called with empty arhivePath, ignoring")
|
||||
return
|
||||
}
|
||||
|
||||
while true {
|
||||
if self.activeEntries.count >= 3 {
|
||||
Logger.shared.log("ChatImportScreen", "updateState concurrent processing limit reached, stop searching")
|
||||
break
|
||||
}
|
||||
if self.pendingEntries.isEmpty {
|
||||
Logger.shared.log("ChatImportScreen", "updateState no more pending entries, stop searching")
|
||||
break
|
||||
}
|
||||
|
||||
let entry = self.pendingEntries.removeFirst()
|
||||
|
||||
Logger.shared.log("ChatImportScreen", "updateState take pending entry \(entry.1)")
|
||||
|
||||
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
|
||||
let tempFile = TempBox.shared.tempFile(fileName: entry.0.path)
|
||||
Logger.shared.log("ChatImportScreen", "Extracting \(entry.0.path) to \(tempFile.path)...")
|
||||
@ -266,6 +283,7 @@ private final class ImportManager {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
Logger.shared.log("ChatImportScreen", "updateState entry \(entry.1) has completed upload")
|
||||
strongSelf.activeEntries.removeValue(forKey: entry.0.path)
|
||||
strongSelf.updateState()
|
||||
}))
|
||||
@ -533,6 +551,8 @@ public final class ChatImportActivityScreen: ViewController {
|
||||
errorText = self.presentationData.strings.ChatImportActivity_ErrorGeneric
|
||||
case .userBlocked:
|
||||
errorText = self.presentationData.strings.ChatImportActivity_ErrorUserBlocked
|
||||
case .limitExceeded:
|
||||
errorText = self.presentationData.strings.ChatImportActivity_ErrorLimitExceeded
|
||||
}
|
||||
self.statusText.attributedText = NSAttributedString(string: errorText, font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemDestructiveColor)
|
||||
case .done:
|
||||
|
@ -1,20 +1,14 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
objc_library(
|
||||
swift_library(
|
||||
name = "Reachability",
|
||||
enable_modules = True,
|
||||
module_name = "Reachability",
|
||||
srcs = glob([
|
||||
"Sources/*.m",
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
"SystemConfiguration",
|
||||
deps = [
|
||||
"//submodules/Reachability/LegacyReachability:LegacyReachability",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
22
submodules/Reachability/LegacyReachability/BUILD
Normal file
22
submodules/Reachability/LegacyReachability/BUILD
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
objc_library(
|
||||
name = "LegacyReachability",
|
||||
enable_modules = True,
|
||||
module_name = "LegacyReachability",
|
||||
srcs = glob([
|
||||
"Sources/*.m",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
"SystemConfiguration",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -24,7 +24,7 @@ typedef enum : NSInteger {
|
||||
extern NSString *kReachabilityChangedNotification;
|
||||
|
||||
|
||||
@interface Reachability : NSObject
|
||||
@interface LegacyReachability : NSObject
|
||||
|
||||
@property (nonatomic, copy) void (^reachabilityChanged)(NetworkStatus status);
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#import <Reachability/Reachability.h>
|
||||
#import <LegacyReachability/LegacyReachability.h>
|
||||
|
||||
#import <pthread.h>
|
||||
#import <libkern/OSAtomic.h>
|
||||
@ -126,14 +126,14 @@ static ReachabilityAtomic *contexts() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void withContext(int32_t key, void (^f)(Reachability *)) {
|
||||
Reachability *reachability = [contexts() with:^id(NSDictionary *dict) {
|
||||
static void withContext(int32_t key, void (^f)(LegacyReachability *)) {
|
||||
LegacyReachability *reachability = [contexts() with:^id(NSDictionary *dict) {
|
||||
return dict[@(key)];
|
||||
}];
|
||||
f(reachability);
|
||||
}
|
||||
|
||||
static int32_t addContext(Reachability *context) {
|
||||
static int32_t addContext(LegacyReachability *context) {
|
||||
int32_t key = OSAtomicIncrement32(&nextKey);
|
||||
[contexts() modify:^id(NSMutableDictionary *dict) {
|
||||
NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
|
||||
@ -155,19 +155,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
{
|
||||
#pragma unused (target, flags)
|
||||
//NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback");
|
||||
//NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback");
|
||||
//NSCAssert([(__bridge NSObject*) info isKindOfClass: [LegacyReachability class]], @"info was wrong class in ReachabilityCallback");
|
||||
|
||||
int32_t key = (int32_t)((intptr_t)info);
|
||||
withContext(key, ^(Reachability *context) {
|
||||
if ([context isKindOfClass:[Reachability class]] && context.reachabilityChanged != nil)
|
||||
withContext(key, ^(LegacyReachability *context) {
|
||||
if ([context isKindOfClass:[LegacyReachability class]] && context.reachabilityChanged != nil)
|
||||
context.reachabilityChanged(context.currentReachabilityStatus);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Reachability implementation
|
||||
#pragma mark - LegacyReachability implementation
|
||||
|
||||
@implementation Reachability
|
||||
@implementation LegacyReachability
|
||||
{
|
||||
int32_t _key;
|
||||
SCNetworkReachabilityRef _reachabilityRef;
|
||||
@ -175,7 +175,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
|
||||
+ (instancetype)reachabilityWithHostName:(NSString *)hostName
|
||||
{
|
||||
Reachability* returnValue = NULL;
|
||||
LegacyReachability* returnValue = NULL;
|
||||
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
|
||||
if (reachability != NULL)
|
||||
{
|
||||
@ -199,7 +199,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach
|
||||
{
|
||||
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress);
|
||||
|
||||
Reachability* returnValue = NULL;
|
||||
LegacyReachability* returnValue = NULL;
|
||||
|
||||
if (reachability != NULL)
|
||||
{
|
180
submodules/Reachability/Sources/Reachability.swift
Normal file
180
submodules/Reachability/Sources/Reachability.swift
Normal file
@ -0,0 +1,180 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
|
||||
import LegacyReachability
|
||||
import Network
|
||||
|
||||
private final class WrappedLegacyReachability: NSObject {
|
||||
@objc private static func threadImpl() {
|
||||
while true {
|
||||
RunLoop.current.run(until: .distantFuture)
|
||||
}
|
||||
}
|
||||
|
||||
private static let thread: Thread = {
|
||||
let thread = Thread(target: Reachability.self, selector: #selector(WrappedLegacyReachability.threadImpl), object: nil)
|
||||
thread.start()
|
||||
return thread
|
||||
}()
|
||||
|
||||
@objc private static func dispatchOnThreadImpl(_ f: @escaping () -> Void) {
|
||||
f()
|
||||
}
|
||||
|
||||
private static func dispatchOnThread(_ f: @escaping @convention(block) () -> Void) {
|
||||
WrappedLegacyReachability.perform(#selector(WrappedLegacyReachability.dispatchOnThreadImpl(_:)), on: WrappedLegacyReachability.thread, with: f, waitUntilDone: false)
|
||||
}
|
||||
|
||||
private let reachability: LegacyReachability
|
||||
|
||||
let value: ValuePromise<Reachability.NetworkType>
|
||||
|
||||
override init() {
|
||||
assert(Thread.current === WrappedLegacyReachability.thread)
|
||||
self.reachability = LegacyReachability.forInternetConnection()
|
||||
let type: Reachability.NetworkType
|
||||
switch self.reachability.currentReachabilityStatus() {
|
||||
case NotReachable:
|
||||
type = .none
|
||||
case ReachableViaWiFi:
|
||||
type = .wifi
|
||||
case ReachableViaWWAN:
|
||||
type = .cellular
|
||||
default:
|
||||
type = .none
|
||||
}
|
||||
self.value = ValuePromise<Reachability.NetworkType>(type)
|
||||
|
||||
super.init()
|
||||
|
||||
self.reachability.reachabilityChanged = { [weak self] status in
|
||||
WrappedLegacyReachability.dispatchOnThread {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let internalNetworkType: Reachability.NetworkType
|
||||
switch status {
|
||||
case NotReachable:
|
||||
internalNetworkType = .none
|
||||
case ReachableViaWiFi:
|
||||
internalNetworkType = .wifi
|
||||
case ReachableViaWWAN:
|
||||
internalNetworkType = .cellular
|
||||
default:
|
||||
internalNetworkType = .none
|
||||
}
|
||||
strongSelf.value.set(internalNetworkType)
|
||||
}
|
||||
}
|
||||
self.reachability.startNotifier()
|
||||
}
|
||||
|
||||
private static var valueRef: Unmanaged<WrappedLegacyReachability>?
|
||||
|
||||
static func withInstance(_ f: @escaping (WrappedLegacyReachability) -> Void) {
|
||||
WrappedLegacyReachability.dispatchOnThread {
|
||||
if self.valueRef == nil {
|
||||
self.valueRef = Unmanaged.passRetained(WrappedLegacyReachability())
|
||||
}
|
||||
if let valueRef = self.valueRef {
|
||||
let value = valueRef.takeUnretainedValue()
|
||||
f(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 12.0, iOS 12.0, OSX 10.14, *)
|
||||
private final class PathMonitor {
|
||||
private let queue: Queue
|
||||
private let monitor: NWPathMonitor
|
||||
|
||||
let networkType = Promise<Reachability.NetworkType>()
|
||||
|
||||
init(queue: Queue) {
|
||||
self.queue = queue
|
||||
self.monitor = NWPathMonitor()
|
||||
|
||||
self.monitor.pathUpdateHandler = { [weak self] path in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let networkType: Reachability.NetworkType
|
||||
if path.status == .satisfied {
|
||||
if path.usesInterfaceType(.cellular) {
|
||||
networkType = .cellular
|
||||
} else {
|
||||
networkType = .wifi
|
||||
}
|
||||
} else {
|
||||
networkType = .none
|
||||
}
|
||||
|
||||
strongSelf.networkType.set(.single(networkType))
|
||||
}
|
||||
}
|
||||
|
||||
self.monitor.start(queue: self.queue.queue)
|
||||
|
||||
let networkType: Reachability.NetworkType
|
||||
let path = self.monitor.currentPath
|
||||
if path.status == .satisfied {
|
||||
if path.usesInterfaceType(.cellular) {
|
||||
networkType = .cellular
|
||||
} else {
|
||||
networkType = .wifi
|
||||
}
|
||||
} else {
|
||||
networkType = .none
|
||||
}
|
||||
|
||||
self.networkType.set(.single(networkType))
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 12.0, iOS 12.0, OSX 10.14, *)
|
||||
private final class SharedPathMonitor {
|
||||
static let queue = Queue()
|
||||
static let impl = QueueLocalObject<PathMonitor>(queue: queue, generate: {
|
||||
return PathMonitor(queue: queue)
|
||||
})
|
||||
}
|
||||
|
||||
public enum Reachability {
|
||||
public enum NetworkType: Equatable {
|
||||
case none
|
||||
case wifi
|
||||
case cellular
|
||||
}
|
||||
|
||||
public static var networkType: Signal<NetworkType, NoError> {
|
||||
if #available(iOSApplicationExtension 12.0, iOS 12.0, OSX 10.14, *) {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
SharedPathMonitor.impl.with { impl in
|
||||
disposable.set(impl.networkType.get().start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
WrappedLegacyReachability.withInstance({ impl in
|
||||
disposable.set(impl.value.get().start(next: { next in
|
||||
subscriber.putNext(next)
|
||||
}))
|
||||
})
|
||||
|
||||
return disposable
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
}
|
||||
}
|
@ -57,7 +57,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
groupCall: nil
|
||||
)))
|
||||
|
||||
self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", peerIds: [], limit: 100)
|
||||
self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||
return .single(nil)
|
||||
@ -228,13 +228,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private let silentTimeout: Int32 = 2
|
||||
|
||||
struct Participant {
|
||||
let ssrc: UInt32
|
||||
let timestamp: Int32
|
||||
let level: Float
|
||||
}
|
||||
|
||||
private var participants: [PeerId: Participant] = [:]
|
||||
private let speakingParticipantsPromise = ValuePromise<Set<PeerId>>()
|
||||
private var speakingParticipants = Set<PeerId>() {
|
||||
private let speakingParticipantsPromise = ValuePromise<[PeerId: UInt32]>()
|
||||
private var speakingParticipants = [PeerId: UInt32]() {
|
||||
didSet {
|
||||
self.speakingParticipantsPromise.set(self.speakingParticipants)
|
||||
}
|
||||
@ -245,17 +246,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
init() {
|
||||
}
|
||||
|
||||
func update(levels: [(PeerId, Float, Bool)]) {
|
||||
func update(levels: [(PeerId, UInt32, Float, Bool)]) {
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent())
|
||||
let currentParticipants: [PeerId: Participant] = self.participants
|
||||
|
||||
var validSpeakers: [PeerId: Participant] = [:]
|
||||
var silentParticipants = Set<PeerId>()
|
||||
var speakingParticipants = Set<PeerId>()
|
||||
for (peerId, level, hasVoice) in levels {
|
||||
var speakingParticipants = [PeerId: UInt32]()
|
||||
for (peerId, ssrc, level, hasVoice) in levels {
|
||||
if level > speakingLevelThreshold && hasVoice {
|
||||
validSpeakers[peerId] = Participant(timestamp: timestamp, level: level)
|
||||
speakingParticipants.insert(peerId)
|
||||
validSpeakers[peerId] = Participant(ssrc: ssrc, timestamp: timestamp, level: level)
|
||||
speakingParticipants[peerId] = ssrc
|
||||
} else {
|
||||
silentParticipants.insert(peerId)
|
||||
}
|
||||
@ -268,17 +269,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
if silentParticipants.contains(peerId) {
|
||||
if delta < silentTimeout {
|
||||
validSpeakers[peerId] = participant
|
||||
speakingParticipants.insert(peerId)
|
||||
speakingParticipants[peerId] = participant.ssrc
|
||||
}
|
||||
} else if delta < cutoffTimeout {
|
||||
validSpeakers[peerId] = participant
|
||||
speakingParticipants.insert(peerId)
|
||||
speakingParticipants[peerId] = participant.ssrc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var audioLevels: [(PeerId, Float, Bool)] = []
|
||||
for (peerId, level, hasVoice) in levels {
|
||||
for (peerId, _, level, hasVoice) in levels {
|
||||
if level > 0.001 {
|
||||
audioLevels.append((peerId, level, hasVoice))
|
||||
}
|
||||
@ -289,8 +290,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.audioLevelsPromise.set(.single(audioLevels))
|
||||
}
|
||||
|
||||
func get() -> Signal<Set<PeerId>, NoError> {
|
||||
return self.speakingParticipantsPromise.get() |> distinctUntilChanged
|
||||
func get() -> Signal<[PeerId: UInt32], NoError> {
|
||||
return self.speakingParticipantsPromise.get()
|
||||
}
|
||||
|
||||
func getAudioLevels() -> Signal<[(PeerId, Float, Bool)], NoError> {
|
||||
@ -887,16 +888,20 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var result: [(PeerId, Float, Bool)] = []
|
||||
var result: [(PeerId, UInt32, Float, Bool)] = []
|
||||
var myLevel: Float = 0.0
|
||||
var myLevelHasVoice: Bool = false
|
||||
var missingSsrcs = Set<UInt32>()
|
||||
for (ssrcKey, level, hasVoice) in levels {
|
||||
var peerId: PeerId?
|
||||
let ssrcValue: UInt32
|
||||
switch ssrcKey {
|
||||
case .local:
|
||||
peerId = strongSelf.accountContext.account.peerId
|
||||
ssrcValue = 0
|
||||
case let .source(ssrc):
|
||||
peerId = strongSelf.ssrcMapping[ssrc]
|
||||
ssrcValue = ssrc
|
||||
}
|
||||
if let peerId = peerId {
|
||||
if case .local = ssrcKey {
|
||||
@ -905,7 +910,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
myLevelHasVoice = hasVoice
|
||||
}
|
||||
}
|
||||
result.append((peerId, level, hasVoice))
|
||||
result.append((peerId, ssrcValue, level, hasVoice))
|
||||
} else if ssrcValue != 0 {
|
||||
missingSsrcs.insert(ssrcValue)
|
||||
}
|
||||
}
|
||||
|
||||
@ -914,6 +921,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let mappedLevel = myLevel * 1.5
|
||||
strongSelf.myAudioLevelPipe.putNext(mappedLevel)
|
||||
strongSelf.processMyAudioLevel(level: mappedLevel, hasVoice: myLevelHasVoice)
|
||||
|
||||
if !missingSsrcs.isEmpty {
|
||||
strongSelf.participantsContext?.ensureHaveParticipants(ssrcs: missingSsrcs)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -985,9 +996,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
|
||||
var reportSpeakingParticipants: [PeerId] = []
|
||||
var reportSpeakingParticipants: [PeerId: UInt32] = [:]
|
||||
let timestamp = CACurrentMediaTime()
|
||||
for peerId in speakingParticipants {
|
||||
for (peerId, ssrc) in speakingParticipants {
|
||||
let shouldReport: Bool
|
||||
if let previousTimestamp = strongSelf.speakingParticipantsReportTimestamp[peerId] {
|
||||
shouldReport = previousTimestamp + 1.0 < timestamp
|
||||
@ -996,7 +1007,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
if shouldReport {
|
||||
strongSelf.speakingParticipantsReportTimestamp[peerId] = timestamp
|
||||
reportSpeakingParticipants.append(peerId)
|
||||
reportSpeakingParticipants[peerId] = ssrc
|
||||
}
|
||||
}
|
||||
|
||||
@ -1008,7 +1019,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
var members = PresentationGroupCallMembers(
|
||||
participants: [],
|
||||
speakingParticipants: speakingParticipants,
|
||||
speakingParticipants: Set(speakingParticipants.keys),
|
||||
totalCount: 0,
|
||||
loadMoreToken: nil
|
||||
)
|
||||
|
@ -1229,7 +1229,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if case let .known(value) = offset, value < 100.0 {
|
||||
if case let .known(value) = offset, value < 200.0 {
|
||||
if let loadMoreToken = strongSelf.currentCallMembers?.1 {
|
||||
strongSelf.currentLoadToken = loadMoreToken
|
||||
strongSelf.call.loadMoreMembers(token: loadMoreToken)
|
||||
|
@ -165,7 +165,10 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
|
||||
case .broadcast:
|
||||
break
|
||||
case .group:
|
||||
let infoFlags = TelegramChannelGroupFlags()
|
||||
var infoFlags = TelegramChannelGroupFlags()
|
||||
if (flags & Int32(1 << 22)) != 0 {
|
||||
infoFlags.insert(.slowModeEnabled)
|
||||
}
|
||||
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ public enum ChatHistoryImport {
|
||||
case chatAdminRequired
|
||||
case invalidChatType
|
||||
case userBlocked
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
public enum ParsedInfo {
|
||||
@ -64,16 +65,17 @@ public enum ChatHistoryImport {
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.initHistoryImport(peer: inputPeer, file: inputFile, mediaCount: mediaCount))
|
||||
return account.network.request(Api.functions.messages.initHistoryImport(peer: inputPeer, file: inputFile, mediaCount: mediaCount), automaticFloodWait: false)
|
||||
|> mapError { error -> InitImportError in
|
||||
switch error.errorDescription {
|
||||
case "CHAT_ADMIN_REQUIRED":
|
||||
if error.errorDescription == "CHAT_ADMIN_REQUIRED" {
|
||||
return .chatAdminRequired
|
||||
case "IMPORT_PEER_TYPE_INVALID":
|
||||
} else if error.errorDescription == "IMPORT_PEER_TYPE_INVALID" {
|
||||
return .invalidChatType
|
||||
case "USER_IS_BLOCKED":
|
||||
} else if error.errorDescription == "USER_IS_BLOCKED" {
|
||||
return .userBlocked
|
||||
default:
|
||||
} else if error.errorDescription == "FLOOD_WAIT" {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
@ -107,7 +109,10 @@ public enum ChatHistoryImport {
|
||||
|
||||
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, mimeType: String, type: MediaType) -> Signal<Float, UploadMediaError> {
|
||||
var forceNoBigParts = true
|
||||
if let size = fileSize(file.path), size >= 30 * 1024 * 1024 {
|
||||
guard let size = fileSize(file.path), size != 0 else {
|
||||
return .single(1.0)
|
||||
}
|
||||
if size >= 30 * 1024 * 1024 {
|
||||
forceNoBigParts = false
|
||||
}
|
||||
|
||||
|
@ -187,8 +187,8 @@ public enum GetGroupCallParticipantsError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, peerIds: [PeerId], limit: Int32) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), ids: peerIds.map { $0.id }, sources: [], offset: offset, limit: limit))
|
||||
public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
|
||||
return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), ids: [], sources: ssrcs.map { Int32(bitPattern: $0) }, offset: offset, limit: limit))
|
||||
|> mapError { _ -> GetGroupCallParticipantsError in
|
||||
return .generic
|
||||
}
|
||||
@ -368,7 +368,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
||||
|> mapError { _ -> JoinGroupCallError in
|
||||
return .generic
|
||||
},
|
||||
getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", peerIds: [], limit: 100)
|
||||
getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", ssrcs: [], limit: 100)
|
||||
|> mapError { _ -> JoinGroupCallError in
|
||||
return .generic
|
||||
},
|
||||
@ -772,7 +772,7 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
private var isLoadingMore: Bool = false
|
||||
private var shouldResetStateFromServer: Bool = false
|
||||
private var missingPeerIds = Set<PeerId>()
|
||||
private var missingSsrcs = Set<UInt32>()
|
||||
|
||||
private let updateDefaultMuteDisposable = MetaDisposable()
|
||||
|
||||
@ -811,8 +811,6 @@ public final class GroupCallParticipantsContext {
|
||||
})
|
||||
strongSelf.activeSpeakersValue = peerIds
|
||||
|
||||
strongSelf.ensureHaveParticipants(peerIds: peerIds)
|
||||
|
||||
if !strongSelf.hasReceivedSpeakingParticipantsReport {
|
||||
var updatedParticipants = strongSelf.stateValue.state.participants
|
||||
var indexMap: [PeerId: Int] = [:]
|
||||
@ -890,7 +888,7 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
}
|
||||
|
||||
public func reportSpeakingParticipants(ids: [PeerId]) {
|
||||
public func reportSpeakingParticipants(ids: [PeerId: UInt32]) {
|
||||
if !ids.isEmpty {
|
||||
self.hasReceivedSpeakingParticipantsReport = true
|
||||
}
|
||||
@ -906,7 +904,7 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
|
||||
for activityPeerId in ids {
|
||||
for (activityPeerId, _) in ids {
|
||||
if let index = indexMap[activityPeerId] {
|
||||
var updateTimestamp = false
|
||||
if let activityTimestamp = updatedParticipants[index].activityTimestamp {
|
||||
@ -947,30 +945,32 @@ public final class GroupCallParticipantsContext {
|
||||
overlayState: strongSelf.stateValue.overlayState
|
||||
)
|
||||
}
|
||||
|
||||
self.ensureHaveParticipants(ssrcs: Set(ids.map { $0.1 }))
|
||||
}
|
||||
|
||||
private func ensureHaveParticipants(peerIds: Set<PeerId>) {
|
||||
var missingPeerIds = Set<PeerId>()
|
||||
public func ensureHaveParticipants(ssrcs: Set<UInt32>) {
|
||||
var missingSsrcs = Set<UInt32>()
|
||||
|
||||
var existingParticipantIds = Set<PeerId>()
|
||||
var existingSsrcs = Set<UInt32>()
|
||||
for participant in self.stateValue.state.participants {
|
||||
existingParticipantIds.insert(participant.peer.id)
|
||||
existingSsrcs.insert(participant.ssrc)
|
||||
}
|
||||
|
||||
for peerId in peerIds {
|
||||
if !existingParticipantIds.contains(peerId) {
|
||||
missingPeerIds.insert(peerId)
|
||||
for ssrc in ssrcs {
|
||||
if !existingSsrcs.contains(ssrc) {
|
||||
missingSsrcs.insert(ssrc)
|
||||
}
|
||||
}
|
||||
|
||||
if !missingPeerIds.isEmpty {
|
||||
self.missingPeerIds.formUnion(missingPeerIds)
|
||||
self.loadMissingPeers()
|
||||
if !missingSsrcs.isEmpty {
|
||||
self.missingSsrcs.formUnion(missingSsrcs)
|
||||
self.loadMissingSsrcs()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadMissingPeers() {
|
||||
if self.missingPeerIds.isEmpty {
|
||||
private func loadMissingSsrcs() {
|
||||
if self.missingSsrcs.isEmpty {
|
||||
return
|
||||
}
|
||||
if self.isLoadingMore {
|
||||
@ -978,16 +978,16 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
self.isLoadingMore = true
|
||||
|
||||
let peerIds = self.missingPeerIds
|
||||
let ssrcs = self.missingSsrcs
|
||||
|
||||
self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", peerIds: Array(peerIds), limit: 100)
|
||||
self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: Array(ssrcs), limit: 100)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isLoadingMore = false
|
||||
|
||||
strongSelf.missingPeerIds.subtract(peerIds)
|
||||
strongSelf.missingSsrcs.subtract(ssrcs)
|
||||
|
||||
var updatedState = strongSelf.stateValue.state
|
||||
|
||||
@ -1013,7 +1013,7 @@ public final class GroupCallParticipantsContext {
|
||||
if strongSelf.shouldResetStateFromServer {
|
||||
strongSelf.resetStateFromServer()
|
||||
} else {
|
||||
strongSelf.loadMissingPeers()
|
||||
strongSelf.loadMissingSsrcs()
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -1165,7 +1165,7 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
self.updateQueue.removeAll()
|
||||
|
||||
self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", peerIds: [], limit: 100)
|
||||
self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: [], limit: 100)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1285,7 +1285,7 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
self.isLoadingMore = true
|
||||
|
||||
self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: token, peerIds: [], limit: 100)
|
||||
self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: token, ssrcs: [], limit: 100)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -34,12 +34,6 @@ extension CellularNetworkType {
|
||||
|
||||
#endif
|
||||
|
||||
enum InternalNetworkType: Equatable {
|
||||
case none
|
||||
case wifi
|
||||
case cellular
|
||||
}
|
||||
|
||||
public enum NetworkType: Equatable {
|
||||
case none
|
||||
case wifi
|
||||
@ -50,7 +44,7 @@ public enum NetworkType: Equatable {
|
||||
|
||||
extension NetworkType {
|
||||
#if os(iOS)
|
||||
init(internalType: InternalNetworkType, cellularType: CellularNetworkType) {
|
||||
init(internalType: Reachability.NetworkType, cellularType: CellularNetworkType) {
|
||||
switch internalType {
|
||||
case .none:
|
||||
self = .none
|
||||
@ -61,7 +55,7 @@ extension NetworkType {
|
||||
}
|
||||
}
|
||||
#else
|
||||
init(internalType: InternalNetworkType) {
|
||||
init(internalType: Reachability.NetworkType) {
|
||||
switch internalType {
|
||||
case .none:
|
||||
self = .none
|
||||
@ -72,91 +66,11 @@ extension NetworkType {
|
||||
#endif
|
||||
}
|
||||
|
||||
private final class WrappedReachability: NSObject {
|
||||
@objc private static func threadImpl() {
|
||||
while true {
|
||||
RunLoop.current.run(until: .distantFuture)
|
||||
}
|
||||
}
|
||||
|
||||
static let thread: Thread = {
|
||||
let thread = Thread(target: WrappedReachability.self, selector: #selector(WrappedReachability.threadImpl), object: nil)
|
||||
thread.start()
|
||||
return thread
|
||||
}()
|
||||
|
||||
@objc private static func dispatchOnThreadImpl(_ f: @escaping () -> Void) {
|
||||
f()
|
||||
}
|
||||
|
||||
private static func dispatchOnThread(_ f: @escaping @convention(block) () -> Void) {
|
||||
WrappedReachability.perform(#selector(WrappedReachability.dispatchOnThreadImpl(_:)), on: WrappedReachability.thread, with: f, waitUntilDone: false)
|
||||
}
|
||||
|
||||
private let reachability: Reachability
|
||||
|
||||
let value: ValuePromise<InternalNetworkType>
|
||||
|
||||
override init() {
|
||||
assert(Thread.current === WrappedReachability.thread)
|
||||
self.reachability = Reachability.forInternetConnection()
|
||||
let type: InternalNetworkType
|
||||
switch self.reachability.currentReachabilityStatus() {
|
||||
case NotReachable:
|
||||
type = .none
|
||||
case ReachableViaWiFi:
|
||||
type = .wifi
|
||||
case ReachableViaWWAN:
|
||||
type = .cellular
|
||||
default:
|
||||
type = .none
|
||||
}
|
||||
self.value = ValuePromise<InternalNetworkType>(type)
|
||||
|
||||
super.init()
|
||||
|
||||
self.reachability.reachabilityChanged = { [weak self] status in
|
||||
WrappedReachability.dispatchOnThread {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let internalNetworkType: InternalNetworkType
|
||||
switch status {
|
||||
case NotReachable:
|
||||
internalNetworkType = .none
|
||||
case ReachableViaWiFi:
|
||||
internalNetworkType = .wifi
|
||||
case ReachableViaWWAN:
|
||||
internalNetworkType = .cellular
|
||||
default:
|
||||
internalNetworkType = .none
|
||||
}
|
||||
strongSelf.value.set(internalNetworkType)
|
||||
}
|
||||
}
|
||||
self.reachability.startNotifier()
|
||||
}
|
||||
|
||||
static var valueRef: Unmanaged<WrappedReachability>?
|
||||
|
||||
static func withInstance(_ f: @escaping (WrappedReachability) -> Void) {
|
||||
WrappedReachability.dispatchOnThread {
|
||||
if self.valueRef == nil {
|
||||
self.valueRef = Unmanaged.passRetained(WrappedReachability())
|
||||
}
|
||||
if let valueRef = self.valueRef {
|
||||
let value = valueRef.takeUnretainedValue()
|
||||
f(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class NetworkTypeManagerImpl {
|
||||
let queue: Queue
|
||||
let updated: (NetworkType) -> Void
|
||||
var networkTypeDisposable: Disposable?
|
||||
var currentNetworkType: InternalNetworkType?
|
||||
var currentNetworkType: Reachability.NetworkType?
|
||||
var networkType: NetworkType?
|
||||
#if os(iOS)
|
||||
var currentCellularType: CellularNetworkType
|
||||
@ -196,29 +110,27 @@ private final class NetworkTypeManagerImpl {
|
||||
|
||||
let networkTypeDisposable = MetaDisposable()
|
||||
self.networkTypeDisposable = networkTypeDisposable
|
||||
|
||||
WrappedReachability.withInstance({ [weak self] impl in
|
||||
networkTypeDisposable.set((impl.value.get()
|
||||
|> deliverOn(queue)).start(next: { networkStatus in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
||||
networkTypeDisposable.set((Reachability.networkType
|
||||
|> deliverOn(queue)).start(next: { [weak self] networkStatus in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.currentNetworkType != networkStatus {
|
||||
strongSelf.currentNetworkType = networkStatus
|
||||
|
||||
let networkType: NetworkType
|
||||
#if os(iOS)
|
||||
networkType = NetworkType(internalType: networkStatus, cellularType: strongSelf.currentCellularType)
|
||||
#else
|
||||
networkType = NetworkType(internalType: networkStatus)
|
||||
#endif
|
||||
if strongSelf.networkType != networkType {
|
||||
strongSelf.networkType = networkType
|
||||
updated(networkType)
|
||||
}
|
||||
if strongSelf.currentNetworkType != networkStatus {
|
||||
strongSelf.currentNetworkType = networkStatus
|
||||
|
||||
let networkType: NetworkType
|
||||
#if os(iOS)
|
||||
networkType = NetworkType(internalType: networkStatus, cellularType: strongSelf.currentCellularType)
|
||||
#else
|
||||
networkType = NetworkType(internalType: networkStatus)
|
||||
#endif
|
||||
if strongSelf.networkType != networkType {
|
||||
strongSelf.networkType = networkType
|
||||
updated(networkType)
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func stop() {
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -73,6 +73,32 @@ public struct ShareRootControllerInitializationData {
|
||||
}
|
||||
}
|
||||
|
||||
private func extractTextFileHeader(path: String) -> String? {
|
||||
guard let file = ManagedFile(queue: nil, path: path, mode: .read) else {
|
||||
return nil
|
||||
}
|
||||
guard let size = file.getSize() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let limit = 3000
|
||||
|
||||
var data = file.readData(count: min(size, limit))
|
||||
let additionalCapacity = min(10, max(0, size - data.count))
|
||||
|
||||
for alignment in 0 ... additionalCapacity {
|
||||
if alignment != 0 {
|
||||
data.append(file.readData(count: 1))
|
||||
}
|
||||
if let text = String(data: data, encoding: .utf8) {
|
||||
return text
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public class ShareRootControllerImpl {
|
||||
private let initializationData: ShareRootControllerInitializationData
|
||||
private let getExtensionContext: () -> NSExtensionContext?
|
||||
@ -523,11 +549,11 @@ public class ShareRootControllerImpl {
|
||||
return try? NSRegularExpression(pattern: string)
|
||||
}
|
||||
|
||||
if let mainFileText = try? String(contentsOf: URL(fileURLWithPath: url.path)) {
|
||||
let fullRange = NSRange(mainFileText.startIndex ..< mainFileText.endIndex, in: mainFileText)
|
||||
if let mainFileTextHeader = extractTextFileHeader(path: url.path) {
|
||||
let fullRange = NSRange(mainFileTextHeader.startIndex ..< mainFileTextHeader.endIndex, in: mainFileTextHeader)
|
||||
var foundMatch = false
|
||||
for pattern in filePatterns {
|
||||
if pattern.firstMatch(in: mainFileText, options: [], range: fullRange) != nil {
|
||||
if pattern.firstMatch(in: mainFileTextHeader, options: [], range: fullRange) != nil {
|
||||
foundMatch = true
|
||||
break
|
||||
}
|
||||
@ -550,14 +576,7 @@ public class ShareRootControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
if let mainFile = mainFile, let mainFileText = try? String(contentsOf: URL(fileURLWithPath: mainFile.path)) {
|
||||
let mainFileHeader: String
|
||||
if mainFileText.count < 2000 {
|
||||
mainFileHeader = mainFileText
|
||||
} else {
|
||||
mainFileHeader = String(mainFileText[mainFileText.startIndex ..< mainFileText.index(mainFileText.startIndex, offsetBy: 2000)])
|
||||
}
|
||||
|
||||
if let mainFile = mainFile, let mainFileHeader = extractTextFileHeader(path :mainFile.path) {
|
||||
final class TempController: ViewController {
|
||||
override public var _presentedInModal: Bool {
|
||||
get {
|
||||
|
Loading…
x
Reference in New Issue
Block a user