Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2021-01-29 17:36:49 +03:00
commit b740522e3c
20 changed files with 3656 additions and 3324 deletions

139
.github/workflows/build.yml vendored Normal file
View 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

View File

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

View File

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

View File

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

View File

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

View 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",
],
)

View File

@ -24,7 +24,7 @@ typedef enum : NSInteger {
extern NSString *kReachabilityChangedNotification;
@interface Reachability : NSObject
@interface LegacyReachability : NSObject
@property (nonatomic, copy) void (^reachabilityChanged)(NetworkStatus status);

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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