Merge branch 'master' into gift-resale

This commit is contained in:
Ilya Laktyushin 2025-04-12 13:35:10 +04:00
commit 66e8f140c1
1570 changed files with 580 additions and 712341 deletions

3
.gitmodules vendored
View File

@ -29,3 +29,6 @@ url=../tgcalls.git
[submodule "third-party/dav1d/dav1d"]
path = third-party/dav1d/dav1d
url = https://github.com/ali-fareed/dav1d.git
[submodule "third-party/td/td"]
path = third-party/td/td
url = https://github.com/tdlib/td

View File

@ -14115,3 +14115,70 @@ Sorry for the inconvenience.";
"WebApp.ImportData.AccountHeader" = "ACCOUNT TO IMPORT DATA FROM";
"WebApp.ImportData.CreatedOn" = "created on %@";
"WebApp.ImportData.Import" = "Import";
"CallList.ToastCallLinkCopied.Text" = "Call link copied";
"CallList.ToastCallLinkCopied.Action" = "View Call";
"CallList.NewCall" = "Start New Call";
"CallList.NewCallLink" = "Create Call Link";
"Chat.SendStarsToBecomeTopInfo" = "Send %@ or more to highlight your profile";
"VideoChat.RevokeLink" = "Revoke Link";
"InviteLink.GroupCallLinkHelp" = "Anyone on Telegram can join your call by following the link below.";
"InviteLink.CallLinkTitle" = "Call Link";
"InviteLink.CreatedGroupCallFooter" = "Be the first to join the call and add people from there. [Open Call >](open_call)";
"InviteLink.QRCode.InfoGroupCall" = "Everyone on Telegram can scan this code to join your group call.";
"Call.GenericGroupCallTitle" = "Group Call";
"VideoChat.EncryptionKeyLabel" = "End-to-end encrypted";
"VideoChat.EncryptionKeyText" = "These four emojis represent the call's encryption key. They must match for all participants and change when someone joins or leaves.";
"VideoChat.EncryptionKeyDone" = "Close";
"VideoChat.InviteMember" = "Add Member";
"VideoChat.GroupCallTitle" = "Group Call";
"Chat.ViewGroupCall" = "JOIN GROUP CALL";
"NewCall.SearchPlaceholder" = "Search for contacts or usernames";
"NewCall.VideoOption" = "Call with video enabled";
"NewCall.ActionCallSingle" = "Call %@";
"NewCall.ActionCallMultiple" = "Call";
"Chat.ToastCallLinkExpired.Text" = "This link is no longer active";
"Chat.CallMessage.GroupCallParticipantCount_1" = "1 person";
"Chat.CallMessage.GroupCallParticipantCount_any" = "%d people";
"Chat.CallMessage.DeclinedGroupCall" = "Declined Group Call";
"Chat.CallMessage.MissedGroupCall" = "Missed Group Call";
"Chat.CallMessage.CancelledGroupCall" = "Cancelled Group Call";
"Chat.CallMessage.IncomingGroupCall" = "Incoming Group Call";
"Chat.CallMessage.OutgoingGroupCall" = "Outgoing Group Call";
"Invitation.GroupCall" = "Group Call";
"Invitation.JoinGroupCall" = "Join Group Call";
"Invitation.PublicGroup" = "public group";
"Invitation.PrivateGroup" = "private group";
"Invitation.GroupCall.Text" = "You are invited to join a group call.";
"Invitation.Group.AlreadyJoinedSingle" = "**%@** already joined this group.";
"Invitation.Group.AlreadyJoinedMultiple" = "%@ already joined this group.";
"Invitation.Group.AlreadyJoinedMultipleWithCount_1" = "{} and **%d** other person already joined this group.";
"Invitation.Group.AlreadyJoinedMultipleWithCount_any" = "{} and **%d** other people already joined this group.";
"Invitation.GroupCall.AlreadyJoinedSingle" = "**%@** already joined this call.";
"Invitation.GroupCall.AlreadyJoinedMultiple" = "%@ already joined this call.";
"Invitation.GroupCall.AlreadyJoinedMultipleWithCount_1" = "{} and **%d** other person already joined this call.";
"Invitation.GroupCall.AlreadyJoinedMultipleWithCount_any" = "{} and **%d** other people already joined this call.";
"Call.ShareLink" = "Share Call Link";
"Call.AddMemberTitle" = "Add Member";
"Call.IncomingGroupCallTitle.Single" = "%@";
"Call.IncomingGroupCallTitle.Multiple_1" = "{} and 1 other";
"Call.IncomingGroupCallTitle.Multiple_any" = "{} and %d others";
"GroupCall.RevokeLinkText" = "Are you sure you want to revoke this link? Once you do, no one will be able to join the call using it.";

View File

@ -99,6 +99,12 @@ public enum ContactListPeer: Equatable {
}
public final class ContactSelectionControllerParams {
public enum MultipleSelectionMode {
case disabled
case possible
case always
}
public let context: AccountContext
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let mode: ContactSelectionControllerMode
@ -107,7 +113,7 @@ public final class ContactSelectionControllerParams {
public let options: Signal<[ContactListAdditionalOption], NoError>
public let displayDeviceContacts: Bool
public let displayCallIcons: Bool
public let multipleSelection: Bool
public let multipleSelection: MultipleSelectionMode
public let requirePhoneNumbers: Bool
public let allowChannelsInSearch: Bool
public let confirmation: (ContactListPeer) -> Signal<Bool, NoError>
@ -124,7 +130,7 @@ public final class ContactSelectionControllerParams {
options: Signal<[ContactListAdditionalOption], NoError> = .single([]),
displayDeviceContacts: Bool = false,
displayCallIcons: Bool = false,
multipleSelection: Bool = false,
multipleSelection: MultipleSelectionMode = .disabled,
requirePhoneNumbers: Bool = false,
allowChannelsInSearch: Bool = false,
confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) },

View File

@ -425,9 +425,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
isVideo = conferenceCall.flags.contains(.isVideo)
if message.flags.contains(.Incoming) {
hasIncoming = true
//TODO:localize
let missedTimeout: Int32
#if DEBUG
#if DEBUG && false
missedTimeout = 5
#else
missedTimeout = 30
@ -463,8 +462,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
peersString.append(", ")
}
if peer.id == item.context.account.peerId {
//TODO:localize
peersString += "You"
peersString += item.presentationData.strings.DialogList_You
} else {
peersString += peer.compactDisplayTitle
}

View File

@ -108,7 +108,8 @@ public final class CallListController: TelegramBaseController {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
if case .tab = self.mode {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed))
//self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed))
self.navigationItem.rightBarButtonItem = nil
let icon: UIImage?
if useSpecialTabBarIcons() {
@ -191,7 +192,7 @@ public final class CallListController: TelegramBaseController {
}
}
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed))
//self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed))
case .navigation:
if self.editingMode {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
@ -296,9 +297,8 @@ public final class CallListController: TelegramBaseController {
if let result {
switch result {
case .linkCopied:
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: presentationData.strings.CallList_ToastCallLinkCopied_Text, customUndoText: presentationData.strings.CallList_ToastCallLinkCopied_Action, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .undo = action {
openCall()
}
@ -383,7 +383,8 @@ public final class CallListController: TelegramBaseController {
})
} else {
strongSelf.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)), animated: true)
strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(strongSelf.presentationData.theme), style: .plain, target: self, action: #selector(strongSelf.callPressed)), animated: true)
//strongSelf.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(strongSelf.presentationData.theme), style: .plain, target: self, action: #selector(strongSelf.callPressed)), animated: true)
strongSelf.navigationItem.setRightBarButton(nil, animated: true)
}
case .navigation:
if strongSelf.editingMode {
@ -399,9 +400,9 @@ public final class CallListController: TelegramBaseController {
}
}
}
}, createGroupCall: { [weak self] in
}, openNewCall: { [weak self] in
if let strongSelf = self {
strongSelf.createGroupCall(peerIds: [], isVideo: false)
strongSelf.callPressed()
}
})
@ -515,8 +516,7 @@ public final class CallListController: TelegramBaseController {
return
}
//TODO:localize
let options = [ContactListAdditionalOption(title: "New Call Link", icon: .generic(PresentationResourcesItemList.linkIcon(presentationData.theme)!), action: { [weak self] in
let options = [ContactListAdditionalOption(title: self.presentationData.strings.CallList_NewCallLink, icon: .generic(PresentationResourcesItemList.linkIcon(presentationData.theme)!), action: { [weak self] in
guard let self else {
return
}
@ -652,7 +652,8 @@ public final class CallListController: TelegramBaseController {
switch self.mode {
case .tab:
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)), animated: true)
self.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed)), animated: true)
self.navigationItem.setRightBarButton(nil, animated: true)
//self.navigationItem.setRightBarButton(UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed)), animated: true)
case .navigation:
self.navigationItem.setLeftBarButton(nil, animated: true)
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)), animated: true)
@ -738,8 +739,7 @@ public final class CallListController: TelegramBaseController {
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
default:
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
self.present(textAlertController(context: self.context, title: nil, text: "An error occurred", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
self.present(textAlertController(context: self.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
})
}

View File

@ -67,16 +67,16 @@ final class CallListNodeInteraction {
let delete: ([EngineMessage.Id]) -> Void
let updateShowCallsTab: (Bool) -> Void
let openGroupCall: (EnginePeer.Id) -> Void
let createGroupCall: () -> Void
let openNewCall: () -> Void
init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EngineMessage) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void, createGroupCall: @escaping () -> Void) {
init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EngineMessage) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void, openNewCall: @escaping () -> Void) {
self.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions
self.call = call
self.openInfo = openInfo
self.delete = delete
self.updateShowCallsTab = updateShowCallsTab
self.openGroupCall = openGroupCall
self.createGroupCall = createGroupCall
self.openNewCall = openNewCall
}
}
@ -125,10 +125,9 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
}), directionHint: entry.directionHint)
case let .displayTabInfo(_, text):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
case .createGroupCall:
//TODO:localize
let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "New Call Link", hasSeparator: false, sectionId: 1, noInsets: true, editing: false, action: {
nodeInteraction.createGroupCall()
case .openNewCall:
let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: true, editing: false, action: {
nodeInteraction.openNewCall()
})
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .groupCall(peer, _, isActive):
@ -150,10 +149,9 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
}), directionHint: entry.directionHint)
case let .displayTabInfo(_, text):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
case .createGroupCall:
//TODO:localize
let item = ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "New Call Link", sectionId: 1, noInsets: true, editing: false, action: {
nodeInteraction.createGroupCall()
case .openNewCall:
let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: true, editing: false, action: {
nodeInteraction.openNewCall()
})
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .groupCall(peer, _, isActive):
@ -224,7 +222,7 @@ final class CallListControllerNode: ASDisplayNode {
private let call: (EngineMessage) -> Void
private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void
private let createGroupCall: () -> Void
private let openNewCall: () -> Void
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
private let emptyStateUpdated: (Bool) -> Void
private let emptyStatePromise = Promise<Bool>()
@ -234,7 +232,7 @@ final class CallListControllerNode: ASDisplayNode {
private var previousContentOffset: ListViewVisibleContentOffset?
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EngineMessage) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, createGroupCall: @escaping () -> Void) {
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EngineMessage) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, openNewCall: @escaping () -> Void) {
self.controller = controller
self.context = context
self.mode = mode
@ -243,7 +241,7 @@ final class CallListControllerNode: ASDisplayNode {
self.joinGroupCall = joinGroupCall
self.openInfo = openInfo
self.emptyStateUpdated = emptyStateUpdated
self.createGroupCall = createGroupCall
self.openNewCall = openNewCall
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
@ -447,11 +445,11 @@ final class CallListControllerNode: ASDisplayNode {
strongSelf.joinGroupCall(peerId, activeCall)
}
}))
}, createGroupCall: { [weak self] in
}, openNewCall: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.createGroupCall()
strongSelf.openNewCall()
})
let viewProcessingQueue = self.viewProcessingQueue
@ -517,27 +515,17 @@ final class CallListControllerNode: ASDisplayNode {
}
|> distinctUntilChanged
let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|> map { configuration -> Bool in
var isConferencePossible = true
if let data = configuration.data, let value = data["ios_enable_conference"] as? Double {
isConferencePossible = value != 0.0
}
return isConferencePossible
}
let callListNodeViewTransition = combineLatest(
callListViewUpdate,
self.statePromise.get(),
groupCalls,
showCallsTab,
currentGroupCallPeerId,
canCreateGroupCall
currentGroupCallPeerId
)
|> mapToQueue { (updateAndType, state, groupCalls, showCallsTab, currentGroupCallPeerId, canCreateGroupCall) -> Signal<CallListNodeListViewTransition, NoError> in
|> mapToQueue { (updateAndType, state, groupCalls, showCallsTab, currentGroupCallPeerId) -> Signal<CallListNodeListViewTransition, NoError> in
let (update, type) = updateAndType
let processedView = CallListNodeView(originalView: update.view, filteredEntries: callListNodeEntriesForView(view: update.view, canCreateGroupCall: canCreateGroupCall && mode == .tab, groupCalls: groupCalls, state: state, showSettings: showSettings, showCallsTab: showCallsTab, isRecentCalls: type == .all, currentGroupCallPeerId: currentGroupCallPeerId), presentationData: state.presentationData)
let processedView = CallListNodeView(originalView: update.view, filteredEntries: callListNodeEntriesForView(view: update.view, displayOpenNewCall: mode == .tab && type == .all, groupCalls: groupCalls, state: state, showSettings: showSettings, showCallsTab: showCallsTab, isRecentCalls: type == .all, currentGroupCallPeerId: currentGroupCallPeerId), presentationData: state.presentationData)
let previous = previousView.swap(processedView)
let previousType = previousType.swap(type)

View File

@ -25,7 +25,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
enum SortIndex: Comparable {
case displayTab
case displayTabInfo
case createGroupCall
case openNewCall
case groupCall(EnginePeer.Id, String)
case message(EngineMessage.Index)
case hole(EngineMessage.Index)
@ -41,7 +41,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
default:
return false
}
case .createGroupCall:
case .openNewCall:
switch rhs {
case .displayTab, .displayTabInfo:
return false
@ -50,7 +50,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
}
case let .groupCall(lhsPeerId, lhsTitle):
switch rhs {
case .displayTab, .displayTabInfo, .createGroupCall:
case .displayTab, .displayTabInfo, .openNewCall:
return false
case let .groupCall(rhsPeerId, rhsTitle):
if lhsTitle == rhsTitle {
@ -63,7 +63,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
}
case let .hole(lhsIndex):
switch rhs {
case .displayTab, .displayTabInfo, .groupCall, .createGroupCall:
case .displayTab, .displayTabInfo, .groupCall, .openNewCall:
return false
case let .hole(rhsIndex):
return lhsIndex < rhsIndex
@ -72,7 +72,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
}
case let .message(lhsIndex):
switch rhs {
case .displayTab, .displayTabInfo, .groupCall, .createGroupCall:
case .displayTab, .displayTabInfo, .groupCall, .openNewCall:
return false
case let .hole(rhsIndex):
return lhsIndex < rhsIndex
@ -86,7 +86,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
case displayTab(PresentationTheme, String, Bool)
case displayTabInfo(PresentationTheme, String)
case createGroupCall
case openNewCall
case groupCall(peer: EnginePeer, editing: Bool, isActive: Bool)
case messageEntry(topMessage: EngineMessage, messages: [EngineMessage], theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, editing: Bool, hasActiveRevealControls: Bool, displayHeader: Bool, missed: Bool)
case holeEntry(index: EngineMessage.Index, theme: PresentationTheme)
@ -97,8 +97,8 @@ enum CallListNodeEntry: Comparable, Identifiable {
return .displayTab
case .displayTabInfo:
return .displayTabInfo
case .createGroupCall:
return .createGroupCall
case .openNewCall:
return .openNewCall
case let .groupCall(peer, _, _):
return .groupCall(peer.id, peer.compactDisplayTitle)
case let .messageEntry(message, _, _, _, _, _, _, _, _):
@ -114,7 +114,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
return .setting(0)
case .displayTabInfo:
return .setting(1)
case .createGroupCall:
case .openNewCall:
return .setting(2)
case let .groupCall(peer, _, _):
return .groupCall(peer.id)
@ -143,8 +143,8 @@ enum CallListNodeEntry: Comparable, Identifiable {
} else {
return false
}
case .createGroupCall:
if case .createGroupCall = rhs {
case .openNewCall:
if case .openNewCall = rhs {
return true
} else {
return false
@ -212,7 +212,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
}
}
func callListNodeEntriesForView(view: EngineCallList, canCreateGroupCall: Bool, groupCalls: [EnginePeer], state: CallListNodeState, showSettings: Bool, showCallsTab: Bool, isRecentCalls: Bool, currentGroupCallPeerId: EnginePeer.Id?) -> [CallListNodeEntry] {
func callListNodeEntriesForView(view: EngineCallList, displayOpenNewCall: Bool, groupCalls: [EnginePeer], state: CallListNodeState, showSettings: Bool, showCallsTab: Bool, isRecentCalls: Bool, currentGroupCallPeerId: EnginePeer.Id?) -> [CallListNodeEntry] {
var result: [CallListNodeEntry] = []
for entry in view.items {
switch entry {
@ -237,8 +237,8 @@ func callListNodeEntriesForView(view: EngineCallList, canCreateGroupCall: Bool,
}
}
if canCreateGroupCall {
result.append(.createGroupCall)
if displayOpenNewCall {
result.append(.openNewCall)
}
if showSettings {

View File

@ -298,9 +298,25 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = invoice.title
case let action as TelegramMediaAction:
switch action.action {
case .conferenceCall:
//TODO:localize
messageText = "Group call"
case let .conferenceCall(conferenceCall):
let incoming = message.flags.contains(.Incoming)
let missedTimeout: Int32 = 30
let currentTime = Int32(Date().timeIntervalSince1970)
if conferenceCall.flags.contains(.isMissed) {
messageText = strings.Chat_CallMessage_DeclinedGroupCall
} else if message.timestamp < currentTime - missedTimeout {
messageText = strings.Chat_CallMessage_MissedGroupCall
} else if conferenceCall.duration != nil {
messageText = strings.Chat_CallMessage_CancelledGroupCall
} else {
if incoming {
messageText = strings.Chat_CallMessage_IncomingGroupCall
} else {
messageText = strings.Chat_CallMessage_OutgoingGroupCall
}
}
case let .phoneCall(_, discardReason, _, isVideo):
hideAuthor = !isPeerGroup
let incoming = message.flags.contains(.Incoming)

View File

@ -474,7 +474,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
if let requestAddContact = self?.requestAddContact {
requestAddContact(phoneNumber)
}
}, openPeer: { [weak self] peer in
}, openPeer: { [weak self] peer, _ in
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch {
requestOpenPeerFromSearch(peer)
}

View File

@ -52,14 +52,14 @@ private enum ContactListSearchEntryId: Hashable {
}
private enum ContactListSearchEntry: Comparable, Identifiable {
case addContact(PresentationTheme, PresentationStrings, String)
case peer(Int, PresentationTheme, PresentationStrings, ContactListPeer, EnginePeer.Presence?, ContactListSearchGroup, Bool, Bool)
case addContact(theme: PresentationTheme, strings: PresentationStrings, phoneNumber: String)
case peer(index: Int, theme: PresentationTheme, strings: PresentationStrings, peer: ContactListPeer, presence: EnginePeer.Presence?, group: ContactListSearchGroup, enabled: Bool, requiresPremiumForMessaging: Bool, displayCallIcons: Bool)
var stableId: ContactListSearchEntryId {
switch self {
case .addContact:
return .addContact
case let .peer(_, _, _, peer, _, _, _, _):
case let .peer(_, _, _, peer, _, _, _, _, _):
return .peerId(peer.id)
}
}
@ -72,9 +72,9 @@ private enum ContactListSearchEntry: Comparable, Identifiable {
} else {
return false
}
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsPeer, lhsPresence, lhsGroup, lhsEnabled, lhsRequiresPremiumForMessaging):
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsPeer, lhsPresence, lhsGroup, lhsEnabled, lhsRequiresPremiumForMessaging, lhsDisplayCallIcons):
switch rhs {
case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsPeer, rhsPresence, rhsGroup, rhsEnabled, rhsRequiresPremiumForMessaging):
case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsPeer, rhsPresence, rhsGroup, rhsEnabled, rhsRequiresPremiumForMessaging, rhsDisplayCallIcons):
if lhsIndex != rhsIndex {
return false
}
@ -103,6 +103,9 @@ private enum ContactListSearchEntry: Comparable, Identifiable {
if lhsRequiresPremiumForMessaging != rhsRequiresPremiumForMessaging {
return false
}
if lhsDisplayCallIcons != rhsDisplayCallIcons {
return false
}
return true
default:
return false
@ -114,23 +117,23 @@ private enum ContactListSearchEntry: Comparable, Identifiable {
switch lhs {
case .addContact:
return true
case let .peer(lhsIndex, _, _, _, _, _, _, _):
case let .peer(lhsIndex, _, _, _, _, _, _, _, _):
switch rhs {
case .addContact:
return false
case let .peer(rhsIndex, _, _, _, _, _, _, _):
case let .peer(rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex
}
}
}
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) -> ListViewItem {
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, isPeerEnabled: @escaping (ContactListPeer) -> Bool, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) -> ListViewItem {
switch self {
case let .addContact(theme, strings, phoneNumber):
return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
addContact?(phoneNumber)
})
case let .peer(_, theme, strings, peer, presence, group, enabled, requiresPremiumForMessaging):
case let .peer(_, theme, strings, peer, presence, group, enabled, requiresPremiumForMessaging, displayCallIcons):
let header: ListViewItemHeader
let status: ContactsPeerItemStatus
switch group {
@ -161,8 +164,16 @@ private enum ContactListSearchEntry: Comparable, Identifiable {
case let .deviceContact(stableId, contact):
peerItem = .deviceContact(stableId: stableId, contact: contact)
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: peerItem, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
openPeer(peer)
var additionalActions: [ContactsPeerItemAction] = []
if displayCallIcons {
additionalActions = [ContactsPeerItemAction(icon: .voiceCall, action: { _, sourceNode, gesture in
openPeer(peer, .voiceCall)
}), ContactsPeerItemAction(icon: .videoCall, action: { _, sourceNode, gesture in
openPeer(peer, .videoCall)
})]
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: peerItem, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled && isPeerEnabled(peer), selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
openPeer(peer, .generic)
}, disabledAction: { _ in
if case let .peer(peer, _, _) = peer {
openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic)
@ -187,12 +198,12 @@ struct ContactListSearchContainerTransition {
let query: String
}
private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, emptyResults: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) -> ContactListSearchContainerTransition {
private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, emptyResults: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, isPeerEnabled: @escaping (ContactListPeer) -> Bool, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) -> ContactListSearchContainerTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, addContact: addContact, openPeer: openPeer, openDisabledPeer: openDisabledPeer, contextAction: contextAction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, addContact: addContact, openPeer: openPeer, openDisabledPeer: openDisabledPeer, contextAction: contextAction), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, isPeerEnabled: isPeerEnabled, addContact: addContact, openPeer: openPeer, openDisabledPeer: openDisabledPeer, contextAction: contextAction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, isPeerEnabled: isPeerEnabled, addContact: addContact, openPeer: openPeer, openDisabledPeer: openDisabledPeer, contextAction: contextAction), directionHint: nil) }
return ContactListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, emptyResults: emptyResults, query: query)
}
@ -211,9 +222,16 @@ public struct ContactsSearchCategories: OptionSet {
}
public final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
public enum OpenPeerAction {
case generic
case voiceCall
case videoCall
}
private let context: AccountContext
private let isPeerEnabled: (ContactListPeer) -> Bool
private let addContact: ((String) -> Void)?
private let openPeer: (ContactListPeer) -> Void
private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void
private let openDisabledPeer: (EnginePeer, ChatListDisabledPeerReason) -> Void
private let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
@ -238,8 +256,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
return true
}
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], displayCallIcons: Bool = false, isPeerEnabled: @escaping (ContactListPeer) -> Bool = { _ in true }, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) {
self.context = context
self.isPeerEnabled = isPeerEnabled
self.addContact = addContact
self.openPeer = openPeer
self.openDisabledPeer = openDisabledPeer
@ -441,7 +460,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
enabled = false
}
}
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), localPeersAndPresences.1[peer.id], .contacts, enabled, requiresPremiumForMessaging))
entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons))
if searchDeviceContacts, case let .user(user) = peer, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
}
@ -483,7 +502,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
}
}
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), nil, .global, enabled, requiresPremiumForMessaging))
entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
}
@ -518,7 +537,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
}
}
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), nil, .global, enabled, requiresPremiumForMessaging))
entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
}
@ -539,13 +558,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
continue outer
}
}
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .deviceContact(stableId, contact.0), nil, .deviceContacts, true, false))
entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .deviceContact(stableId, contact.0), presence: nil, group: .deviceContacts, enabled: true, requiresPremiumForMessaging: false, displayCallIcons: displayCallIcons))
index += 1
}
}
if let _ = addContact, isViablePhoneNumber(query) {
entries.append(.addContact(themeAndStrings.0, themeAndStrings.1, query))
entries.append(.addContact(theme: themeAndStrings.0, strings: themeAndStrings.1, phoneNumber: query))
}
return (entries, query)
@ -571,9 +590,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
}
}
let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, emptyResults: items?.isEmpty ?? false, query: query, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, timeFormat: strongSelf.presentationData.dateTimeFormat, addContact: addContact, openPeer: { peer in
let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, emptyResults: items?.isEmpty ?? false, query: query, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, timeFormat: strongSelf.presentationData.dateTimeFormat, isPeerEnabled: strongSelf.isPeerEnabled, addContact: addContact, openPeer: { peer, action in
self?.listNode.clearHighlightAnimated(true)
self?.openPeer(peer)
self?.openPeer(peer, action)
}, openDisabledPeer: { peer, reason in
guard let self else {
return

View File

@ -508,7 +508,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
return
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.deviceContacts], addContact: nil, openPeer: { [weak self] peer in
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.deviceContacts], addContact: nil, openPeer: { [weak self] peer, _ in
if let strongSelf = self, case let .deviceContact(id, _) = peer {
strongSelf.selectionState = strongSelf.selectionState.withSelectedContactId(id)
strongSelf.requestDeactivateSearch?()

View File

@ -438,9 +438,8 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
icon = nil
isUserInteractionEnabled = action != nil
case let .starsReactions(topCount):
//TODO:localize
self.action = nil
self.text = "Send \(topCount) or more to highlight your profile"
self.text = self.presentationData.strings.Chat_SendStarsToBecomeTopInfo("\(topCount)").string
self.targetSelectionIndex = nil
icon = nil
isUserInteractionEnabled = action != nil

View File

@ -492,10 +492,9 @@ public final class InviteLinkInviteController: ViewController {
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
//TODO:localize
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: "Revoke Link"),
ActionSheetTextItem(title: presentationData.strings.GroupCall_RevokeLinkText),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { [weak self] in
dismissAction()
@ -674,9 +673,8 @@ public final class InviteLinkInviteController: ViewController {
}
var entries: [InviteLinkInviteEntry] = []
//TODO:localize
let helpText: String = "Anyone on Telegram can join your call by following the link below."
entries.append(.header(title: "Call Link", text: helpText))
let helpText: String = presentationData.strings.InviteLink_GroupCallLinkHelp
entries.append(.header(title: presentationData.strings.InviteLink_CallLinkTitle, text: helpText))
let mainInvite: ExportedInvitation = .link(link: mainInvite?.link ?? "", title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil)

View File

@ -369,8 +369,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
return (TelegramTextAttributes.URL, contents)
}
)
//TODO:localize
let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString("Be the first to join the call and add people from there. [Open Call >](open_call)", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString(item.presentationData.strings.InviteLink_CreatedGroupCallFooter, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = justCreatedCallTextAttributedString.string.range(of: ">"), let chevronImage {
justCreatedCallTextAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: justCreatedCallTextAttributedString.string))
}

View File

@ -194,8 +194,6 @@ public func JoinLinkPreviewController(
if let data = context.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_legacy_join_link"] != nil {
return LegacyJoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState)
} else if case let .invite(invite) = resolvedState, !invite.flags.requestNeeded, !invite.flags.isBroadcast, !invite.flags.canRefulfillSubscription {
//TODO:release
var verificationStatus: JoinSubjectScreenMode.Group.VerificationStatus?
if invite.flags.isFake {
verificationStatus = .fake

View File

@ -253,8 +253,7 @@ public final class QrCodeScreen: ViewController {
case .channel:
text = self.presentationData.strings.InviteLink_QRCode_InfoChannel
case .groupCall:
//TODO:localize
text = "Everyone on Telegram can scan this code to join your group call."
text = self.presentationData.strings.InviteLink_QRCode_InfoGroupCall
}
case .chatFolder:
title = self.presentationData.strings.InviteLink_QRCodeFolder_Title

View File

@ -1047,11 +1047,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
if let settingInfoText = settingInfoText {
entries.append(.settingInfo(presentationData.theme, settingInfoText, settingInfoLink))
}
if case .phoneNumber = kind {
entries.append(.phoneDiscoveryHeader(presentationData.theme, presentationData.strings.PrivacyPhoneNumberSettings_DiscoveryHeader))
entries.append(.phoneDiscoveryEverybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.phoneDiscoveryEnabled != false))
entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false))
entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink))
}
if case .voiceMessages = kind, !isPremium {

View File

@ -498,14 +498,12 @@ public final class CallController: ViewController {
}
static func openConferenceAddParticipant(context: AccountContext, disablePeerIds: [EnginePeer.Id], shareLink: (() -> Void)?, completion: @escaping ([(id: EnginePeer.Id, isVideo: Bool)]) -> Void) -> ViewController {
//TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
var options: [ContactListAdditionalOption] = []
var openShareLinkImpl: (() -> Void)?
if shareLink != nil {
//TODO:localize
options.append(ContactListAdditionalOption(title: "Share Call Link", icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
options.append(ContactListAdditionalOption(title: presentationData.strings.Call_ShareLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
openShareLinkImpl?()
}, clearHighlightAutomatically: false))
}
@ -515,11 +513,11 @@ public final class CallController: ViewController {
updatedPresentationData: (initial: presentationData, signal: .single(presentationData)),
mode: .generic,
title: { strings in
//TODO:localize
return "Add Member"
return strings.Call_AddMemberTitle
},
options: .single(options),
displayCallIcons: true,
multipleSelection: .disabled,
confirmation: { peer in
switch peer {
case let .peer(peer, _, _):

View File

@ -765,8 +765,8 @@ public final class PresentationCallImpl: PresentationCall {
self.localVideoEndpointId = nil
self.remoteVideoEndpointId = nil
//TODO:localize
self.callKitIntegration?.updateCallIsConference(uuid: self.internalId, title: self.conferenceTitle ?? "Group Call")
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.callKitIntegration?.updateCallIsConference(uuid: self.internalId, title: self.conferenceTitle ?? presentationData.strings.Call_GenericGroupCallTitle)
}
func internal_markAsCanBeRemoved() {

View File

@ -210,6 +210,11 @@ private final class PendingConferenceInvitationContext {
case ringing
}
enum InvitationError {
case generic
case privacy(peer: EnginePeer?)
}
private let engine: TelegramEngine
private var requestDisposable: Disposable?
private var stateDisposable: Disposable?
@ -218,19 +223,12 @@ private final class PendingConferenceInvitationContext {
private var hadMessage: Bool = false
private var didNotifyEnded: Bool = false
init(engine: TelegramEngine, reference: InternalGroupCallReference, peerId: PeerId, isVideo: Bool, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void) {
init(engine: TelegramEngine, reference: InternalGroupCallReference, peerId: PeerId, isVideo: Bool, onStateUpdated: @escaping (State) -> Void, onEnded: @escaping (Bool) -> Void, onError: @escaping (InvitationError) -> Void) {
self.engine = engine
self.requestDisposable = (engine.calls.inviteConferenceCallParticipant(reference: reference, peerId: peerId, isVideo: isVideo).startStrict(next: { [weak self] messageId in
self.requestDisposable = ((engine.calls.inviteConferenceCallParticipant(reference: reference, peerId: peerId, isVideo: isVideo) |> deliverOnMainQueue).startStrict(next: { [weak self] messageId in
guard let self else {
return
}
guard let messageId else {
if !self.didNotifyEnded {
self.didNotifyEnded = true
onEnded(false)
}
return
}
self.messageId = messageId
onStateUpdated(.ringing)
@ -296,6 +294,24 @@ private final class PendingConferenceInvitationContext {
}
}
})
}, error: { [weak self] error in
guard let self else {
return
}
if !self.didNotifyEnded {
self.didNotifyEnded = true
onEnded(false)
}
let mappedError: InvitationError
switch error {
case .privacy(let peer):
mappedError = .privacy(peer: peer)
default:
mappedError = .generic
}
onError(mappedError)
}))
}
@ -792,6 +808,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private let e2eContext: ConferenceCallE2EContext?
private var lastErrorAlertTimestamp: Double = 0.0
init(
accountContext: AccountContext,
audioSession: ManagedAudioSession,
@ -842,6 +860,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.beginWithVideo = beginWithVideo
self.keyPair = keyPair
if self.isConference && conferenceSourceId == nil {
self.isMutedValue = .unmuted
self.isMutedPromise.set(self.isMutedValue)
self.stateValue.muteState = nil
}
if let keyPair, let initialCall {
self.e2eContext = ConferenceCallE2EContext(
engine: accountContext.engine,
@ -1742,6 +1766,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.onMutedSpeechActivityDetected?(value)
}
}, isConference: self.isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: self.isStream, sharedAudioDevice: self.sharedAudioContext?.audioDevice, encryptionContext: encryptionContext))
let isEffectivelyMuted: Bool
switch self.isMutedValue {
case let .muted(isPushToTalkActive):
isEffectivelyMuted = !isPushToTalkActive
case .unmuted:
isEffectivelyMuted = false
}
genericCallContext.setIsMuted(isEffectivelyMuted)
}
self.genericCallContext = genericCallContext
@ -1890,13 +1923,21 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
reference = .id(id: callInfo.id, accessHash: callInfo.accessHash)
}
let isEffectivelyMuted: Bool
switch self.isMutedValue {
case let .muted(isPushToTalkActive):
isEffectivelyMuted = !isPushToTalkActive
case .unmuted:
isEffectivelyMuted = false
}
self.currentLocalSsrc = ssrc
self.requestDisposable.set((self.accountContext.engine.calls.joinGroupCall(
peerId: self.peerId,
joinAs: self.joinAsPeerId,
callId: callInfo.id,
reference: reference,
preferMuted: true,
preferMuted: isEffectivelyMuted,
joinPayload: joinPayload,
peerAdminIds: peerAdminIds,
inviteHash: self.invite,
@ -1962,7 +2003,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: self.audioSessionControl)
self.e2eContext?.begin()
if let e2eState = joinCallResult.e2eState {
self.e2eContext?.begin(initialState: e2eState)
} else {
self.e2eContext?.begin(initialState: nil)
}
}, error: { [weak self] error in
guard let self else {
return
@ -3536,6 +3581,32 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
onEnded: { success in
didEndAlready = true
onEnded?(success)
},
onError: { [weak self] error in
guard let self else {
return
}
let timestamp = CACurrentMediaTime()
if self.lastErrorAlertTimestamp > timestamp - 1.0 {
return
}
self.lastErrorAlertTimestamp = timestamp
let presentationData = self.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
var errorText = presentationData.strings.Login_UnknownError
switch error {
case let .privacy(peer):
if let peer {
errorText = presentationData.strings.Call_PrivacyErrorMessage(peer.compactDisplayTitle).string
}
default:
break
}
self.accountContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
]), on: .root, blockInteraction: false, completion: {})
}
)
if !didEndAlready {

View File

@ -65,9 +65,9 @@ final class VideoChatActionButtonComponent: Component {
}
}
enum MicrophoneState {
enum MicrophoneState: Equatable {
case connecting
case muted
case muted(forced: Bool)
case unmuted
case raiseHand
case scheduled

View File

@ -550,7 +550,7 @@ final class VideoChatEncryptionKeyComponent: Component {
self.isUpdating = false
}
#if DEBUG && true
#if DEBUG && false
if self.component == nil {
self.mockStateTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 4.0, repeats: true, block: { [weak self] _ in
guard let self else {
@ -614,11 +614,10 @@ final class VideoChatEncryptionKeyComponent: Component {
)
}
//TODO:localize
let collapsedTextSize = self.collapsedText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "End-to-end encrypted", font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor))
text: .plain(NSAttributedString(string: component.strings.VideoChat_EncryptionKeyLabel, font: Font.semibold(12.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 1000.0, height: 1000.0)
@ -627,7 +626,7 @@ final class VideoChatEncryptionKeyComponent: Component {
let expandedTextSize = self.expandedText.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: "These four emojis represent the call's encryption key. They must match for all participants and change when someone joins or leaves.", font: Font.regular(12.0), textColor: component.theme.list.itemPrimaryTextColor)),
text: .plain(NSAttributedString(string: component.strings.VideoChat_EncryptionKeyText, font: Font.regular(12.0), textColor: component.theme.list.itemPrimaryTextColor)),
maximumNumberOfLines: 0,
lineSpacing: 0.3
)),
@ -638,7 +637,7 @@ final class VideoChatEncryptionKeyComponent: Component {
let expandedButtonTextSize = self.expandedButtonText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Close", font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor))
text: .plain(NSAttributedString(string: component.strings.VideoChat_EncryptionKeyDone, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - expandedSideInset * 2.0, height: 1000.0)

View File

@ -182,7 +182,7 @@ final class VideoChatMicButtonComponent: Component {
enum Content: Equatable {
case connecting
case muted
case muted(forced: Bool)
case unmuted(pushToTalk: Bool)
case raiseHand(isRaised: Bool)
case scheduled(state: ScheduledState)
@ -263,11 +263,15 @@ final class VideoChatMicButtonComponent: Component {
switch component.content {
case .connecting, .unmuted, .raiseHand, .scheduled:
self.beginTrackingWasPushToTalk = false
case .muted:
case let .muted(forced):
if forced {
self.beginTrackingWasPushToTalk = false
} else {
self.beginTrackingWasPushToTalk = true
component.updateUnmutedStateIsPushToTalk(true)
}
}
}
return super.beginTracking(touch, with: event)
}
@ -291,8 +295,11 @@ final class VideoChatMicButtonComponent: Component {
switch component.content {
case .connecting:
break
case .muted:
case let .muted(forced):
if forced {
} else {
component.updateUnmutedStateIsPushToTalk(false)
}
case .unmuted:
if self.beginTrackingWasPushToTalk {
if timestamp < self.beginTrackingTimestamp + 0.15 {
@ -340,8 +347,12 @@ final class VideoChatMicButtonComponent: Component {
case .connecting:
titleText = component.strings.VoiceChat_Connecting
isEnabled = false
case .muted:
case let .muted(forced):
if forced {
titleText = component.strings.VoiceChat_MutedByAdmin
} else {
titleText = component.strings.VoiceChat_Unmute
}
case let .unmuted(isPushToTalk):
titleText = isPushToTalk ? component.strings.VoiceChat_Live : component.strings.VoiceChat_Mute
case let .raiseHand(isRaised):
@ -433,7 +444,9 @@ final class VideoChatMicButtonComponent: Component {
context.fill(CGRect(origin: CGPoint(), size: size))
case .muted, .unmuted, .raiseHand, .scheduled:
let colors: [UIColor]
if case .muted = component.content {
if case .muted(forced: true) = component.content {
colors = [UIColor(rgb: 0x3252EF), UIColor(rgb: 0xC64688)]
} else if case .muted(forced: false) = component.content {
colors = [UIColor(rgb: 0x0080FF), UIColor(rgb: 0x00A1FE)]
} else if case .raiseHand = component.content {
colors = [UIColor(rgb: 0x3252EF), UIColor(rgb: 0xC64688)]
@ -606,7 +619,9 @@ final class VideoChatMicButtonComponent: Component {
transition.setScale(view: blobView, scale: availableSize.width / 116.0)
let blobsColor: UIColor
if case .muted = component.content {
if case .muted(forced: true) = component.content {
blobsColor = UIColor(rgb: 0x914BAD)
} else if case .muted(forced: false) = component.content {
blobsColor = UIColor(rgb: 0x0086FF)
} else if case .raiseHand = component.content {
blobsColor = UIColor(rgb: 0x914BAD)
@ -657,7 +672,9 @@ final class VideoChatMicButtonComponent: Component {
}
let glowColor: UIColor
if case .muted = component.content {
if case .muted(forced: true) = component.content {
glowColor = UIColor(rgb: 0x3252EF)
} else if case .muted(forced: false) = component.content {
glowColor = UIColor(rgb: 0x0086FF)
} else if case .raiseHand = component.content {
glowColor = UIColor(rgb: 0x3252EF)

View File

@ -1282,18 +1282,7 @@ final class VideoChatParticipantsComponent: Component {
let invitedPeer = component.invitedPeers[i - self.listParticipants.count]
participantPeerId = invitedPeer.peer.id
let subtitle: PeerListItemComponent.Subtitle
//TODO:localize
switch invitedPeer.state {
case .none:
subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral)
case .connecting:
subtitle = PeerListItemComponent.Subtitle(text: "connecting...", color: .neutral)
case .requesting:
subtitle = PeerListItemComponent.Subtitle(text: "requesting...", color: .neutral)
case .ringing:
subtitle = PeerListItemComponent.Subtitle(text: "invited", color: .neutral)
}
let subtitle: PeerListItemComponent.Subtitle = PeerListItemComponent.Subtitle(text: component.strings.VoiceChat_StatusInvited, color: .neutral)
let rightAccessoryComponent: AnyComponent<Empty> = AnyComponent(VideoChatParticipantInvitedStatusComponent(
theme: component.theme
@ -1861,11 +1850,10 @@ final class VideoChatParticipantsComponent: Component {
let iconType: VideoChatListInviteComponent.Icon
switch inviteOption.type {
case let .invite(isMultiple):
//TODO:localize
if isMultiple {
inviteText = component.strings.VoiceChat_InviteMember
} else {
inviteText = "Add Member"
inviteText = component.strings.VideoChat_InviteMember
}
iconType = .addUser
case .shareLink:

View File

@ -685,9 +685,8 @@ final class VideoChatScreenComponent: Component {
if let result {
switch result {
case .linkCopied:
//TODO:localize
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: presentationData.strings.CallList_ToastCallLinkCopied_Text, customUndoText: presentationData.strings.CallList_ToastCallLinkCopied_Action, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
return false
}), in: .current)
case .openCall:
@ -2038,11 +2037,10 @@ final class VideoChatScreenComponent: Component {
maxTitleWidth -= 110.0
}
//TODO:localize
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(VideoChatTitleComponent(
title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? "Group Call",
title: self.callState?.title ?? self.peer?.debugDisplayTitle ?? environment.strings.VideoChat_GroupCallTitle,
status: idleTitleStatusText,
isRecording: self.callState?.recordingStartTimestamp != nil,
strings: environment.strings,
@ -2555,9 +2553,12 @@ final class VideoChatScreenComponent: Component {
micButtonContent = .unmuted(pushToTalk: self.isPushToTalkActive)
actionButtonMicrophoneState = .unmuted
} else {
micButtonContent = .muted
actionButtonMicrophoneState = .muted
micButtonContent = .muted(forced: false)
actionButtonMicrophoneState = .muted(forced: false)
}
} else if isConference {
micButtonContent = .muted(forced: true)
actionButtonMicrophoneState = .muted(forced: true)
} else {
micButtonContent = .raiseHand(isRaised: callState.raisedHand)
actionButtonMicrophoneState = .raiseHand
@ -2708,6 +2709,18 @@ final class VideoChatScreenComponent: Component {
} else if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
displayVideoControlButton = false
}
if case .audio = videoControlButtonContent {
if let (availableOutputs, _) = self.audioOutputState {
if availableOutputs.count <= 0 {
displayVideoControlButton = false
}
} else {
displayVideoControlButton = false
}
}
if videoControlButtonContent == videoButtonContent {
displayVideoControlButton = false
}
let videoControlButtonSize = self.videoControlButton.update(
transition: transition,

View File

@ -129,7 +129,6 @@ extension VideoChatScreenComponent.View {
if let participant {
dismissController?()
//TODO:release
if groupCall.invitePeer(participant.peer.id, isVideo: false) {
let text: String
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
@ -242,7 +241,6 @@ extension VideoChatScreenComponent.View {
}
dismissController?()
//TODO:release
if groupCall.invitePeer(peer.id, isVideo: false) {
let text: String
if case let .channel(channel) = self.peer, case .broadcast = channel.info {
@ -315,7 +313,6 @@ extension VideoChatScreenComponent.View {
}
dismissController?()
//TODO:release
if groupCall.invitePeer(peer.id, isVideo: false) {
let text: String
if case let .channel(channel) = self.peer, case .broadcast = channel.info {

View File

@ -1256,7 +1256,6 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
if let participant = participant {
dismissController?()
//TODO:release
if strongSelf.call.invitePeer(participant.peer.id, isVideo: false) {
let text: String
if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info {
@ -1365,7 +1364,6 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
}
dismissController?()
//TODO:release
if strongSelf.call.invitePeer(peer.id, isVideo: false) {
let text: String
if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info {
@ -1434,7 +1432,6 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController {
}
dismissController?()
//TODO:release
if strongSelf.call.invitePeer(peer.id, isVideo: false) {
let text: String
if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info {

View File

@ -79,7 +79,7 @@ public final class ConferenceCallE2EContext {
self.synchronizeRemovedParticipantsTimer?.invalidate()
}
func begin() {
func begin(initialState: JoinGroupCallResult.E2EState?) {
self.scheduledSynchronizeRemovedParticipantsAfterPoll = true
self.synchronizeRemovedParticipantsTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true, block: { [weak self] _ in
guard let self else {
@ -88,6 +88,13 @@ public final class ConferenceCallE2EContext {
self.synchronizeRemovedParticipants()
})
if let initialState {
self.e2ePoll0Offset = initialState.subChain0.offset
self.e2ePoll1Offset = initialState.subChain1.offset
self.addE2EBlocks(blocks: initialState.subChain0.blocks, subChainId: 0)
self.addE2EBlocks(blocks: initialState.subChain1.blocks, subChainId: 1)
}
self.e2ePoll(subChainId: 0)
self.e2ePoll(subChainId: 1)
}
@ -182,7 +189,6 @@ public final class ConferenceCallE2EContext {
self.e2eEncryptionKeyHashValue.set(outEmoji.isEmpty ? nil : outEmoji)
for outBlock in outBlocks {
//TODO:release queue
let _ = self.engine.calls.sendConferenceCallBroadcast(callId: self.callId, accessHash: self.accessHash, block: outBlock).startStandalone()
}
}
@ -400,7 +406,6 @@ public final class ConferenceCallE2EContext {
}
func kickPeer(id: EnginePeer.Id) {
//TODO:release
if !self.pendingKickPeers.contains(id) {
self.pendingKickPeers.append(id)
@ -426,9 +431,9 @@ public final class ConferenceCallE2EContext {
})
}
public func begin() {
public func begin(initialState: JoinGroupCallResult.E2EState?) {
self.impl.with { impl in
impl.begin()
impl.begin(initialState: initialState)
}
}

View File

@ -588,10 +588,21 @@ public struct JoinGroupCallResult {
case broadcast(isExternalStream: Bool)
}
public struct E2EState {
public let subChain0: (offset: Int, blocks: [Data])
public let subChain1: (offset: Int, blocks: [Data])
public init(subChain0: (offset: Int, blocks: [Data]), subChain1: (offset: Int, blocks: [Data])) {
self.subChain0 = subChain0
self.subChain1 = subChain1
}
}
public var callInfo: GroupCallInfo
public var state: GroupCallParticipantsContext.State
public var connectionMode: ConnectionMode
public var jsonParams: String
public var e2eState: E2EState?
}
public class JoinGroupCallE2E {
@ -791,7 +802,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
connectionMode = .broadcast(isExternalStream: false)
}
return account.postbox.transaction { transaction -> JoinGroupCallResult in
return account.postbox.transaction { transaction -> Signal<JoinGroupCallResult, InternalJoinError> in
if let peerId {
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData {
@ -806,6 +817,9 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
var state = state
var e2eSubChain1State: (offset: Int, blocks: [Data])?
var e2eSubChain0State: (offset: Int, blocks: [Data])?
for update in updates.allUpdates {
switch update {
case let .updateGroupCallParticipants(_, participants, _):
@ -852,21 +866,41 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
}
}
}
case let .updateGroupCallChainBlocks(_, subChainId, blocks, nextOffset):
if subChainId == 0 {
e2eSubChain0State = (offset: Int(nextOffset), blocks: blocks.map { $0.makeData() })
} else {
e2eSubChain1State = (offset: Int(nextOffset), blocks: blocks.map { $0.makeData() })
}
default:
break
}
}
var e2eState: JoinGroupCallResult.E2EState?
if let e2eSubChain0State, let e2eSubChain1State {
e2eState = JoinGroupCallResult.E2EState(
subChain0: e2eSubChain0State,
subChain1: e2eSubChain1State
)
}
if generateE2E != nil && e2eState == nil {
return .fail(.error(.generic))
}
state.participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
return JoinGroupCallResult(
return .single(JoinGroupCallResult(
callInfo: parsedCall,
state: state,
connectionMode: connectionMode,
jsonParams: parsedClientParams
)
jsonParams: parsedClientParams,
e2eState: e2eState
))
}
|> castError(InternalJoinError.self)
|> switchToLatest
}
}
}
@ -882,13 +916,19 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
}
}
func _internal_inviteConferenceCallParticipant(account: Account, reference: InternalGroupCallReference, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<MessageId?, NoError> {
public enum InviteConferenceCallParticipantError {
case generic
case privacy(peer: EnginePeer?)
}
func _internal_inviteConferenceCallParticipant(account: Account, reference: InternalGroupCallReference, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<MessageId, InviteConferenceCallParticipantError> {
return account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
}
|> mapToSignal { inputPeer -> Signal<MessageId?, NoError> in
|> castError(InviteConferenceCallParticipantError.self)
|> mapToSignal { inputPeer -> Signal<MessageId, InviteConferenceCallParticipantError> in
guard let inputPeer else {
return .complete()
return .fail(.generic)
}
var flags: Int32 = 0
@ -897,17 +937,26 @@ func _internal_inviteConferenceCallParticipant(account: Account, reference: Inte
}
return account.network.request(Api.functions.phone.inviteConferenceCallParticipant(flags: flags, call: reference.apiInputGroupCall, userId: inputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
|> `catch` { error -> Signal<Api.Updates?, InviteConferenceCallParticipantError> in
if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return account.postbox.transaction { transaction -> InviteConferenceCallParticipantError in
return .privacy(peer: transaction.getPeer(peerId).flatMap(EnginePeer.init))
}
|> mapToSignal { result -> Signal<MessageId?, NoError> in
|> castError(InviteConferenceCallParticipantError.self)
|> mapToSignal { error -> Signal<Api.Updates?, InviteConferenceCallParticipantError> in
return .fail(error)
}
}
return .fail(.generic)
}
|> mapToSignal { result -> Signal<MessageId, InviteConferenceCallParticipantError> in
if let result {
account.stateManager.addUpdates(result)
if let message = result.messageIds.first {
return .single(message)
}
}
return .single(nil)
return .fail(.generic)
}
}
}

View File

@ -109,7 +109,7 @@ public extension TelegramEngine {
return _internal_sendConferenceCallBroadcast(account: self.account, callId: callId, accessHash: accessHash, block: block)
}
public func inviteConferenceCallParticipant(reference: InternalGroupCallReference, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<EngineMessage.Id?, NoError> {
public func inviteConferenceCallParticipant(reference: InternalGroupCallReference, peerId: EnginePeer.Id, isVideo: Bool) -> Signal<EngineMessage.Id, InviteConferenceCallParticipantError> {
return _internal_inviteConferenceCallParticipant(account: self.account, reference: reference, peerId: peerId, isVideo: isVideo)
}

View File

@ -336,6 +336,8 @@ public enum PresentationResourceKey: Int32 {
case peerStatusLockedImage
case expandDownArrowImage
case expandSmallDownArrowImage
case callListCallIcon
}
public enum ChatExpiredStoryIndicatorType: Hashable {

View File

@ -199,4 +199,10 @@ public struct PresentationResourcesRootController {
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortIcon"), color: .white)
})
}
public static func callListCallIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.callListCallIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Call List/NewCallListIcon"), color: theme.rootController.navigationBar.accentTextColor)
})
}
}

View File

@ -616,9 +616,27 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case .conferenceCall:
//TODO:localize
let titleString = "Group call"
case let .conferenceCall(conferenceCall):
var titleString: String
let incoming = message.flags.contains(.Incoming)
let missedTimeout: Int32 = 30
let currentTime = Int32(Date().timeIntervalSince1970)
if conferenceCall.flags.contains(.isMissed) {
titleString = strings.Chat_CallMessage_DeclinedGroupCall
} else if message.timestamp < currentTime - missedTimeout {
titleString = strings.Chat_CallMessage_MissedGroupCall
} else if conferenceCall.duration != nil {
titleString = strings.Chat_CallMessage_CancelledGroupCall
} else {
if incoming {
titleString = strings.Chat_CallMessage_IncomingGroupCall
} else {
titleString = strings.Chat_CallMessage_OutgoingGroupCall
}
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case let .groupPhoneCall(_, _, scheduleDate, duration):
if let scheduleDate = scheduleDate {

View File

@ -153,8 +153,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
callDuration = conferenceCall.duration
if conferenceCall.otherParticipants.count > 0 {
//TODO:localize
peopleTextString = "\(conferenceCall.otherParticipants.count + 1) people"
peopleTextString = item.presentationData.strings.Chat_CallMessage_GroupCallParticipantCount(Int32(conferenceCall.otherParticipants.count + 1))
if let peer = item.message.author {
peopleAvatars.append(peer)
}
@ -165,9 +164,8 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
}
}
//TODO:localize
let missedTimeout: Int32
#if DEBUG
#if DEBUG && false
missedTimeout = 5
#else
missedTimeout = 30
@ -175,16 +173,16 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
let currentTime = Int32(Date().timeIntervalSince1970)
if conferenceCall.flags.contains(.isMissed) {
titleString = "Declined Group Call"
titleString = item.presentationData.strings.Chat_CallMessage_DeclinedGroupCall
} else if item.message.timestamp < currentTime - missedTimeout {
titleString = "Missed Group Call"
titleString = item.presentationData.strings.Chat_CallMessage_MissedGroupCall
} else if conferenceCall.duration != nil {
titleString = "Cancelled Group Call"
titleString = item.presentationData.strings.Chat_CallMessage_CancelledGroupCall
} else {
if incoming {
titleString = "Incoming Group Call"
titleString = item.presentationData.strings.Chat_CallMessage_IncomingGroupCall
} else {
titleString = "Outgoing Group Call"
titleString = item.presentationData.strings.Chat_CallMessage_OutgoingGroupCall
}
updateConferenceTimerEndTimeout = (item.message.timestamp + missedTimeout) - currentTime
}
@ -280,7 +278,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
var avatarsWidth: CGFloat = 0.0
if !peopleAvatars.isEmpty {
avatarsWidth += avatarsLeftInset
avatarsWidth += 1.0 * peopleAvatarSize + CGFloat(peopleAvatars.count - 1) * peopleAvatarSpacing
avatarsWidth += 1.0 * peopleAvatarSize + CGFloat(min(3, peopleAvatars.count) - 1) * peopleAvatarSpacing
avatarsWidth += avatarsRightInset
labelsWidth += avatarsWidth
}

View File

@ -475,8 +475,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
text = nil
entities = nil
case "telegram_call":
//TODO:localize
actionTitle = "JOIN GROUP CALL"
actionTitle = item.presentationData.strings.Chat_ViewGroupCall
default:
break
}

View File

@ -490,7 +490,7 @@ private final class JoinSubjectScreenComponent: Component {
contentHeight += 31.0
titleString = group.title
subtitleString = group.isPublic ? "public group" : "private group"
subtitleString = group.isPublic ? environment.strings.Invitation_PublicGroup : environment.strings.Invitation_PrivateGroup
descriptionTextString = group.about
previewPeers = group.members
@ -528,10 +528,9 @@ private final class JoinSubjectScreenComponent: Component {
}
contentHeight += peerAvatarSize.height + 21.0
case let .groupCall(groupCall):
//TODO:localize
titleString = "Group Call"
titleString = environment.strings.Invitation_GroupCall
subtitleString = nil
descriptionTextString = "You are invited to join a group call."
descriptionTextString = environment.strings.Invitation_GroupCall_Text
previewPeers = groupCall.members
totalMemberCount = groupCall.totalMemberCount
@ -691,12 +690,11 @@ private final class JoinSubjectScreenComponent: Component {
if !previewPeers.isEmpty {
contentHeight += 11.0
//TODO:localize
let previewPeersString: String
switch component.mode {
case .group:
if previewPeers.count == 1 {
previewPeersString = "**\(previewPeers[0].compactDisplayTitle)** already joined this group."
previewPeersString = environment.strings.Invitation_Group_AlreadyJoinedSingle(previewPeers[0].compactDisplayTitle).string
} else {
let firstPeers = previewPeers.prefix(upTo: 2)
let peersTextArray = firstPeers.map { "**\($0.compactDisplayTitle)**" }
@ -717,14 +715,14 @@ private final class JoinSubjectScreenComponent: Component {
}
}
if totalMemberCount > firstPeers.count {
previewPeersString = "\(peersText) and **\(totalMemberCount - firstPeers.count)** other people already joined this group."
previewPeersString = environment.strings.Invitation_Group_AlreadyJoinedMultipleWithCount(Int32(totalMemberCount - firstPeers.count)).replacingOccurrences(of: "{}", with: peersText)
} else {
previewPeersString = "\(peersText) already joined this group."
previewPeersString = environment.strings.Invitation_Group_AlreadyJoinedMultiple(peersText).string
}
}
case .groupCall:
if previewPeers.count == 1 {
previewPeersString = "**\(previewPeers[0].compactDisplayTitle)** already joined this call."
previewPeersString = environment.strings.Invitation_GroupCall_AlreadyJoinedSingle(previewPeers[0].compactDisplayTitle).string
} else {
let firstPeers = previewPeers.prefix(upTo: 2)
let peersTextArray = firstPeers.map { "**\($0.compactDisplayTitle)**" }
@ -745,9 +743,9 @@ private final class JoinSubjectScreenComponent: Component {
}
}
if totalMemberCount > firstPeers.count {
previewPeersString = "\(peersText) and **\(totalMemberCount - firstPeers.count)** other people already joined this call."
previewPeersString = environment.strings.Invitation_GroupCall_AlreadyJoinedMultipleWithCount(Int32(totalMemberCount - firstPeers.count)).replacingOccurrences(of: "{}", with: peersText)
} else {
previewPeersString = "\(peersText) already joined this call."
previewPeersString = environment.strings.Invitation_GroupCall_AlreadyJoinedMultiple(peersText).string
}
}
}
@ -854,8 +852,7 @@ private final class JoinSubjectScreenComponent: Component {
case .group:
actionButtonTitle = environment.strings.Invitation_JoinGroup
case .groupCall:
//TODO:localize
actionButtonTitle = "Join Group Call"
actionButtonTitle = environment.strings.Invitation_JoinGroupCall
}
let actionButtonSize = self.actionButton.update(
transition: transition,

View File

@ -11118,7 +11118,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
giftsContext?.updateSorting(sorting == .date ? .value : .date)
})))
if hasPinnedGifts && hasVisibility {
if hasPinnedGifts {
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Reorder, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
}, action: { _, f in

View File

@ -1330,7 +1330,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
if self.hasGlobalSearch {
categories.insert(.global)
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, updatedPresentationData: self.updatedPresentationData, onlyWriteable: true, categories: categories, addContact: nil, openPeer: { [weak self] peer in
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, updatedPresentationData: self.updatedPresentationData, onlyWriteable: true, categories: categories, addContact: nil, openPeer: { [weak self] peer, _ in
if let strongSelf = self {
var updated = false
var count = 0

View File

@ -1722,7 +1722,7 @@ final class StoryItemSetContainerSendMessage {
case .contact:
let theme = component.theme
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) })
let contactsController = component.context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: component.context, updatedPresentationData: updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true))
let contactsController = component.context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: component.context, updatedPresentationData: updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: .always))
contactsController.presentScheduleTimePicker = { [weak self, weak view] completion in
guard let self, let view else {
return

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "newcall_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1977,6 +1977,22 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
self.maybeCheckForUpdates()
SharedDisplayLinkDriver.shared.updateForegroundState(self.isActiveValue)
func cancelWindowPanGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if let recognizer = recognizer as? WindowPanRecognizer {
recognizer.cancel()
}
}
}
for subview in view.subviews {
cancelWindowPanGestures(view: subview)
}
}
//cancelWindowPanGestures(view: self.mainWindow.hostView.containerView)
}
func applicationWillTerminate(_ application: UIApplication) {
@ -2164,14 +2180,18 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let internalId = CallSessionManager.getStableIncomingUUID(peerId: fromPeerId.id._internalGetInt64Value(), messageId: messageId.id)
//TODO:localize
var displayTitle = "\(fromTitle)"
var strings: PresentationStrings = defaultPresentationStrings
let _ = (self.sharedContextPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
strings = sharedApplicationContext.sharedContext.currentPresentationData.with { $0.strings }
})
let displayTitle: String
if let memberCountString = payloadJson["member_count"] as? String, let memberCount = Int(memberCountString) {
if memberCount == 1 {
displayTitle.append(" and 1 other")
displayTitle = strings.Call_IncomingGroupCallTitle_Multiple(Int32(memberCount)).replacingOccurrences(of: "{}", with: fromTitle)
} else {
displayTitle.append(" and \(memberCount) others")
}
displayTitle = strings.Call_IncomingGroupCallTitle_Single(fromTitle).string
}
callKitIntegration.reportIncomingCall(

View File

@ -2898,7 +2898,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
if let currentGroupCallController = self.context.sharedContext as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == conferenceCall.callId {
if let currentGroupCallController = self.context.sharedContext.currentGroupCallController as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == conferenceCall.callId {
self.context.sharedContext.navigateToCurrentCall()
return
}
@ -2932,8 +2932,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
default:
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
self.present(textAlertController(context: self.context, title: nil, text: "An error occurred", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
self.present(textAlertController(context: self.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
})
}, longTap: { [weak self] action, params in
@ -7513,17 +7512,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
navigationController.pushViewController(self, animated: false)
let updatedLayout = self.validLayout
let updatedFrame = self.view.frame
if let initialLayout, let updatedLayout, transition.isAnimated {
let initialView = self.view.superview
let updatedFrame = self.view.convert(self.view.bounds, to: navigationController.view)
navigationController.view.addSubview(self.view)
self.view.clipsToBounds = true
self.view.frame = initialFrame
self.containerLayoutUpdated(initialLayout, transition: .immediate)
self.containerLayoutUpdated(updatedLayout, transition: transition)
self.chatDisplayNode.historyNode.layer.animateScaleX(from: initialLayout.size.width / updatedLayout.size.width, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
self.chatDisplayNode.historyNode.layer.animatePosition(from: CGPoint(x: (updatedLayout.size.width - initialLayout.size.width) / 2.0, y: 0.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.chatDisplayNode.inputPanelBackgroundNode.layer.removeAllAnimations()
self.chatDisplayNode.inputPanelBackgroundNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.chatDisplayNode.inputPanelNode?.frame.height ?? 45.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.view.layer.animate(from: 14.0, to: updatedLayout.deviceMetrics.screenCornerRadius, keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4)
transition.updateFrame(view: self.view, frame: updatedFrame, completion: { _ in

View File

@ -190,7 +190,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let inputPanelContainerNode: ChatInputPanelContainer
private let inputPanelOverlayNode: SparseNode
private let inputPanelClippingNode: SparseNode
private let inputPanelBackgroundNode: NavigationBackgroundNode
let inputPanelBackgroundNode: NavigationBackgroundNode
private var navigationBarBackgroundContent: WallpaperBubbleBackgroundNode?
private var inputPanelBackgroundContent: WallpaperBubbleBackgroundNode?
@ -1880,9 +1880,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
displayMode = .aspectFit
} else if case .compact = layout.metrics.widthClass {
if layout.size.width < layout.size.height && layout.size.height < layout.deviceMetrics.screenSize.height {
wallpaperBounds.size.height = layout.deviceMetrics.screenSize.height
wallpaperBounds.size = layout.deviceMetrics.screenSize
} else if layout.size.width > layout.size.height && layout.size.height < layout.deviceMetrics.screenSize.width {
wallpaperBounds.size.height = layout.deviceMetrics.screenSize.width
wallpaperBounds.size = layout.deviceMetrics.screenSize
}
}
self.backgroundNode.updateLayout(size: wallpaperBounds.size, displayMode: displayMode, transition: transition)
@ -2296,14 +2296,22 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.navigateButtons.update(rect: apparentNavigateButtonsFrame, within: layout.size, transition: transition)
if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let titleAccessoryPanelFrame, !titleAccessoryPanelNode.frame.equalTo(titleAccessoryPanelFrame) {
let previousFrame = titleAccessoryPanelNode.frame
titleAccessoryPanelNode.frame = titleAccessoryPanelFrame
if transition.isAnimated && previousFrame.width != titleAccessoryPanelFrame.width {
} else {
transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: -titleAccessoryPanelFrame.height))
}
}
if let chatTranslationPanel = self.chatTranslationPanel, let translationPanelFrame, !chatTranslationPanel.frame.equalTo(translationPanelFrame) {
let previousFrame = chatTranslationPanel.frame
chatTranslationPanel.frame = translationPanelFrame
if transition.isAnimated && previousFrame.width != translationPanelFrame.width {
} else {
transition.animatePositionAdditive(node: chatTranslationPanel, offset: CGPoint(x: 0.0, y: -translationPanelFrame.height))
}
}
if let chatImportStatusPanel = self.chatImportStatusPanel, let importStatusPanelFrame, !chatImportStatusPanel.frame.equalTo(importStatusPanelFrame) {
chatImportStatusPanel.frame = importStatusPanelFrame

View File

@ -424,7 +424,7 @@ extension ChatControllerImpl {
let _ = currentLocationController.swap(controller)
})
case .contact:
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true, requirePhoneNumbers: true))
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: .always, requirePhoneNumbers: true))
contactsController.presentScheduleTimePicker = { [weak self] completion in
if let strongSelf = self {
strongSelf.presentScheduleTimePicker(completion: completion)
@ -1620,7 +1620,7 @@ extension ChatControllerImpl {
}
func presentContactPicker() {
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true))
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: .always))
contactsController.navigationPresentation = .modal
self.chatDisplayNode.dismissInput()
self.effectiveNavigationController?.pushViewController(contactsController)

View File

@ -120,7 +120,7 @@ final class ComposeControllerNode: ASDisplayNode {
return
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global], addContact: nil, openPeer: { [weak self] peer in
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global], addContact: nil, openPeer: { [weak self] peer, _ in
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch, case let .peer(peer, _, _) = peer {
requestOpenPeerFromSearch(peer.id)
}

View File

@ -265,6 +265,10 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
if isCall && count == 0 {
self.titleView.title = CounterControllerTitle(title: self.params.title ?? self.presentationData.strings.Compose_NewGroupTitle, counter: nil)
} else {
var count = count
if isCall {
count += 1
}
self.titleView.title = CounterControllerTitle(title: self.params.title ?? self.presentationData.strings.Compose_NewGroupTitle, counter: "\(count)/\(maxCount)")
}
if self.rightNavigationButton == nil && !isCall {

View File

@ -131,11 +131,10 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
}, checkOptionTitle: nil)
case let .groupCreation(isCall):
if isCall {
//TODO:localize
placeholder = "Search for contacts or usernames"
placeholder = self.presentationData.strings.NewCall_SearchPlaceholder
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
proceedImpl?()
}, checkOptionTitle: isCall ? "Call with video enabled" : nil)
}, checkOptionTitle: self.presentationData.strings.NewCall_VideoOption)
} else {
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
self.footerPanelNode = nil
@ -483,15 +482,14 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
count = contactListNode.selectionState?.selectedPeerIndices.count ?? 0
}
if case let .groupCreation(isCall) = self.mode, isCall {
//TODO:localize
if count == 0 {
// Don't set anything to prevent state update
} else if count <= 1 {
let callTitle: String
if case let .contacts(contactListNode) = self.contentNode, let peer = contactListNode.selectedPeers.first, case let .peer(peer, _, _) = peer {
callTitle = "Call \(EnginePeer(peer).compactDisplayTitle)"
callTitle = self.presentationData.strings.NewCall_ActionCallSingle(EnginePeer(peer).compactDisplayTitle).string
} else {
callTitle = "Call"
callTitle = self.presentationData.strings.NewCall_ActionCallMultiple
}
footerPanelNode.content = FooterPanelNode.Content(title: callTitle, badge: "")
} else {

View File

@ -40,7 +40,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
private let options: Signal<[ContactListAdditionalOption], NoError>
private let displayDeviceContacts: Bool
private let displayCallIcons: Bool
private let multipleSelection: Bool
private let multipleSelection: ContactSelectionControllerParams.MultipleSelectionMode
private let requirePhoneNumbers: Bool
private let allowChannelsInSearch: Bool
@ -153,15 +153,17 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
}
})
if !params.multipleSelection {
if self.multipleSelection != .always {
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in
self?.activateSearch()
})
self.navigationBar?.setContentNode(self.searchContentNode, animated: false)
}
if params.multipleSelection {
if self.multipleSelection == .always {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.beginSearch))
} else if self.multipleSelection == .possible {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.beginSelection))
}
self.getCurrentSendMessageContextMediaPreview = { [weak self] in
@ -220,7 +222,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
}
override func loadDisplayNode() {
self.displayNode = ContactSelectionControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, options: self.options, displayDeviceContacts: self.displayDeviceContacts, displayCallIcons: self.displayCallIcons, multipleSelection: self.multipleSelection, requirePhoneNumbers: self.requirePhoneNumbers, allowChannelsInSearch: self.allowChannelsInSearch, isPeerEnabled: self.isPeerEnabled)
self.displayNode = ContactSelectionControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, options: self.options, displayDeviceContacts: self.displayDeviceContacts, displayCallIcons: self.displayCallIcons, multipleSelection: self.multipleSelection == .always, requirePhoneNumbers: self.requirePhoneNumbers, allowChannelsInSearch: self.allowChannelsInSearch, isPeerEnabled: self.isPeerEnabled)
self._ready.set(self.contactsNode.contactListNode.ready)
self.contactsNode.navigationBar = self.navigationBar
@ -229,8 +231,8 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
self?.deactivateSearch()
}
self.contactsNode.requestOpenPeerFromSearch = { [weak self] peer in
self?.openPeer(peer: peer, action: .generic, node: nil, gesture: nil)
self.contactsNode.requestOpenPeerFromSearch = { [weak self] peer, action in
self?.openPeer(peer: peer, action: action, node: nil, gesture: nil)
}
self.contactsNode.contactListNode.activateSearch = { [weak self] in
@ -335,7 +337,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
if let searchContentNode = self.searchContentNode as? NavigationBarSearchContentNode {
self.contactsNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
} else if self.multipleSelection {
} else if self.multipleSelection == .always {
let contentNode = ContactsSearchNavigationContentNode(presentationData: self.presentationData, dismissSearch: { [weak self] in
if let strongSelf = self, let navigationBar = strongSelf.navigationBar, let searchContentNode = strongSelf.searchContentNode as? ContactsSearchNavigationContentNode {
searchContentNode.deactivate()

View File

@ -37,7 +37,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
var navigationBar: NavigationBar?
var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
var requestOpenPeerFromSearch: ((ContactListPeer, ContactListAction) -> Void)?
var requestOpenDisabledPeerFromSearch: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
var requestMultipleAction: ((_ silent: Bool, _ scheduleTime: Int32?, _ parameters: ChatSendMessageActionSheetController.SendParameters?) -> Void)?
var dismiss: (() -> Void)?
@ -258,7 +258,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
categories.insert(.channels)
}
let searchContainerNode = ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, filters: self.filters, addContact: nil, openPeer: { [weak self] peer in
let searchContainerNode = ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, filters: self.filters, displayCallIcons: self.displayCallIcons, isPeerEnabled: self.isPeerEnabled, addContact: nil, openPeer: { [weak self] peer, action in
if let strongSelf = self {
var updated = false
strongSelf.contactListNode.updateSelectionState { state -> ContactListNodeGroupSelectionState? in
@ -285,7 +285,16 @@ final class ContactSelectionControllerNode: ASDisplayNode {
if updated {
strongSelf.requestDeactivateSearch?()
} else {
strongSelf.requestOpenPeerFromSearch?(peer)
let mappedAction: ContactListAction
switch action {
case .generic:
mappedAction = .generic
case .voiceCall:
mappedAction = .voiceCall
case .videoCall:
mappedAction = .videoCall
}
strongSelf.requestOpenPeerFromSearch?(peer, mappedAction)
}
}
}, openDisabledPeer: { [weak self] peer, reason in
@ -330,7 +339,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
categories.insert(.channels)
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, filters: self.filters, addContact: nil, openPeer: { [weak self] peer in
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, filters: self.filters, displayCallIcons: self.displayCallIcons, isPeerEnabled: self.isPeerEnabled, addContact: nil, openPeer: { [weak self] peer, action in
if let strongSelf = self {
var updated = false
strongSelf.contactListNode.updateSelectionState { state -> ContactListNodeGroupSelectionState? in
@ -357,7 +366,16 @@ final class ContactSelectionControllerNode: ASDisplayNode {
if updated {
strongSelf.requestDeactivateSearch?()
} else {
strongSelf.requestOpenPeerFromSearch?(peer)
let mappedAction: ContactListAction
switch action {
case .generic:
mappedAction = .generic
case .voiceCall:
mappedAction = .voiceCall
case .videoCall:
mappedAction = .videoCall
}
strongSelf.requestOpenPeerFromSearch?(peer, mappedAction)
}
}
}, openDisabledPeer: { [weak self] peer, reason in

View File

@ -394,6 +394,11 @@ func openResolvedUrlImpl(
let _ = (signal
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
if let currentGroupCallController = context.sharedContext.currentGroupCallController as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == resolvedCallLink.id {
context.sharedContext.navigateToCurrentCall()
return
}
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
id: resolvedCallLink.id,
accessHash: resolvedCallLink.accessHash,
@ -407,8 +412,7 @@ func openResolvedUrlImpl(
if case .chat = urlContext {
elevatedLayout = false
}
//TODO:localize
present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: "This link is no longer active"), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in
present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.Chat_ToastCallLinkExpired_Text), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in
return true
}), nil)
})

View File

@ -1963,10 +1963,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return
}
let isVideo = controller?.isCallVideoOptionSelected ?? false
if peerIds.count == 1 {
//TODO:release isVideo
controller?.dismiss()
self.performCall(context: context, parentController: parentController, peerId: peerIds[0], isVideo: false, began: {
self.performCall(context: context, parentController: parentController, peerId: peerIds[0], isVideo: isVideo, began: {
let _ = (context.sharedContext.hasOngoingCall.get()
|> filter { $0 }
|> timeout(1.0, queue: Queue.mainQueue(), alternate: .single(true))
@ -1981,7 +1982,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
})
})
} else {
self.createGroupCall(context: context, parentController: parentController, peerIds: peerIds, completion: {
self.createGroupCall(context: context, parentController: parentController, peerIds: peerIds, isVideo: isVideo, completion: {
controller?.dismiss()
})
}
@ -2012,7 +2013,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
})
}
private func createGroupCall(context: AccountContext, parentController: ViewController, peerIds: [EnginePeer.Id], completion: (() -> Void)? = nil) {
private func createGroupCall(context: AccountContext, parentController: ViewController, peerIds: [EnginePeer.Id], isVideo: Bool, completion: (() -> Void)? = nil) {
parentController.view.endEditing(true)
var cancelImpl: (() -> Void)?
@ -2058,7 +2059,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
isStream: false
),
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
beginWithVideo: false,
beginWithVideo: isVideo,
invitePeerIds: peerIds
)
completion?()
@ -2080,9 +2081,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
if let result {
switch result {
case .linkCopied:
//TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
parentController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: presentationData.strings.CallList_ToastCallLinkCopied_Text, customUndoText: presentationData.strings.CallList_ToastCallLinkCopied_Action, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .undo = action {
openCall()
}

View File

@ -899,6 +899,7 @@ public final class OngoingGroupCallContext {
}
return OngoingGroupCallRequestedVideoChannel(
audioSsrc: channel.audioSsrc,
userId: channel.peerId,
endpointId: channel.endpointId,
ssrcGroups: channel.ssrcGroups.map { group in
return OngoingGroupCallSsrcGroup(

1
third-party/td/td vendored Submodule

@ -0,0 +1 @@
Subproject commit a03a90470d6fca9a5a3db747ba3f3e4a465b5fe7

View File

@ -1,194 +0,0 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: None # All
AllowShortIfStatementsOnASingleLine: Never # WithoutElse
AllowShortLambdasOnASingleLine: Inline # All
AllowShortLoopsOnASingleLine: false # true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
# AttributeMacros:
# - __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAfterAttributes: Never
# BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: Always
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma # BeforeColon
BreakInheritanceList: BeforeComma # BeforeColon
BreakStringLiterals: true
ColumnLimit: 120 # 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- Q_FOREACH_THIS_LIST_MUST_BE_NON_EMPTY
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 0
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
# InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
Decimal: 0
Hex: 0
# JavaScriptQuotes: Leave
# JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
# ObjCBinPackProtocolList: Never
# ObjCBlockIndentWidth: 2
# ObjCBreakBeforeNestedBlockParam: true
# ObjCSpaceAfterProperty: false
# ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: NextLine
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right # Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false # true
RemoveBracesLLVM: false
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 0 # 1
SortIncludes: CaseInsensitive # CaseSensitive
# SortJavaStaticImport: Before
SortUsingDeclarations: Lexicographic # LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: 1 # -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 100 # 8
UseTab: Never
...

View File

@ -1,39 +0,0 @@
* text=auto
*.cpp text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.hpp text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.h text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.c text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.tl text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.mm text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.txt text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.sh text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent eol=lf
*.php text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.ps1 text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent eol=crlf
*.cmake text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.md text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.in text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.html text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.java text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.py text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.js text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.patch text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.swift text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.pbxproj text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.cs text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.xaml text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.appxmanifest text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.vsixmanifest text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.nuspec text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.targets text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.json text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.csproj text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.sln text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.xml text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.config text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
sqlite/sqlite/* linguist-vendored
*.pfx binary
*.png binary

View File

@ -1,8 +0,0 @@
**/*build*/
**/.*.swp
**/.DS_Store
**/auto/
docs/
/tdlib/
vcpkg/
.clang-tidy

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +0,0 @@
# - Adds a compiler flag if it is supported by the compiler
#
# This function checks that the supplied compiler flag is supported and then
# adds it to the corresponding compiler flags
#
# add_cxx_compiler_flag(<FLAG> [<VARIANT>])
#
# - Example
#
# include(AddCXXCompilerFlag)
# add_cxx_compiler_flag(-Wall)
# add_cxx_compiler_flag(-no-strict-aliasing RELEASE)
# Requires CMake 2.6+
if (__add_cxx_compiler_flag)
return()
endif()
set(__add_cxx_compiler_flag INCLUDED)
include(CheckCXXCompilerFlag)
function(mangle_compiler_flag FLAG OUTPUT)
string(TOUPPER "HAVE_CXX_FLAG_${FLAG}" SANITIZED_FLAG)
string(REPLACE "+" "X" SANITIZED_FLAG ${SANITIZED_FLAG})
string(REGEX REPLACE "[^A-Za-z_0-9]" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
string(REGEX REPLACE "_+" "_" SANITIZED_FLAG ${SANITIZED_FLAG})
set(${OUTPUT} "${SANITIZED_FLAG}" PARENT_SCOPE)
endfunction(mangle_compiler_flag)
function(add_cxx_compiler_flag FLAG)
string(REPLACE "-Wno-" "-W" MAIN_FLAG ${FLAG})
mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG_NAME)
if (DEFINED CMAKE_REQUIRED_FLAGS)
set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}")
else()
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
endif()
check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG_NAME})
if (DEFINED OLD_CMAKE_REQUIRED_FLAGS)
set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
else()
unset(CMAKE_REQUIRED_FLAGS)
endif()
if (${MANGLED_FLAG_NAME})
set(VARIANT ${ARGV1})
if (ARGV1)
string(TOUPPER "_${VARIANT}" VARIANT)
endif()
set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
endif()
endfunction()
function(add_required_cxx_compiler_flag FLAG)
string(REPLACE "-Wno-" "-W" MAIN_FLAG ${FLAG})
mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG_NAME)
set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}")
check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG_NAME})
set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
if (${MANGLED_FLAG_NAME})
set(VARIANT ${ARGV1})
if (ARGV1)
string(TOUPPER "_${VARIANT}" VARIANT)
endif()
set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE)
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${FLAG}" PARENT_SCOPE)
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}" PARENT_SCOPE)
else()
message(FATAL_ERROR "Required flag '${FLAG}' is not supported by the compiler")
endif()
endfunction()

View File

@ -1,62 +0,0 @@
# Original issue:
# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733
#
# For reference:
# * https://gcc.gnu.org/wiki/Atomic/GCCMM
#
# riscv64 specific:
# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html
#
# ATOMICS_FOUND - system has C++ atomics
# ATOMICS_LIBRARIES - libraries needed to use C++ atomics
if (ATOMICS_FOUND)
return()
endif()
include(CheckCXXSourceCompiles)
# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed
# to convert smaller atomics to those larger ones via masking and
# shifting like LLVM, but it's a known bug that it does not. This means
# anything that wants to use atomics on 1-byte or 2-byte types needs
# to link atomic library, but not 4-byte or 8-byte (though it does no harm).
set(ATOMIC_CODE
"
#include <atomic>
#include <cstdint>
std::atomic<std::uint8_t> n8{0}; // riscv64
std::atomic<std::uint64_t> n64{0}; // armel, mipsel, powerpc
int main() {
++n8;
++n64;
}")
set(ATOMICS_LIBS " " "-latomic")
if (CMAKE_SYSTEM_NAME MATCHES "NetBSD")
set(ATOMICS_LIBS "${ATOMICS_LIBS}" /usr/pkg/gcc12/x86_64--netbsd/lib/libatomic.so /usr/pkg/gcc12/i486--netbsdelf/lib/libatomic.so)
endif()
foreach (ATOMICS_LIBRARY ${ATOMICS_LIBS})
unset(ATOMICS_FOUND CACHE)
set(CMAKE_REQUIRED_LIBRARIES "${ATOMICS_LIBRARY}")
check_cxx_source_compiles("${ATOMIC_CODE}" ATOMICS_FOUND)
unset(CMAKE_REQUIRED_LIBRARIES)
if (ATOMICS_FOUND)
if (NOT ATOMICS_LIBRARY STREQUAL " ")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY)
set(ATOMICS_LIBRARIES "${ATOMICS_LIBRARY}" CACHE STRING "Atomic library" FORCE)
else()
set(ATOMICS_LIBRARIES "" CACHE STRING "Atomic operations library" FORCE)
endif()
break()
endif()
endforeach()
if (Atomics_FIND_REQUIRED AND NOT ATOMICS_FOUND)
message(FATAL_ERROR "Atomic operations library isn't found.")
endif()
unset(ATOMICS_LIBRARY)
unset(ATOMICS_LIBS)
unset(ATOMIC_CODE)

View File

@ -1,25 +0,0 @@
if (APPLE)
find_path(READLINE_INCLUDE_DIR readline/readline.h /opt/homebrew/opt/readline/include /usr/local/opt/readline/include /opt/local/include /opt/include /usr/local/include /usr/include NO_DEFAULT_PATH)
endif()
find_path(READLINE_INCLUDE_DIR readline/readline.h)
if (APPLE)
find_library(READLINE_LIBRARY readline /opt/homebrew/opt/readline/lib /usr/local/opt/readline/lib /opt/local/lib /opt/lib /usr/local/lib /usr/lib NO_DEFAULT_PATH)
endif()
find_library(READLINE_LIBRARY readline)
if (READLINE_INCLUDE_DIR AND READLINE_LIBRARY AND NOT GNU_READLINE_FOUND)
set(CMAKE_REQUIRED_INCLUDES "${READLINE_INCLUDE_DIR}")
set(CMAKE_REQUIRED_LIBRARIES "${READLINE_LIBRARY}")
include(CheckCXXSourceCompiles)
unset(GNU_READLINE_FOUND CACHE)
check_cxx_source_compiles("#include <stdio.h>\n#include <readline/readline.h>\nint main() { rl_replace_line(\"\", 0); }" GNU_READLINE_FOUND)
if (NOT GNU_READLINE_FOUND)
unset(READLINE_INCLUDE_DIR CACHE)
unset(READLINE_LIBRARY CACHE)
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Readline DEFAULT_MSG READLINE_INCLUDE_DIR READLINE_LIBRARY)
mark_as_advanced(READLINE_INCLUDE_DIR READLINE_LIBRARY)

View File

@ -1,99 +0,0 @@
function(get_relative_link OUTPUT PATH)
if (PATH MATCHES "^[$]<[$]<CONFIG:DEBUG>:")
set(${OUTPUT} "" PARENT_SCOPE)
return()
endif()
string(REGEX REPLACE "^[$]<[$]<NOT:[$]<CONFIG:DEBUG>>:(.*)>$" "\\1" PATH "${PATH}")
get_filename_component(NAME "${PATH}" NAME_WE)
if (IS_ABSOLUTE ${PATH})
get_filename_component(DIRECTORY_NAME "${PATH}" DIRECTORY)
if (WIN32)
set(${OUTPUT} "-l\"${DIRECTORY_NAME}/${NAME}\"" PARENT_SCOPE)
else()
get_filename_component(FULL_NAME "${PATH}" NAME)
set(${OUTPUT} "-L\"${DIRECTORY_NAME}\" -l:${FULL_NAME}" PARENT_SCOPE)
endif()
return()
endif()
if (NOT WIN32 AND NAME MATCHES "^lib")
string(REGEX REPLACE "^lib" "-l" LINK "${NAME}")
elseif (NAME MATCHES "^-")
set(LINK "${NAME}")
else()
string(CONCAT LINK "-l" "${NAME}")
endif()
set(${OUTPUT} "${LINK}" PARENT_SCOPE)
endfunction()
function(generate_pkgconfig TARGET DESCRIPTION)
# message("Generating pkg-config for ${TARGET}")
get_filename_component(PREFIX "${CMAKE_INSTALL_PREFIX}" REALPATH)
get_target_property(LIST "${TARGET}" LINK_LIBRARIES)
set(REQS "")
set(LIBS "")
foreach (LIB ${LIST})
if (TARGET "${LIB}")
set(HAS_REQS 1)
list(APPEND REQS "${LIB}")
else()
set(HAS_LIBS 1)
get_relative_link(LINK "${LIB}")
if (NOT LINK EQUAL "")
list(APPEND LIBS "${LINK}")
endif()
endif()
endforeach()
if (HAS_REQS)
set(REQUIRES "")
foreach (REQ ${REQS})
set(REQUIRES "${REQUIRES} ${REQ}")
endforeach()
set(REQUIRES "Requires.private:${REQUIRES}\n")
endif()
if (HAS_LIBS)
set(LIBRARIES "")
list(REVERSE LIBS)
list(REMOVE_DUPLICATES LIBS)
foreach (LIB ${LIBS})
set(LIBRARIES " ${LIB}${LIBRARIES}")
endforeach()
set(LIBRARIES "Libs.private:${LIBRARIES}\n")
endif()
if (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
else()
set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
endif()
if (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
else()
set(PKGCONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}")
endif()
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig")
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/${TARGET}.pc" CONTENT
"prefix=${PREFIX}
Name: ${TARGET}
Description: ${DESCRIPTION}
Version: ${PROJECT_VERSION}
CFlags: -I\"${PKGCONFIG_INCLUDEDIR}\"
Libs: -L\"${PKGCONFIG_LIBDIR}\" -l${TARGET}
${REQUIRES}${LIBRARIES}")
get_target_property(LIBRARY_TYPE "${TARGET}" TYPE)
if (LIBRARY_TYPE STREQUAL "STATIC_LIBRARY" OR LIBRARY_TYPE STREQUAL "SHARED_LIBRARY")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/${TARGET}.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
elseif (LIBRARY_TYPE STREQUAL "INTERFACE_LIBRARY")
# TODO: support interface libraries
else()
message(FATAL_ERROR "Don't know how to handle ${TARGET} of type ${LIBRARY_TYPE}")
endif()
endfunction()

View File

@ -1,127 +0,0 @@
# - Returns a version string from Git
#
# These functions force a re-configure on each git commit so that you can
# trust the values of the variables in your build system.
#
# get_git_head_revision(<refspecvar> <hashvar>)
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2020 Ryan Pavlik <ryan.pavlik@gmail.com> <abiryan@ryand.net>
# http://academic.cleardefinition.com
#
# Copyright 2009-2013, Iowa State University.
# Copyright 2013-2020, Ryan Pavlik
# Copyright 2013-2020, Contributors
# SPDX-License-Identifier: BSL-1.0
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if (__get_git_revision_description)
return()
endif()
set(__get_git_revision_description YES)
# We must run the following at "include" time, not at function call time,
# to find the path to this module rather than the path to a calling list file
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
# Function _git_find_closest_git_dir finds the next closest .git directory
# that is part of any directory in the path defined by _start_dir.
# The result is returned in the parent scope variable whose name is passed
# as variable _git_dir_var. If no .git directory can be found, the
# function returns an empty string via _git_dir_var.
#
# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
# neither foo nor bar contain a file/directory .git. This will return
# C:/bla/.git
#
function(_git_find_closest_git_dir _start_dir _git_dir_var)
set(cur_dir "${_start_dir}")
set(git_dir "${_start_dir}/.git")
while (NOT EXISTS "${git_dir}")
# .git dir not found, search parent directories
set(git_previous_parent "${cur_dir}")
get_filename_component(cur_dir "${cur_dir}" DIRECTORY)
if (cur_dir STREQUAL git_previous_parent)
# We have reached the root directory, we are not in git
set(${_git_dir_var} "" PARENT_SCOPE)
return()
endif()
set(git_dir "${cur_dir}/.git")
endwhile()
set(${_git_dir_var} "${git_dir}" PARENT_SCOPE)
endfunction()
function(get_git_head_revision _refspecvar _hashvar)
_git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR)
if (NOT GIT_DIR STREQUAL "")
file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_CURRENT_SOURCE_DIR}" "${GIT_DIR}")
if (_relative_to_source_dir MATCHES "^[.][.]")
# We've gone above the CMake root dir.
set(GIT_DIR "")
endif()
endif()
if (GIT_DIR STREQUAL "")
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
return()
endif()
find_package(Git QUIET)
# Check if the current source dir is a git submodule or a worktree.
# In both cases .git is a file instead of a directory.
#
if ((NOT IS_DIRECTORY ${GIT_DIR}) AND Git_FOUND)
# The following git command will return a non empty string that
# points to the super project working tree if the current
# source dir is inside a git submodule.
# Otherwise, the command will return an empty string.
#
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE out
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if (NOT out STREQUAL "")
# If out is non-empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule
file(READ ${GIT_DIR} submodule)
string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule})
string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE)
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
else()
# GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree
file(READ ${GIT_DIR} worktree_ref)
# The .git directory contains a path to the worktree information directory
# inside the parent git repo of the worktree.
string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir ${worktree_ref})
string(STRIP ${git_worktree_dir} git_worktree_dir)
_git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR)
set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD")
endif()
else()
set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
endif()
if (NOT EXISTS "${HEAD_SOURCE_FILE}")
return()
endif()
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
if (NOT EXISTS "${GIT_DATA}")
file(MAKE_DIRECTORY "${GIT_DATA}")
endif()
set(HEAD_FILE "${GIT_DATA}/HEAD")
configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY)
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY)
include("${GIT_DATA}/grabRef.cmake")
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
endfunction()

View File

@ -1,43 +0,0 @@
#
# Internal file for GetGitRevisionDescription.cmake
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright 2009-2012, Iowa State University
# Copyright 2011-2015, Contributors
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
# SPDX-License-Identifier: BSL-1.0
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
if (HEAD_CONTENTS MATCHES "ref")
# named branch
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
if (EXISTS "@GIT_DIR@/${HEAD_REF}")
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
else()
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
if (PACKED_REFS MATCHES "([0-9a-z]*) ${HEAD_REF}")
set(HEAD_HASH "${CMAKE_MATCH_1}")
endif()
endif()
else()
# detached HEAD
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
endif()
if (NOT HEAD_HASH)
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
string(STRIP "${HEAD_HASH}" HEAD_HASH)
endif()

View File

@ -1,14 +0,0 @@
function(prevent_in_source_build)
get_filename_component(REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
get_filename_component(REAL_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH)
if (REAL_BINARY_DIR STREQUAL REAL_SOURCE_DIR)
message(" Out-of-source build must be used. Remove the files already")
message(" created by CMake and rerun CMake from a new directory:")
message(" rm -rf CMakeFiles CMakeCache.txt")
message(" mkdir build")
message(" cd build")
message(" cmake ..")
message(FATAL_ERROR "In-source build failed.")
endif()
endfunction()

View File

@ -1,185 +0,0 @@
# Configures C++17 compiler, setting TDLib-specific compilation options.
function(td_set_up_compiler)
set(CMAKE_EXPORT_COMPILE_COMMANDS 1 PARENT_SCOPE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON PARENT_SCOPE)
include(illumos)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(GCC 1)
set(GCC 1 PARENT_SCOPE)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CLANG 1)
set(CLANG 1 PARENT_SCOPE)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
set(INTEL 1)
set(INTEL 1 PARENT_SCOPE)
elseif (NOT MSVC)
message(FATAL_ERROR "Compiler isn't supported")
endif()
include(CheckCXXCompilerFlag)
if (GCC OR CLANG OR INTEL)
if (WIN32 AND INTEL)
set(STD17_FLAG /Qstd=c++17)
else()
set(STD17_FLAG -std=c++17)
endif()
if (GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0))
message(FATAL_ERROR "No C++17 support in the compiler. Please upgrade the compiler to at least GCC 7.0.")
endif()
if (CLANG AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
message(FATAL_ERROR "No C++17 support in the compiler. Please upgrade the compiler to at least clang 5.0.")
endif()
check_cxx_compiler_flag(${STD17_FLAG} HAVE_STD17)
elseif (MSVC)
set(HAVE_STD17 MSVC_VERSION>=1914) # MSVC 2017 version 15.7
endif()
if (NOT HAVE_STD17)
message(FATAL_ERROR "No C++17 support in the compiler. Please upgrade the compiler.")
endif()
if (MSVC)
if (CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
endif()
add_definitions(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /utf-8 /GR- /W4 /wd4100 /wd4127 /wd4324 /wd4505 /wd4814 /wd4702 /bigobj")
elseif (CLANG OR GCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD17_FLAG} -fno-omit-frame-pointer -fno-exceptions -fno-rtti")
if (APPLE)
set(TD_LINKER_FLAGS "-Wl,-dead_strip")
if (NOT CMAKE_BUILD_TYPE MATCHES "Deb")
set(TD_LINKER_FLAGS "${TD_LINKER_FLAGS},-x,-S")
endif()
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
set(TD_LINKER_FLAGS "-Wl,-z,ignore")
elseif (EMSCRIPTEN)
set(TD_LINKER_FLAGS "-Wl,--gc-sections")
elseif (ANDROID)
set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL -Wl,--icf=safe")
else()
set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL")
endif()
endif()
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
if (WIN32 OR CYGWIN)
if (GCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj")
endif()
endif()
elseif (INTEL)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD17_FLAG}")
endif()
if (WIN32)
add_definitions(-DNTDDI_VERSION=0x06020000 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -DPSAPI_VERSION=1 -DNOMINMAX -DUNICODE -D_UNICODE -DWIN32_LEAN_AND_MEAN)
endif()
if (CYGWIN)
add_definitions(-D_DEFAULT_SOURCE=1 -DFD_SETSIZE=4096)
endif()
# _FILE_OFFSET_BITS is broken in Android NDK r15, r15b and r17 and doesn't work prior to Android 7.0
add_definitions(-D_FILE_OFFSET_BITS=64)
# _GNU_SOURCE might not be defined by g++
add_definitions(-D_GNU_SOURCE)
if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lsocket -lnsl")
if (ILLUMOS)
add_definitions(-DTD_ILLUMOS=1)
endif()
endif()
include(AddCXXCompilerFlag)
if (NOT MSVC)
add_cxx_compiler_flag("-Wall")
add_cxx_compiler_flag("-Wextra")
add_cxx_compiler_flag("-Wimplicit-fallthrough=2")
add_cxx_compiler_flag("-Wpointer-arith")
add_cxx_compiler_flag("-Wcast-qual")
add_cxx_compiler_flag("-Wsign-compare")
add_cxx_compiler_flag("-Wduplicated-branches")
add_cxx_compiler_flag("-Wduplicated-cond")
add_cxx_compiler_flag("-Walloc-zero")
add_cxx_compiler_flag("-Wlogical-op")
add_cxx_compiler_flag("-Wno-tautological-compare")
add_cxx_compiler_flag("-Wpointer-arith")
add_cxx_compiler_flag("-Wvla")
add_cxx_compiler_flag("-Wnon-virtual-dtor")
add_cxx_compiler_flag("-Wno-unused-parameter")
add_cxx_compiler_flag("-Wconversion")
add_cxx_compiler_flag("-Wno-sign-conversion")
add_cxx_compiler_flag("-Wc++17-compat-pedantic")
add_cxx_compiler_flag("-Wdeprecated")
add_cxx_compiler_flag("-Wno-unused-command-line-argument")
add_cxx_compiler_flag("-Qunused-arguments")
add_cxx_compiler_flag("-Wno-unknown-warning-option")
add_cxx_compiler_flag("-Wodr")
add_cxx_compiler_flag("-flto-odr-type-merging")
add_cxx_compiler_flag("-Wno-psabi")
add_cxx_compiler_flag("-Wunused-member-function")
add_cxx_compiler_flag("-Wunused-private-field")
# add_cxx_compiler_flag("-Werror")
# add_cxx_compiler_flag("-Wcast-align")
#std::int32_t <-> int and off_t <-> std::size_t/std::int64_t
# add_cxx_compiler_flag("-Wuseless-cast")
#external headers like openssl
# add_cxx_compiler_flag("-Wzero-as-null-pointer-constant")
endif()
if (GCC)
add_cxx_compiler_flag("-Wno-maybe-uninitialized") # too many false positives
endif()
if (WIN32 AND GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0))
# warns about casts of function pointers returned by GetProcAddress
add_cxx_compiler_flag("-Wno-cast-function-type")
endif()
if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0))
# warns about a lot of "return std::move", which are not redundant for compilers without fix for DR 1579, i.e. GCC 4.9 or clang 3.8
# see http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1579
add_cxx_compiler_flag("-Wno-redundant-move")
endif()
if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0))
add_cxx_compiler_flag("-Wno-stringop-overflow") # some false positives
endif()
if (CLANG AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5))
# https://stackoverflow.com/questions/26744556/warning-returning-a-captured-reference-from-a-lambda
add_cxx_compiler_flag("-Wno-return-stack-address")
endif()
if (GCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13.0))
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104030
add_cxx_compiler_flag("-Wbidi-chars=none")
endif()
if (MINGW)
add_cxx_compiler_flag("-ftrack-macro-expansion=0")
endif()
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem /usr/include/c++/v1")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=leak")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}" PARENT_SCOPE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}" PARENT_SCOPE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" PARENT_SCOPE)
endfunction()

View File

@ -1,278 +0,0 @@
# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake
# files which are included with CMake 2.8.4
# It has been altered for iOS development
# Options:
#
# IOS_PLATFORM = OS (default) or SIMULATOR
# This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders
# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch.
# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch.
#
# IOS_ARCH = automatic(default) or "arch1;arch2" (e.q. "x86_64;arm64")
# By default this value will be automatically chosen based on the IOS_PLATFORM value above.
# If set manually, it will override the default and force to build those architectures only.
#
# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder
# By default this location is automatically chosen based on the IOS_PLATFORM value above.
# If set manually, it will override the default location and force the user of a particular Developer Platform
#
# CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder
# By default this location is automatically chosen based on the CMAKE_IOS_DEVELOPER_ROOT value.
# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path.
# If set manually, this will force the use of a specific SDK version
# Macros:
#
# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE)
# A convenience macro for setting xcode specific properties on targets
# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1")
#
# find_host_package (PROGRAM ARGS)
# A macro used to find executable programs on the host system, not within the iOS environment.
# Thanks to the android-cmake project for providing the command
# Standard settings
set (CMAKE_SYSTEM_NAME Darwin)
set (CMAKE_SYSTEM_VERSION 1)
set (UNIX True)
set (APPLE True)
set (IOS True)
# Required as of cmake 2.8.10
set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE)
# Determine the cmake host system version so we know where to find the iOS SDKs
find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin)
if (CMAKE_UNAME)
execute_process(COMMAND uname -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}")
endif (CMAKE_UNAME)
# Force the compilers to gcc for iOS
set (CMAKE_C_COMPILER /usr/bin/gcc)
set (CMAKE_CXX_COMPILER /usr/bin/g++)
set (CMAKE_AR ar CACHE FILEPATH "" FORCE)
set (CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE)
set (PKG_CONFIG_EXECUTABLE pkg-config CACHE FILEPATH "" FORCE)
# Setup iOS platform unless specified manually with IOS_PLATFORM
if (NOT DEFINED IOS_PLATFORM)
set (IOS_PLATFORM "OS")
endif (NOT DEFINED IOS_PLATFORM)
set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform")
# Check the platform selection and setup for developer root
if (IOS_PLATFORM STREQUAL "OS")
set (IOS_PLATFORM_LOCATION "iPhoneOS.platform")
set (XCODE_IOS_PLATFORM ios)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos")
set (APPLE_IOS True)
elseif (IOS_PLATFORM STREQUAL "SIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform")
set (XCODE_IOS_PLATFORM ios-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator")
set (APPLE_IOS True)
elseif (IOS_PLATFORM STREQUAL "WATCHOS")
set (IOS_PLATFORM_LOCATION "WatchOS.platform")
set (XCODE_IOS_PLATFORM watchos)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-watchos")
set (APPLE_WATCH True)
elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "WatchSimulator.platform")
set (XCODE_IOS_PLATFORM watchos-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-watchsimulator")
set (APPLE_WATCH True)
elseif (IOS_PLATFORM STREQUAL "TVOS")
set (IOS_PLATFORM_LOCATION "AppleTvOS.platform")
set (XCODE_IOS_PLATFORM tvos)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvos")
set (APPLE_TV True)
elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "AppleTvSimulator.platform")
set (XCODE_IOS_PLATFORM tvos-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-tvsimulator")
set (APPLE_TV True)
elseif (IOS_PLATFORM STREQUAL "VISIONOS")
set (IOS_PLATFORM_LOCATION "XROS.platform")
set (XCODE_IOS_PLATFORM xros)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xros")
set (APPLE_VISION True)
elseif (IOS_PLATFORM STREQUAL "VISIONSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "XRSimulator.platform")
set (XCODE_IOS_PLATFORM xros-simulator)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xrsimulator")
set (APPLE_VISION True)
else (IOS_PLATFORM STREQUAL "OS")
message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS, SIMULATOR, or WATCHOS.")
endif ()
# All iOS/Darwin specific settings - some may be redundant
set (CMAKE_SHARED_LIBRARY_PREFIX "lib")
set (CMAKE_SHARED_LIBRARY_SUFFIX ".dylib")
set (CMAKE_SHARED_MODULE_PREFIX "lib")
set (CMAKE_SHARED_MODULE_SUFFIX ".so")
set (CMAKE_MODULE_EXISTS 1)
set (CMAKE_DL_LIBS "")
set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ")
set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ")
set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}")
set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}")
if (IOS_DEPLOYMENT_TARGET)
set (XCODE_IOS_PLATFORM_VERSION_FLAGS "-m${XCODE_IOS_PLATFORM}-version-min=${IOS_DEPLOYMENT_TARGET}")
endif()
set (CMAKE_SHARED_LINKER_FLAGS_INIT "-fapplication-extension")
set (CMAKE_C_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS}")
# Hidden visibility is required for cxx on iOS
set (CMAKE_CXX_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fvisibility-inlines-hidden")
set (CMAKE_C_LINK_FLAGS "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fapplication-extension -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}")
set (CMAKE_CXX_LINK_FLAGS "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fapplication-extension -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}")
set (CMAKE_PLATFORM_HAS_INSTALLNAME 1)
set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -headerpad_max_install_names")
set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -headerpad_max_install_names")
set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,")
set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,")
set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a")
# hack: if a new cmake (which uses CMAKE_INSTALL_NAME_TOOL) runs on an old build tree
# (where install_name_tool was hardcoded) and where CMAKE_INSTALL_NAME_TOOL isn't in the cache
# and still cmake didn't fail in CMakeFindBinUtils.cmake (because it isn't rerun)
# hardcode CMAKE_INSTALL_NAME_TOOL here to install_name_tool, so it behaves as it did before, Alex
if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL)
find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool)
endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL)
# Setup iOS deployment target
set (IOS_DEPLOYMENT_TARGET ${IOS_DEPLOYMENT_TARGET} CACHE STRING "Minimum iOS version")
# Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT
# Note Xcode 4.3 changed the installation location, choose the most recent one available
execute_process(COMMAND /usr/bin/xcode-select -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
set (XCODE_POST_43_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer")
set (XCODE_PRE_43_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer")
if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
if (EXISTS ${XCODE_POST_43_ROOT})
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_POST_43_ROOT})
elseif (EXISTS ${XCODE_PRE_43_ROOT})
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_PRE_43_ROOT})
endif (EXISTS ${XCODE_POST_43_ROOT})
endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform")
# Find and use the most recent iOS sdk unless specified manually with CMAKE_IOS_SDK_ROOT
if (NOT DEFINED CMAKE_IOS_SDK_ROOT)
file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*")
if (_CMAKE_IOS_SDKS)
list (SORT _CMAKE_IOS_SDKS)
list (REVERSE _CMAKE_IOS_SDKS)
list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT)
else (_CMAKE_IOS_SDKS)
message (FATAL_ERROR "No iOS SDK's found in default search path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.")
endif (_CMAKE_IOS_SDKS)
message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}")
endif (NOT DEFINED CMAKE_IOS_SDK_ROOT)
set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK")
# Set the sysroot default to the most recent SDK
set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support")
# Set the architectures unless specified manually with IOS_ARCH
if (NOT DEFINED IOS_ARCH)
if (IOS_PLATFORM STREQUAL "OS")
set (IOS_ARCH "arm64")
elseif (IOS_PLATFORM STREQUAL "SIMULATOR")
set (IOS_ARCH "x86_64;arm64")
elseif (IOS_PLATFORM STREQUAL "WATCHOS")
set (IOS_ARCH "armv7k;arm64_32;arm64")
# Include C++ Standard Library for Xcode 15 builds.
include_directories(SYSTEM "${CMAKE_IOS_SDK_ROOT}/usr/include/c++/v1")
elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR")
set (IOS_ARCH "x86_64;arm64")
# Include C++ Standard Library for Xcode 15 builds.
include_directories(SYSTEM "${CMAKE_IOS_SDK_ROOT}/usr/include/c++/v1")
elseif (IOS_PLATFORM STREQUAL "TVOS")
set (IOS_ARCH "arm64")
elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR")
set (IOS_ARCH "x86_64;arm64")
elseif (IOS_PLATFORM STREQUAL "VISIONOS")
set (IOS_ARCH "arm64")
elseif (IOS_PLATFORM STREQUAL "VISIONSIMULATOR")
set (IOS_ARCH "x86_64;arm64")
endif()
endif()
message (STATUS "The iOS architectures: ${IOS_ARCH}")
set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE STRING "Build architecture for iOS")
# Set the find root to the iOS developer roots and to user defined paths
set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE STRING "iOS find search path root")
# default to searching for frameworks first
set (CMAKE_FIND_FRAMEWORK FIRST)
# set up the default search directories for frameworks
set (CMAKE_SYSTEM_FRAMEWORK_PATH
${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks
${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks
${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks
)
# only search the iOS sdks, not the remainder of the host filesystem
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# This little macro lets you set any Xcode specific property
macro (set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE)
set_property (TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE})
endmacro (set_xcode_property)
# This macro lets you find executable programs on the host system
macro (find_host_package)
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
set (IOS FALSE)
find_package(${ARGN})
set (IOS TRUE)
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endmacro (find_host_package)

View File

@ -1,10 +0,0 @@
if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
#
# Determine if the host is running an illumos distribution:
#
execute_process(COMMAND /usr/bin/uname -o OUTPUT_VARIABLE UNAME_O OUTPUT_STRIP_TRAILING_WHITESPACE)
if (UNAME_O STREQUAL "illumos")
set(ILLUMOS 1)
endif()
endif()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,145 +0,0 @@
# TDLib
TDLib (Telegram Database library) is a cross-platform library for building [Telegram](https://telegram.org) clients. It can be easily used from almost any programming language.
## Table of Contents
- [Features](#features)
- [Examples and documentation](#usage)
- [Dependencies](#dependencies)
- [Building](#building)
- [Using in CMake C++ projects](#using-cxx)
- [Using in Java projects](#using-java)
- [Using in .NET projects](#using-dotnet)
- [Using with other programming languages](#using-json)
- [License](#license)
<a name="features"></a>
## Features
`TDLib` has many advantages. Notably `TDLib` is:
* **Cross-platform**: `TDLib` can be used on Android, iOS, Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, illumos, Windows Phone, WebAssembly, watchOS, tvOS, visionOS, Tizen, Cygwin. It should also work on other *nix systems with or without minimal effort.
* **Multilanguage**: `TDLib` can be easily used with any programming language that is able to execute C functions. Additionally, it already has native Java (using `JNI`) bindings and .NET (using `C++/CLI` and `C++/CX`) bindings.
* **Easy to use**: `TDLib` takes care of all network implementation details, encryption and local data storage.
* **High-performance**: in the [Telegram Bot API](https://core.telegram.org/bots/api), each `TDLib` instance handles more than 25000 active bots simultaneously.
* **Well-documented**: all `TDLib` API methods and public interfaces are fully documented.
* **Consistent**: `TDLib` guarantees that all updates are delivered in the right order.
* **Reliable**: `TDLib` remains stable on slow and unreliable Internet connections.
* **Secure**: all local data is encrypted using a user-provided encryption key.
* **Fully-asynchronous**: requests to `TDLib` don't block each other or anything else, responses are sent when they are available.
<a name="usage"></a>
## Examples and documentation
See our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
Take a look at our [examples](https://github.com/tdlib/td/blob/master/example/README.md#tdlib-usage-and-build-examples).
See a [TDLib build instructions generator](https://tdlib.github.io/td/build.html) for detailed instructions on how to build TDLib.
See description of our [JSON](#using-json), [C++](#using-cxx), [Java](#using-java) and [.NET](#using-dotnet) interfaces.
See the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html)
for a list of all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
<a name="dependencies"></a>
## Dependencies
`TDLib` depends on:
* C++17 compatible compiler (Clang 5.0+, GCC 7.0+, MSVC 19.1+ (Visual Studio 2017.7+), Intel C++ Compiler 19+)
* OpenSSL
* zlib
* gperf (build only)
* CMake (3.10+, build only)
* PHP (optional, for documentation generation)
<a name="building"></a>
## Building
The simplest way to build `TDLib` is to use our [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
You need only to choose your programming language and target operating system to receive complete build instructions.
In general, you need to install all `TDLib` [dependencies](#dependencies), enter directory containing `TDLib` sources and compile them using CMake:
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
```
To build `TDLib` on low memory devices you can run [SplitSource.php](https://github.com/tdlib/td/blob/master/SplitSource.php) script
before compiling main `TDLib` source code and compile only needed targets:
```
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --target prepare_cross_compiling
cd ..
php SplitSource.php
cd build
cmake --build . --target tdjson
cmake --build . --target tdjson_static
cd ..
php SplitSource.php --undo
```
In our tests clang 6.0 with libc++ required less than 500 MB of RAM per file and GCC 4.9/6.3 used less than 1 GB of RAM per file.
<a name="using-cxx"></a>
## Using in CMake C++ projects
For C++ projects that use CMake, the best approach is to build `TDLib` as part of your project or to install it system-wide.
There are several libraries that you could use in your CMake project:
* Td::TdJson, Td::TdJsonStatic — dynamic and static version of a JSON interface. This has a simple C interface, so it can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for more information.
* Td::TdStatic — static library with C++ interface for general usage.
See [ClientManager](https://core.telegram.org/tdlib/docs/classtd_1_1_client_manager.html) and [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) documentation for more information.
For example, part of your CMakeLists.txt may look like this:
```
add_subdirectory(td)
target_link_libraries(YourTarget PRIVATE Td::TdStatic)
```
Or you could install `TDLib` and then reference it in your CMakeLists.txt like this:
```
find_package(Td 1.8.46 REQUIRED)
target_link_libraries(YourTarget PRIVATE Td::TdStatic)
```
See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/blob/master/example/cpp/CMakeLists.txt).
<a name="using-java"></a>
## Using in Java projects
`TDLib` provides native Java interface through JNI. To enable it, specify option `-DTD_ENABLE_JNI=ON` to CMake.
See [example/java](https://github.com/tdlib/td/tree/master/example/java) for example of using `TDLib` from Java and detailed build and usage instructions.
<a name="using-dotnet"></a>
## Using in .NET projects
`TDLib` provides native .NET interface through `C++/CLI` and `C++/CX`. To enable it, specify option `-DTD_ENABLE_DOTNET=ON` to CMake.
.NET Core supports `C++/CLI` only since version 3.1 and only on Windows, so if older .NET Core is used or portability is needed, then `TDLib` JSON interface should be used through P/Invoke instead.
See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for example of using `TDLib` from C# and detailed build and usage instructions.
See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for example of using `TDLib` from C# UWP application and detailed build and usage instructions for Visual Studio Extension "TDLib for Universal Windows Platform".
When `TDLib` is built with `TD_ENABLE_DOTNET` option enabled, `C++` documentation is removed from some files. You need to checkout these files to return `C++` documentation back:
```
git checkout td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
```
<a name="using-json"></a>
## Using from other programming languages
`TDLib` provides efficient native C++, Java, and .NET interfaces.
But for most use cases we suggest to use the JSON interface, which can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for detailed JSON interface description,
the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of
all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
`TDLib` JSON interface adheres to semantic versioning and versions with the same major version number are binary and backward compatible, but the underlying `TDLib` API can be different for different minor and even patch versions.
If you need to support different `TDLib` versions, then you can use a value of the `version` option to find exact `TDLib` version to use appropriate API methods.
See [example/python/tdjson_example.py](https://github.com/tdlib/td/blob/master/example/python/tdjson_example.py) for an example of such usage.
<a name="license"></a>
## License
`TDLib` is licensed under the terms of the Boost Software License. See [LICENSE_1_0.txt](http://www.boost.org/LICENSE_1_0.txt) for more information.

View File

@ -1,511 +0,0 @@
<?php
function disjoint_set_find(&$parents, $x) {
if ($parents[$x] !== $x) {
return $parents[$x] = disjoint_set_find($parents, $parents[$x]);
}
return $x;
}
function disjoint_set_union(&$parents, $x, $y) {
$x = disjoint_set_find($parents, $x);
$y = disjoint_set_find($parents, $y);
if ($x !== $y) {
if (rand(0, 1) == 0) {
$parents[$x] = $y;
} else {
$parents[$y] = $x;
}
}
}
function split_file($file, $chunks, $undo) {
$cpp_name = "$file.cpp";
echo "Processing file $cpp_name".PHP_EOL;
$new_files = array();
foreach (range(0, $chunks - 1) as $n) {
$new_files[] = "$file$n.cpp";
}
$is_generated = (strpos($file, 'td/generate/') === 0);
$cmake_file = $is_generated ? 'td/generate/CMakeLists.txt' : 'CMakeLists.txt';
$cmake = file_get_contents($cmake_file);
$cmake_cpp_name = $cpp_name;
$cmake_new_files = $new_files;
if ($is_generated) {
foreach ($cmake_new_files as &$file_ref) {
$file_ref = str_replace('td/generate/auto/td', '${TD_AUTO_INCLUDE_DIR}', $file_ref);
}
$cmake_cpp_name = str_replace('td/generate/auto/td', '${TD_AUTO_INCLUDE_DIR}', $cmake_cpp_name);
}
if ($undo) {
foreach ($new_files as $file) {
if (file_exists($file)) {
echo "Unlinking ".$file.PHP_EOL;
unlink($file);
}
}
if (strpos($cmake, $cmake_cpp_name) === false) {
$cmake = str_replace(implode(PHP_EOL.' ', $cmake_new_files), $cmake_cpp_name, $cmake);
file_put_contents($cmake_file, $cmake);
}
return;
}
if (strpos($cmake, $cmake_cpp_name) !== false) {
$cmake = str_replace($cmake_cpp_name, implode(PHP_EOL.' ', $cmake_new_files), $cmake);
file_put_contents($cmake_file, $cmake);
}
if (!file_exists($cpp_name)) {
echo "ERROR: skip nonexistent file $cpp_name".PHP_EOL;
return;
}
$lines = file($cpp_name);
$depth = 0;
$target_depth = 1 + $is_generated;
$is_static = false;
$in_define = false;
$in_comment = false;
$current = '';
$common = '';
$functions = array();
$namespace_begin = '';
$namespace_end = '';
foreach ($lines as $line) {
$add_depth = strpos($line, 'namespace ') === 0 ? 1 : (strpos($line, '} // namespace') === 0 ? -1 : 0);
if ($add_depth) {
# namespace begin/end
if ($add_depth > 0) {
$depth += $add_depth;
}
if ($depth <= $target_depth) {
if ($add_depth > 0) {
$namespace_begin .= $line;
} else {
$namespace_end .= $line;
}
}
if ($add_depth < 0) {
$depth += $add_depth;
}
if ($is_static) {
$common .= $current;
} else {
$functions[] = $current;
}
$common .= $line;
$current = '';
$is_static = false;
$in_define = false;
continue;
}
if (strpos($line, '#undef') === 0 && !trim($current)) {
continue;
}
if ($in_comment && strpos($line, '*/') === 0) {
$in_comment = false;
continue;
}
if (strpos($line, '/*') === 0) {
$in_comment = true;
}
if ($in_comment) {
continue;
}
if ($depth !== $target_depth) {
$common .= $line;
continue;
}
if (strpos($line, 'static ') === 0 && $depth === $target_depth) {
$is_static = true;
}
if (!trim($current) && strpos($line, '#define ') === 0) {
$is_static = true;
$in_define = true;
}
$current .= $line;
if ((strpos($line, '}') === 0 || ($in_define && !trim($line)) || preg_match('/^[a-z].*;\s*$/i', $line)) && $depth === $target_depth) {
# block end
if ($is_static) {
$common .= $current;
} else {
$functions[] = $current;
}
$current = '';
$is_static = false;
$in_define = false;
}
}
$current = trim($current);
if (!empty($current)) {
fwrite(STDERR, "ERROR: $current".PHP_EOL);
exit();
}
if (count($functions) < $chunks) {
fwrite(STDERR, "ERROR: file is too small to be split more".PHP_EOL);
return;
}
$deps = array(); // all functions from the same subarray must be in the same file
$parents = array();
foreach ($functions as $i => $f) {
if (preg_match_all('/(?J)create_handler<(?<name>[A-Z][A-Za-z]*)>|'.
'(?<name>[A-Z][A-Za-z]*) (final )?: public (Td::ResultHandler|Request)|'.
'(CREATE_REQUEST|CREATE_NO_ARGS_REQUEST)[(](?<name>[A-Z][A-Za-z]*)|'.
'(?<name>complete_pending_preauthentication_requests)|'.
'(?<name>get_message_history_slice)|'.
'(Up|Down)load(?!ManagerCallback)[a-zA-Z]+C(?<name>allback)|(up|down)load_[a-z_]*_c(?<name>allback)_|'.
'(?<name>lazy_to_json)|'.
'(?<name>LogEvent)[^sA]|'.
'(?<name>parse)[(]|'.
'(?<name>store)[(]/', $f, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$name = $match['name'];
if ($name === 'parse' || $name === 'store') {
if ($is_generated) {
continue;
}
$name = 'LogEvent';
}
$deps[$name][] = $i;
}
}
$parents[$i] = $i;
}
foreach ($deps as $func_ids) {
foreach ($func_ids as $func_id) {
disjoint_set_union($parents, $func_ids[0], $func_id);
}
}
$sets = array();
$set_sizes = array();
foreach ($functions as $i => $f) {
$parent = disjoint_set_find($parents, $i);
if (!isset($sets[$parent])) {
$sets[$parent] = '';
$set_sizes[$parent] = 0;
}
$sets[$parent] .= $f;
$set_sizes[$parent] += strlen($f);
}
arsort($set_sizes);
$files = array_fill(0, $chunks, '');
$file_sizes = array_fill(0, $chunks, 0);
foreach ($set_sizes as $parent => $size) {
$file_id = array_search(min($file_sizes), $file_sizes);
$files[$file_id] .= $sets[$parent];
$file_sizes[$file_id] += $size;
}
foreach ($files as $n => $f) {
$new_content = $common.$namespace_begin.$f.$namespace_end;
$std_methods = array();
preg_match_all('/std::[a-z_0-9]*|td::unique(?!_)/', $new_content, $std_methods);
$std_methods = array_unique($std_methods[0]);
$needed_std_headers = array();
$type_headers = array(
'std::move' => '',
'std::vector' => '',
'std::string' => '',
'std::uint32_t' => '',
'std::int32_t' => '',
'std::int64_t' => '',
'td::unique' => 'algorithm',
'std::count_if' => 'algorithm',
'std::fill' => 'algorithm',
'std::find' => 'algorithm',
'std::is_sorted' => 'algorithm',
'std::lower_bound' => 'algorithm',
'std::max' => 'algorithm',
'std::merge' => 'algorithm',
'std::min' => 'algorithm',
'std::partial_sort' => 'algorithm',
'std::partition' => 'algorithm',
'std::remove' => 'algorithm',
'std::reverse' => 'algorithm',
'std::rotate' => 'algorithm',
'std::sort' => 'algorithm',
'std::stable_sort' => 'algorithm',
'std::upper_bound' => 'algorithm',
'std::abs' => 'cmath',
'std::isfinite' => 'cmath',
'std::function' => 'functional',
'std::greater' => 'functional',
'std::reference_wrapper' => 'functional',
'std::make_move_iterator' => 'iterator',
'std::numeric_limits' => 'limits',
'std::map' => 'map',
'std::multimap' => 'map',
'std::make_shared' => 'memory',
'std::shared_ptr' => 'memory',
'std::multiset' => 'set',
'std::set' => 'set',
'std::get' => 'tuple',
'std::make_tuple' => 'tuple',
'std::tie' => 'tuple',
'std::tuple' => 'tuple',
'std::decay_t' => 'type_traits',
'std::is_same' => 'type_traits',
'std::unordered_map' => 'unordered_map',
'std::unordered_set' => 'unordered_set',
'std::make_pair' => 'utility',
'std::pair' => 'utility',
'std::swap' => 'utility');
foreach ($type_headers as $type => $header) {
if (in_array($type, $std_methods)) {
$std_methods = array_diff($std_methods, array($type));
if ($header && !in_array($header, $needed_std_headers)) {
$needed_std_headers[] = $header;
}
}
}
if (!$std_methods) { // know all needed std headers
$new_content = preg_replace_callback(
'/#include <([a-z_]*)>/',
function ($matches) use ($needed_std_headers) {
if (in_array($matches[1], $needed_std_headers)) {
return $matches[0];
}
return '';
},
$new_content
);
}
$td_methods = array(
'AccentColorId' => 'AccentColorId',
'account_manager[_(-](?![.]get[(][)])|AccountManager[^;>]' => 'AccountManager',
'AffiliateType' => 'AffiliateType',
'alarm_manager[_(-](?![.]get[(][)])|AlarmManager' => 'AlarmManager',
'animations_manager[_(-](?![.]get[(][)])|AnimationsManager[^;>]' => 'AnimationsManager',
'attach_menu_manager[_(-](?![.]get[(][)])|AttachMenuManager[^;>]' => 'AttachMenuManager',
'audios_manager[_(-](?![.]get[(][)])|AudiosManager' => 'AudiosManager',
'auth_manager[_(-](?![.]get[(][)])|AuthManager' => 'AuthManager',
'AutoDownloadSettings|[a-z_]*auto_download_settings' => 'AutoDownloadSettings',
'autosave_manager[_(-](?![.]get[(][)])|AutosaveManager' => 'AutosaveManager',
'BackgroundId' => 'BackgroundId',
'background_manager[_(-](?![.]get[(][)])|BackgroundManager' => 'BackgroundManager',
'BackgroundType' => 'BackgroundType',
'Birthdate' => 'Birthdate',
'boost_manager[_(-](?![.]get[(][)])|BoostManager' => 'BoostManager',
'bot_info_manager[_(-](?![.]get[(][)])|BotInfoManager' => 'BotInfoManager',
'BotMenuButton|[a-z_]*_menu_button' => 'BotMenuButton',
'send_bot_custom_query|answer_bot_custom_query|set_bot_updates_status' => 'BotQueries',
'bot_recommendation_manager[_(-](?![.]get[(][)])|BotRecommendationManager' => 'BotRecommendationManager',
'BotVerification' => 'BotVerification',
'BotVerifierSettings' => 'BotVerifierSettings',
'BusinessAwayMessage' => 'BusinessAwayMessage',
'BusinessBotRights' => 'BusinessBotRights',
'BusinessChatLink' => 'BusinessChatLink',
'BusinessConnectedBot' => 'BusinessConnectedBot',
'BusinessConnectionId' => 'BusinessConnectionId',
'business_connection_manager[_(-](?![.]get[(][)])|BusinessConnectionManager' => 'BusinessConnectionManager',
'BusinessGreetingMessage' => 'BusinessGreetingMessage',
'BusinessInfo|business_info' => 'BusinessInfo',
'BusinessIntro' => 'BusinessIntro',
'business_manager[_(-](?![.]get[(][)])|BusinessManager' => 'BusinessManager',
'BusinessRecipients' => 'BusinessRecipients',
'BusinessWorkHours' => 'BusinessWorkHours',
'callback_queries_manager[_(-](?![.]get[(][)])|CallbackQueriesManager' => 'CallbackQueriesManager',
'CallId' => 'CallId',
'call_manager[_(-](?![.]get[(][)])|CallManager' => 'CallManager',
'ChannelId' => 'ChannelId',
'channel_recommendation_manager[_(-](?![.]get[(][)])|ChannelRecommendationManager' => 'ChannelRecommendationManager',
'ChatId' => 'ChatId',
'chat_manager[_(-](?![.]get[(][)])|ChatManager([^ ;.]| [^*])' => 'ChatManager',
'common_dialog_manager[_(-](?![.]get[(][)])|CommonDialogManager' => 'CommonDialogManager',
'connection_state_manager[_(-](?![.]get[(][)])|ConnectionStateManager' => 'ConnectionStateManager',
'country_info_manager[_(-](?![.]get[(][)])|CountryInfoManager' => 'CountryInfoManager',
'CustomEmojiId' => 'CustomEmojiId',
'device_token_manager[_(-](?![.]get[(][)])|DeviceTokenManager' => 'DeviceTokenManager',
'DialogAction[^M]' => 'DialogAction',
'dialog_action_manager[_(-](?![.]get[(][)])|DialogActionManager' => 'DialogActionManager',
'DialogFilter[^A-Z]' => 'DialogFilter',
'DialogFilterId' => 'DialogFilterId',
'dialog_filter_manager[_(-](?![.]get[(][)])|DialogFilterManager' => 'DialogFilterManager',
'DialogId' => 'DialogId',
'dialog_invite_link_manager[_(-](?![.]get[(][)])|DialogInviteLinkManager' => 'DialogInviteLinkManager',
'DialogListId' => 'DialogListId',
'DialogLocation' => 'DialogLocation',
'dialog_manager[_(-](?![.]get[(][)])|DialogManager' => 'DialogManager',
'DialogParticipantFilter' => 'DialogParticipantFilter',
'dialog_participant_manager[_(-](?![.]get[(][)])|DialogParticipantManager' => 'DialogParticipantManager',
'DialogSource' => 'DialogSource',
'DisallowedGiftsSettings' => 'DisallowedGiftsSettings',
'documents_manager[_(-](?![.]get[(][)])|DocumentsManager' => 'DocumentsManager',
'download_manager[_(-](?![.]get[(][)])|DownloadManager[^C]' => 'DownloadManager',
'DownloadManagerCallback' => 'DownloadManagerCallback',
'EmailVerification' => 'EmailVerification',
'EmojiGroup' => 'EmojiGroup',
'FactCheck' => 'FactCheck',
'file_reference_manager[_(-](?![.]get[(][)])|FileReferenceManager|file_references[)]' => 'FileReferenceManager',
'file_manager[_(-](?![.]get[(][)])|FileManager([^ ;.]| [^*])|update_file[)]' => 'files/FileManager',
'FolderId' => 'FolderId',
'forum_topic_manager[_(-](?![.]get[(][)])|ForumTopicManager' => 'ForumTopicManager',
'game_manager[_(-](?![.]get[(][)])|GameManager' => 'GameManager',
'G[(][)]|Global[^A-Za-z]' => 'Global',
'GlobalPrivacySettings' => 'GlobalPrivacySettings',
'GroupCallId' => 'GroupCallId',
'group_call_manager[_(-](?![.]get[(][)])|GroupCallManager' => 'GroupCallManager',
'hashtag_hints[_(-](?![.]get[(][)])|HashtagHints' => 'HashtagHints',
'inline_message_manager[_(-](?![.]get[(][)])|InlineMessageManager' => 'InlineMessageManager',
'inline_queries_manager[_(-](?![.]get[(][)])|InlineQueriesManager' => 'InlineQueriesManager',
'InputBusinessChatLink' => 'InputBusinessChatLink',
'language_pack_manager[_(-]|LanguagePackManager' => 'LanguagePackManager',
'link_manager[_(-](?![.]get[(][)])|LinkManager' => 'LinkManager',
'LogeventIdWithGeneration|add_log_event|delete_log_event|get_erase_log_event_promise|parse_time|store_time' => 'logevent/LogEventHelper',
'MessageCopyOptions' => 'MessageCopyOptions',
'MessageEffectId' => 'MessageEffectId',
'MessageForwardInfo|LastForwardedMessageInfo|forward_info' => 'MessageForwardInfo',
'MessageFullId' => 'MessageFullId',
'MessageId' => 'MessageId',
'message_import_manager[_(-](?![.]get[(][)])|MessageImportManager' => 'MessageImportManager',
'message_query_manager[_(-](?![.]get[(][)])|MessageQueryManager' => 'MessageQueryManager',
'MessageLinkInfo' => 'MessageLinkInfo',
'MessageQuote' => 'MessageQuote',
'MessageReaction|UnreadMessageReaction|[a-z_]*message[a-z_]*reaction|reload_paid_reaction_privacy|get_chosen_tags' => 'MessageReaction',
'MessageReactor' => 'MessageReactor',
'MessageSearchOffset' => 'MessageSearchOffset',
'[a-z_]*_message_sender' => 'MessageSender',
'messages_manager[_(-](?![.]get[(][)])|MessagesManager' => 'MessagesManager',
'MessageThreadInfo' => 'MessageThreadInfo',
'MessageTtl' => 'MessageTtl',
'MissingInvitee' => 'MissingInvitee',
'notification_manager[_(-](?![.]get[(][)])|NotificationManager|notifications[)]' => 'NotificationManager',
'notification_settings_manager[_(-](?![.]get[(][)])|NotificationSettingsManager' => 'NotificationSettingsManager',
'online_manager[_(-](?![.]get[(][)])|OnlineManager' => 'OnlineManager',
'option_manager[_(-](?![.]get[(][)])|OptionManager' => 'OptionManager',
'PaidReactionType' => 'PaidReactionType',
'password_manager[_(-](?![.]get[(][)])|PasswordManager' => 'PasswordManager',
'people_nearby_manager[_(-](?![.]get[(][)])|PeopleNearbyManager' => 'PeopleNearbyManager',
'phone_number_manager[_(-](?![.]get[(][)])|PhoneNumberManager' => 'PhoneNumberManager',
'PhotoSizeSource' => 'PhotoSizeSource',
'poll_manager[_(-](?![.]get[(][)])|PollManager' => 'PollManager',
'privacy_manager[_(-](?![.]get[(][)])|PrivacyManager' => 'PrivacyManager',
'promo_data_manager[_(-](?![.]get[(][)])|PromoDataManager' => 'PromoDataManager',
'PublicDialogType|get_public_dialog_type' => 'PublicDialogType',
'quick_reply_manager[_(-](?![.]get[(][)])|QuickReplyManager' => 'QuickReplyManager',
'ReactionListType|[a-z_]*_reaction_list_type' => 'ReactionListType',
'reaction_manager[_(-](?![.]get[(][)])|ReactionManager' => 'ReactionManager',
'ReactionNotificationSettings' => 'ReactionNotificationSettings',
'ReactionNotificationsFrom' => 'ReactionNotificationsFrom',
'ReactionType|[a-z_]*_reaction_type' => 'ReactionType',
'ReferralProgramInfo' => 'ReferralProgramInfo',
'referral_program_manager[_(-](?![.]get[(][)])|ReferralProgramManager' => 'ReferralProgramManager',
'ReferralProgramParameters' => 'ReferralProgramParameters',
'RequestActor|RequestOnceActor' => 'RequestActor',
'saved_messages_manager[_(-](?![.]get[(][)])|SavedMessagesManager' => 'SavedMessagesManager',
'ScopeNotificationSettings|[a-z_]*_scope_notification_settings' => 'ScopeNotificationSettings',
'SecretChatActor' => 'SecretChatActor',
'secret_chats_manager[_(-]|SecretChatsManager' => 'SecretChatsManager',
'secure_manager[_(-](?![.]get[(][)])|SecureManager' => 'SecureManager',
'SentEmailCode' => 'SentEmailCode',
'SharedDialog' => 'SharedDialog',
'sponsored_message_manager[_(-](?![.]get[(][)])|SponsoredMessageManager' => 'SponsoredMessageManager',
'StarAmount' => 'StarAmount',
'StarGift[^A-Z]' => 'StarGift',
'StarGiftAttribute' => 'StarGiftAttribute',
'StarGiftId' => 'StarGiftId',
'star_gift_manager[_(-](?![.]get[(][)])|StarGiftManager' => 'StarGiftManager',
'StarGiftSettings' => 'StarGiftSettings',
'star_manager[_(-](?![.]get[(][)])|StarManager' => 'StarManager',
'StarSubscription[^P]' => 'StarSubscription',
'StarSubscriptionPricing' => 'StarSubscriptionPricing',
'state_manager[_(-](?![.]get[(][)])|StateManager' => 'StateManager',
'statistics_manager[_(-](?![.]get[(][)])|StatisticsManager' => 'StatisticsManager',
'StickerSetId' => 'StickerSetId',
'stickers_manager[_(-](?![.]get[(][)])|StickersManager' => 'StickersManager',
'storage_manager[_(-](?![.]get[(][)])|StorageManager' => 'StorageManager',
'StoryId' => 'StoryId',
'StoryListId' => 'StoryListId',
'story_manager[_(-](?![.]get[(][)])|StoryManager' => 'StoryManager',
'SuggestedAction|[a-z_]*_suggested_action' => 'SuggestedAction',
'suggested_action_manager[_(-](?![.]get[(][)])|SuggestedActionManager' => 'SuggestedActionManager',
'SynchronousRequests' => 'SynchronousRequests',
'TargetDialogTypes' => 'TargetDialogTypes',
'td_api' => 'td_api',
'td_db[(][)]|TdDb[^A-Za-z]' => 'TdDb',
'telegram_api' => 'telegram_api',
'terms_of_service_manager[_(-](?![.]get[(][)])|TermsOfServiceManager' => 'TermsOfServiceManager',
'theme_manager[_(-](?![.]get[(][)])|ThemeManager' => 'ThemeManager',
'ThemeSettings' => 'ThemeSettings',
'time_zone_manager[_(-](?![.]get[(][)])|TimeZoneManager' => 'TimeZoneManager',
'TopDialogCategory|get_top_dialog_category' => 'TopDialogCategory',
'top_dialog_manager[_(-](?![.]get[(][)])|TopDialogManager' => 'TopDialogManager',
'translation_manager[_(-](?![.]get[(][)])|TranslationManager' => 'TranslationManager',
'transcription_manager[_(-](?![.]get[(][)])|TranscriptionManager' => 'TranscriptionManager',
'updates_manager[_(-](?![.]get[(][)])|UpdatesManager|get_difference[)]|updateSentMessage|dummyUpdate' => 'UpdatesManager',
'UserId' => 'UserId',
'user_manager[_(-](?![.]get[(][)])|UserManager([^ ;.]| [^*])' => 'UserManager',
'UserStarGift' => 'UserStarGift',
'video_notes_manager[_(-](?![.]get[(][)])|VideoNotesManager' => 'VideoNotesManager',
'videos_manager[_(-](?![.]get[(][)])|VideosManager' => 'VideosManager',
'voice_notes_manager[_(-](?![.]get[(][)])|VoiceNotesManager' => 'VoiceNotesManager',
'web_app_manager[_(-](?![.]get[(][)])|WebAppManager' => 'WebAppManager',
'WebAppOpenParameters' => 'WebAppOpenParameters',
'WebPageId(Hash)?' => 'WebPageId',
'web_pages_manager[_(-](?![.]get[(][)])|WebPagesManager' => 'WebPagesManager');
foreach ($td_methods as $pattern => $header) {
if (strpos($cpp_name, $header) !== false) {
continue;
}
$include_name = '#include "td/telegram/'.$header.'.h"';
if (strpos($new_content, $include_name) !== false && preg_match('/[^a-zA-Z0-9_]('.$pattern.')/', str_replace($include_name, '', $new_content)) === 0) {
$new_content = str_replace($include_name, '', $new_content);
}
}
if (!file_exists($new_files[$n]) || file_get_contents($new_files[$n]) !== $new_content) {
echo "Writing file ".$new_files[$n].PHP_EOL;
file_put_contents($new_files[$n], $new_content);
}
}
}
if (in_array('--help', $argv) || in_array('-h', $argv)) {
echo "Usage: php SplitSource.php [OPTION]...\n".
"Splits some source files to reduce a maximum amount of RAM needed for compiling a single file.\n".
" -u, --undo Undo all source code changes.\n".
" -h, --help Show this help.\n";
exit(2);
}
$undo = in_array('--undo', $argv) || in_array('-u', $argv);
$files = array('td/telegram/ChatManager' => 10,
'td/telegram/MessagesManager' => 50,
'td/telegram/NotificationManager' => 10,
'td/telegram/Requests' => 50,
'td/telegram/StickersManager' => 10,
'td/telegram/StoryManager' => 10,
'td/telegram/UpdatesManager' => 10,
'td/telegram/UserManager' => 10,
'td/generate/auto/td/telegram/td_api' => 10,
'td/generate/auto/td/telegram/td_api_json' => 10,
'td/generate/auto/td/telegram/telegram_api' => 10);
foreach ($files as $file => $chunks) {
split_file($file, $chunks, $undo);
}

View File

@ -1,8 +0,0 @@
include(CMakeFindDependencyMacro)
#TODO: write all external dependencies
if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/TdTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/TdTargets.cmake")
endif()
if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/TdStaticTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/TdStaticTargets.cmake")
endif()

View File

@ -1,93 +0,0 @@
if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.10"))
message(FATAL_ERROR "CMake >= 3.10 is required")
endif()
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
endif()
# TODO: all benchmarks in one file
add_executable(bench_crypto bench_crypto.cpp)
target_link_libraries(bench_crypto PRIVATE tdutils ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
if (WIN32)
if (MINGW)
target_link_libraries(bench_crypto PRIVATE ws2_32 mswsock crypt32)
else()
target_link_libraries(bench_crypto PRIVATE ws2_32 Mswsock Crypt32)
endif()
endif()
target_include_directories(bench_crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
add_executable(bench_actor bench_actor.cpp)
target_link_libraries(bench_actor PRIVATE tdactor tdutils)
add_executable(bench_http bench_http.cpp)
target_link_libraries(bench_http PRIVATE tdnet tdutils)
add_executable(bench_http_server bench_http_server.cpp)
target_link_libraries(bench_http_server PRIVATE tdnet tdutils)
add_executable(bench_http_server_cheat bench_http_server_cheat.cpp)
target_link_libraries(bench_http_server_cheat PRIVATE tdnet tdutils)
add_executable(bench_http_server_fast bench_http_server_fast.cpp)
target_link_libraries(bench_http_server_fast PRIVATE tdnet tdutils)
add_executable(bench_http_reader bench_http_reader.cpp)
target_link_libraries(bench_http_reader PRIVATE tdnet tdutils)
add_executable(bench_handshake bench_handshake.cpp)
target_link_libraries(bench_handshake PRIVATE tdmtproto tdutils)
add_executable(bench_db bench_db.cpp)
target_link_libraries(bench_db PRIVATE tdactor tddb tdutils)
add_executable(bench_tddb bench_tddb.cpp)
target_link_libraries(bench_tddb PRIVATE tdcore tddb tdutils)
add_executable(bench_misc bench_misc.cpp)
target_link_libraries(bench_misc PRIVATE tdcore tdutils)
add_executable(check_proxy check_proxy.cpp)
target_link_libraries(check_proxy PRIVATE tdclient tdutils)
add_executable(check_tls check_tls.cpp)
target_link_libraries(check_tls PRIVATE tdutils)
add_executable(rmdir rmdir.cpp)
target_link_libraries(rmdir PRIVATE tdutils)
add_executable(wget wget.cpp)
target_link_libraries(wget PRIVATE tdnet tdutils)
add_executable(bench_empty bench_empty.cpp)
target_link_libraries(bench_empty PRIVATE tdutils)
if (NOT WIN32 AND NOT CYGWIN)
add_executable(bench_log bench_log.cpp)
target_link_libraries(bench_log PRIVATE tdutils)
set_source_files_properties(bench_queue.cpp PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations)
add_executable(bench_queue bench_queue.cpp)
target_link_libraries(bench_queue PRIVATE tdutils)
endif()
if (TD_TEST_FOLLY AND TD_WITH_ABSEIL)
find_package(ABSL QUIET)
find_package(folly QUIET)
find_package(gflags QUIET)
if (ABSL_FOUND AND folly_FOUND)
add_executable(memory-hashset-memprof EXCLUDE_FROM_ALL hashset_memory.cpp)
target_compile_definitions(memory-hashset-memprof PRIVATE USE_MEMPROF=1)
target_link_libraries(memory-hashset-memprof PRIVATE tdutils memprof_stat Folly::folly absl::flat_hash_map absl::hash)
add_executable(memory-hashset-os hashset_memory.cpp)
target_compile_definitions(memory-hashset-os PRIVATE USE_MEMPROF=0)
target_link_libraries(memory-hashset-os PRIVATE tdutils Folly::folly absl::flat_hash_map absl::hash)
add_executable(hashmap-build hashmap_build.cpp)
target_link_libraries(hashmap-build PRIVATE tdutils Folly::folly absl::flat_hash_map absl::hash)
endif()
endif()

View File

@ -1,349 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/logging.h"
#include "td/utils/Promise.h"
#include "td/utils/SliceBuilder.h"
#if TD_MSVC
#pragma comment(linker, "/STACK:16777216")
#endif
struct TestActor final : public td::Actor {
static td::int32 actor_count_;
void start_up() final {
actor_count_++;
stop();
}
void tear_down() final {
if (--actor_count_ == 0) {
td::Scheduler::instance()->finish();
}
}
};
td::int32 TestActor::actor_count_;
namespace td {
template <>
class ActorTraits<TestActor> {
public:
static constexpr bool need_context = false;
static constexpr bool need_start_up = true;
};
} // namespace td
class CreateActorBench final : public td::Benchmark {
td::unique_ptr<td::ConcurrentScheduler> scheduler_;
void start_up() final {
scheduler_ = td::make_unique<td::ConcurrentScheduler>(0, 0);
scheduler_->start();
}
void tear_down() final {
scheduler_->finish();
scheduler_.reset();
}
public:
td::string get_description() const final {
return "CreateActor";
}
void run(int n) final {
for (int i = 0; i < n; i++) {
scheduler_->create_actor_unsafe<TestActor>(0, "TestActor").release();
}
while (scheduler_->run_main(10)) {
// empty
}
}
};
template <int type>
class RingBench final : public td::Benchmark {
public:
struct PassActor;
private:
int actor_n_ = -1;
int thread_n_ = -1;
td::vector<td::ActorId<PassActor>> actor_array_;
td::unique_ptr<td::ConcurrentScheduler> scheduler_;
public:
td::string get_description() const final {
static const char *types[] = {"later", "immediate", "raw", "tail", "lambda"};
static_assert(0 <= type && type < 5, "");
return PSTRING() << "Ring (send_" << types[type] << ") (threads_n = " << thread_n_ << ")";
}
struct PassActor final : public td::Actor {
int id = -1;
td::ActorId<PassActor> next_actor;
int start_n = 0;
void pass(int n) {
// LOG(INFO) << "Pass: " << n;
if (n == 0) {
td::Scheduler::instance()->finish();
} else {
if (type == 0) {
send_closure_later(next_actor, &PassActor::pass, n - 1);
} else if (type == 1) {
send_closure(next_actor, &PassActor::pass, n - 1);
} else if (type == 2) {
send_event(next_actor, td::Event::raw(static_cast<td::uint32>(n - 1)));
} else if (type == 3) {
if (n % 5000 == 0) {
send_closure_later(next_actor, &PassActor::pass, n - 1);
} else {
// TODO: it is three times faster than send_event
// maybe send event could be further optimized?
next_actor.get_actor_unsafe()->raw_event(td::Event::raw(static_cast<td::uint32>(n - 1)).data);
}
} else if (type == 4) {
send_lambda(next_actor, [n, ptr = next_actor.get_actor_unsafe()] { ptr->pass(n - 1); });
}
}
}
void raw_event(const td::Event::Raw &raw) final {
pass(static_cast<int>(raw.u32));
}
void start_up() final {
yield();
}
void wakeup() final {
if (start_n != 0) {
int n = start_n;
start_n = 0;
pass(n);
}
}
};
RingBench(int actor_n, int thread_n) : actor_n_(actor_n), thread_n_(thread_n) {
}
void start_up() final {
scheduler_ = td::make_unique<td::ConcurrentScheduler>(thread_n_, 0);
actor_array_ = td::vector<td::ActorId<PassActor>>(actor_n_);
for (int i = 0; i < actor_n_; i++) {
actor_array_[i] =
scheduler_->create_actor_unsafe<PassActor>(thread_n_ ? i % thread_n_ : 0, "PassActor").release();
actor_array_[i].get_actor_unsafe()->id = i;
}
for (int i = 0; i < actor_n_; i++) {
actor_array_[i].get_actor_unsafe()->next_actor = actor_array_[(i + 1) % actor_n_];
}
scheduler_->start();
}
void run(int n) final {
// first actor is on main_thread
actor_array_[0].get_actor_unsafe()->start_n = td::max(n, 100);
while (scheduler_->run_main(10)) {
// empty
}
}
void tear_down() final {
scheduler_->finish();
scheduler_.reset();
}
};
template <int type>
class QueryBench final : public td::Benchmark {
public:
td::string get_description() const final {
static const char *types[] = {"callback", "immediate future", "delayed future", "dummy", "lambda", "lambda_future"};
static_assert(0 <= type && type < 6, "");
return PSTRING() << "QueryBench: " << types[type];
}
class ClientActor final : public td::Actor {
public:
class Callback {
public:
Callback() = default;
Callback(const Callback &) = delete;
Callback &operator=(const Callback &) = delete;
Callback(Callback &&) = delete;
Callback &operator=(Callback &&) = delete;
virtual ~Callback() = default;
virtual void on_result(int x) = 0;
};
explicit ClientActor(td::unique_ptr<Callback> callback) : callback_(std::move(callback)) {
}
void f(int x) {
callback_->on_result(x * x);
}
void dummy(int x, int *y) {
*y = x * x;
}
void f_immediate_promise(int x, td::PromiseActor<int> &&promise) {
promise.set_value(x * x);
}
void f_promise(td::Promise<> promise) {
promise.set_value(td::Unit());
}
private:
td::unique_ptr<Callback> callback_;
};
class ServerActor final : public td::Actor {
public:
class ClientCallback final : public ClientActor::Callback {
public:
explicit ClientCallback(td::ActorId<ServerActor> server) : server_(server) {
}
void on_result(int x) final {
send_closure(server_, &ServerActor::on_result, x);
}
private:
td::ActorId<ServerActor> server_;
};
void start_up() final {
client_ = td::create_actor<ClientActor>("Client", td::make_unique<ClientCallback>(actor_id(this))).release();
}
void on_result(int x) {
CHECK(x == n_ * n_);
wakeup();
}
void wakeup() final {
while (true) {
if (n_ < 0) {
td::Scheduler::instance()->finish();
return;
}
n_--;
if (type == 0) {
send_closure(client_, &ClientActor::f, n_);
return;
} else if (type == 1) {
td::PromiseActor<int> promise;
td::FutureActor<int> future;
init_promise_future(&promise, &future);
send_closure(client_, &ClientActor::f_immediate_promise, n_, std::move(promise));
CHECK(!future.is_ready());
CHECK(!future.empty());
CHECK(future.get_state() == td::FutureActor<int>::State::Waiting);
// int val = future.move_as_ok();
// CHECK(val == n_ * n_);
} else if (type == 2) {
td::PromiseActor<int> promise;
init_promise_future(&promise, &future_);
future_.set_event(td::EventCreator::raw(actor_id(), static_cast<td::uint64>(1)));
send_closure(client_, &ClientActor::f_immediate_promise, n_, std::move(promise));
return;
} else if (type == 3) {
int res;
send_closure(client_, &ClientActor::dummy, n_, &res);
} else if (type == 4) {
int val = 0;
send_lambda(client_, [&] { val = n_ * n_; });
CHECK(val == 0 || val == n_ * n_);
} else if (type == 5) {
send_closure(client_, &ClientActor::f_promise,
td::PromiseCreator::lambda([actor_id = actor_id(this), n = n_](td::Unit) {
send_closure(actor_id, &ServerActor::result, n * n);
}));
return;
}
}
}
void run(int n) {
n_ = n;
wakeup();
}
void raw_event(const td::Event::Raw &event) final {
int val = future_.move_as_ok();
CHECK(val == n_ * n_);
wakeup();
}
void result(int val) {
CHECK(val == n_ * n_);
wakeup();
}
private:
td::ActorId<ClientActor> client_;
int n_ = 0;
td::FutureActor<int> future_;
};
void start_up() final {
scheduler_ = td::make_unique<td::ConcurrentScheduler>(0, 0);
server_ = scheduler_->create_actor_unsafe<ServerActor>(0, "Server");
scheduler_->start();
}
void run(int n) final {
// first actor is on main_thread
{
auto guard = scheduler_->get_main_guard();
send_closure(server_, &ServerActor::run, n);
}
while (scheduler_->run_main(10)) {
// empty
}
}
void tear_down() final {
server_.release();
scheduler_->finish();
scheduler_.reset();
}
private:
td::unique_ptr<td::ConcurrentScheduler> scheduler_;
td::ActorOwn<ServerActor> server_;
};
int main() {
td::init_openssl_threads();
bench(CreateActorBench());
bench(RingBench<4>(504, 0));
bench(RingBench<3>(504, 0));
bench(RingBench<0>(504, 0));
bench(RingBench<1>(504, 0));
bench(RingBench<2>(504, 0));
bench(QueryBench<5>());
bench(QueryBench<4>());
bench(QueryBench<3>());
bench(QueryBench<2>());
bench(QueryBench<1>());
bench(QueryBench<0>());
bench(RingBench<3>(504, 0));
bench(RingBench<0>(504, 10));
bench(RingBench<1>(504, 10));
bench(RingBench<2>(504, 10));
bench(RingBench<0>(504, 2));
bench(RingBench<1>(504, 2));
bench(RingBench<2>(504, 2));
}

View File

@ -1,528 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/port/thread.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/UInt.h"
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <cstdlib>
#include <iterator>
#include <random>
#include <string>
#include <vector>
static constexpr std::size_t DATA_SIZE = 8 << 10;
static constexpr std::size_t SHORT_DATA_SIZE = 64;
#if OPENSSL_VERSION_NUMBER <= 0x10100000L
class SHA1Bench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "SHA1 OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
for (int i = 0; i < n; i++) {
unsigned char md[20];
SHA1(data, DATA_SIZE, md);
}
}
};
#endif
class SHA1ShortBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[SHORT_DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "SHA1 [" << SHORT_DATA_SIZE << "B]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
unsigned char md[20];
for (int i = 0; i < n; i++) {
td::sha1(td::Slice(data, SHORT_DATA_SIZE), md);
}
}
};
class SHA256ShortBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[SHORT_DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "SHA256 [" << SHORT_DATA_SIZE << "B]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
unsigned char md[32];
for (int i = 0; i < n; i++) {
td::sha256(td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
}
}
};
class SHA512ShortBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[SHORT_DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "SHA512 [" << SHORT_DATA_SIZE << "B]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
unsigned char md[64];
for (int i = 0; i < n; i++) {
td::sha512(td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 64));
}
}
};
class HmacSha256ShortBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[SHORT_DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "HMAC-SHA256 [" << SHORT_DATA_SIZE << "B]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
unsigned char md[32];
for (int i = 0; i < n; i++) {
td::hmac_sha256(td::Slice(data, td::min(static_cast<std::size_t>(32), SHORT_DATA_SIZE)),
td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
}
}
};
class HmacSha512ShortBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[SHORT_DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "HMAC-SHA512 [" << SHORT_DATA_SIZE << "B]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
unsigned char md[64];
for (int i = 0; i < n; i++) {
td::hmac_sha512(td::Slice(data, td::min(static_cast<std::size_t>(64), SHORT_DATA_SIZE)),
td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 64));
}
}
};
class AesEcbBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt256 iv;
std::string get_description() const final {
return PSTRING() << "AES ECB OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
}
void run(int n) final {
td::AesState state;
state.init(td::as_slice(key), true);
td::MutableSlice data_slice(data, DATA_SIZE);
for (int i = 0; i <= n; i++) {
size_t step = 16;
for (size_t offset = 0; offset + step <= data_slice.size(); offset += step) {
state.encrypt(data_slice.ubegin() + offset, data_slice.ubegin() + offset, static_cast<int>(step));
}
}
}
};
class AesIgeEncryptBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt256 iv;
std::string get_description() const final {
return PSTRING() << "AES IGE OpenSSL encrypt [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
}
void run(int n) final {
td::MutableSlice data_slice(data, DATA_SIZE);
td::AesIgeState state;
state.init(as_slice(key), as_slice(iv), true);
for (int i = 0; i < n; i++) {
state.encrypt(data_slice, data_slice);
}
}
};
class AesIgeDecryptBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt256 iv;
std::string get_description() const final {
return PSTRING() << "AES IGE OpenSSL decrypt [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
}
void run(int n) final {
td::MutableSlice data_slice(data, DATA_SIZE);
td::AesIgeState state;
state.init(as_slice(key), as_slice(iv), false);
for (int i = 0; i < n; i++) {
state.decrypt(data_slice, data_slice);
}
}
};
class AesCtrBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt128 iv;
std::string get_description() const final {
return PSTRING() << "AES CTR OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
}
void run(int n) final {
td::MutableSlice data_slice(data, DATA_SIZE);
td::AesCtrState state;
state.init(as_slice(key), as_slice(iv));
for (int i = 0; i < n; i++) {
state.encrypt(data_slice, data_slice);
}
}
};
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
class AesCtrOpenSSLBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt128 iv;
std::string get_description() const final {
return PSTRING() << "AES CTR RAW OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
}
void run(int n) final {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, key.raw, iv.raw);
td::MutableSlice data_slice(data, DATA_SIZE);
td::AesCtrState state;
state.init(as_slice(key), as_slice(iv));
for (int i = 0; i < n; i++) {
int len = 0;
auto int_size = static_cast<int>(DATA_SIZE);
EVP_EncryptUpdate(ctx, data_slice.ubegin(), &len, data_slice.ubegin(), int_size);
CHECK(len == int_size);
}
EVP_CIPHER_CTX_free(ctx);
}
};
#endif
class AesCbcDecryptBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt128 iv;
std::string get_description() const final {
return PSTRING() << "AES CBC Decrypt OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(as_mutable_slice(key));
td::Random::secure_bytes(as_mutable_slice(iv));
}
void run(int n) final {
td::MutableSlice data_slice(data, DATA_SIZE);
for (int i = 0; i < n; i++) {
td::aes_cbc_decrypt(as_slice(key), as_mutable_slice(iv), data_slice, data_slice);
}
}
};
class AesCbcEncryptBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt128 iv;
std::string get_description() const final {
return PSTRING() << "AES CBC Encrypt OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(as_mutable_slice(key));
td::Random::secure_bytes(as_mutable_slice(iv));
}
void run(int n) final {
td::MutableSlice data_slice(data, DATA_SIZE);
for (int i = 0; i < n; i++) {
td::aes_cbc_encrypt(as_slice(key), as_mutable_slice(iv), data_slice, data_slice);
}
}
};
template <bool use_state>
class AesIgeShortBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[SHORT_DATA_SIZE];
td::UInt256 key;
td::UInt256 iv;
std::string get_description() const final {
return PSTRING() << "AES IGE OpenSSL " << (use_state ? "EVP" : "C ") << "[" << SHORT_DATA_SIZE << "B]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(as_mutable_slice(key));
td::Random::secure_bytes(as_mutable_slice(iv));
}
void run(int n) final {
td::MutableSlice data_slice(data, SHORT_DATA_SIZE);
for (int i = 0; i < n; i++) {
if (use_state) {
td::AesIgeState ige;
ige.init(as_slice(key), as_slice(iv), false);
ige.decrypt(data_slice, data_slice);
} else {
td::aes_ige_decrypt(as_slice(key), as_mutable_slice(iv), data_slice, data_slice);
}
}
}
};
BENCH(Rand, "std_rand") {
int res = 0;
for (int i = 0; i < n; i++) {
res ^= std::rand();
}
td::do_not_optimize_away(res);
}
BENCH(CppRand, "mt19937_rand") {
std::uint_fast32_t res = 0;
std::mt19937 g(123);
for (int i = 0; i < n; i++) {
res ^= g();
}
td::do_not_optimize_away(res);
}
BENCH(TdRand32, "td_rand_fast32") {
td::uint32 res = 0;
for (int i = 0; i < n; i++) {
res ^= td::Random::fast_uint32();
}
td::do_not_optimize_away(res);
}
BENCH(TdRandFast, "td_rand_fast") {
int res = 0;
for (int i = 0; i < n; i++) {
res ^= td::Random::fast(0, RAND_MAX);
}
td::do_not_optimize_away(res);
}
#if !TD_THREAD_UNSUPPORTED
BENCH(SslRand, "ssl_rand_int32") {
std::vector<td::thread> v;
std::atomic<td::uint32> sum{0};
for (int i = 0; i < 3; i++) {
v.emplace_back([&sum, n] {
td::int32 res = 0;
for (int j = 0; j < n; j++) {
res ^= td::Random::secure_int32();
}
sum += res;
});
}
for (auto &x : v) {
x.join();
}
v.clear();
td::do_not_optimize_away(sum.load());
}
#endif
BENCH(SslRandBuf, "ssl_rand_bytes") {
td::int32 res = 0;
std::array<td::int32, 1000> buf;
for (int i = 0; i < n; i += static_cast<int>(buf.size())) {
td::Random::secure_bytes(reinterpret_cast<td::uint8 *>(buf.data()), sizeof(buf[0]) * buf.size());
for (auto x : buf) {
res ^= x;
}
}
td::do_not_optimize_away(res);
}
BENCH(Pbkdf2, "pbkdf2") {
std::string password = "cucumber";
std::string salt = "abcdefghijklmnopqrstuvw";
std::string key(32, ' ');
td::pbkdf2_sha256(password, salt, n, key);
}
class Crc32Bench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "CRC32 zlib [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
td::uint64 res = 0;
for (int i = 0; i < n; i++) {
res += td::crc32(td::Slice(data, DATA_SIZE));
}
td::do_not_optimize_away(res);
}
};
class Crc64Bench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
std::string get_description() const final {
return PSTRING() << "CRC64 Anton [" << (DATA_SIZE >> 10) << "KB]";
}
void start_up() final {
std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
void run(int n) final {
td::uint64 res = 0;
for (int i = 0; i < n; i++) {
res += td::crc64(td::Slice(data, DATA_SIZE));
}
td::do_not_optimize_away(res);
}
};
int main() {
td::init_openssl_threads();
td::bench(AesCtrBench());
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
td::bench(AesCtrOpenSSLBench());
#endif
td::bench(AesCbcDecryptBench());
td::bench(AesCbcEncryptBench());
td::bench(AesIgeShortBench<true>());
td::bench(AesIgeShortBench<false>());
td::bench(AesIgeEncryptBench());
td::bench(AesIgeDecryptBench());
td::bench(AesEcbBench());
td::bench(Pbkdf2Bench());
td::bench(RandBench());
td::bench(CppRandBench());
td::bench(TdRand32Bench());
td::bench(TdRandFastBench());
#if !TD_THREAD_UNSUPPORTED
td::bench(SslRandBench());
#endif
td::bench(SslRandBufBench());
#if OPENSSL_VERSION_NUMBER <= 0x10100000L
td::bench(SHA1Bench());
#endif
td::bench(SHA1ShortBench());
td::bench(SHA256ShortBench());
td::bench(SHA512ShortBench());
td::bench(HmacSha256ShortBench());
td::bench(HmacSha512ShortBench());
td::bench(Crc32Bench());
td::bench(Crc64Bench());
}

View File

@ -1,244 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/db/binlog/Binlog.h"
#include "td/db/binlog/ConcurrentBinlog.h"
#include "td/db/BinlogKeyValue.h"
#include "td/db/DbKey.h"
#include "td/db/SeqKeyValue.h"
#include "td/db/SqliteConnectionSafe.h"
#include "td/db/SqliteDb.h"
#include "td/db/SqliteKeyValueAsync.h"
#include "td/db/SqliteKeyValueSafe.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include <memory>
template <class KeyValueT>
class TdKvBench final : public td::Benchmark {
td::unique_ptr<td::ConcurrentScheduler> scheduler_;
td::string name_;
public:
explicit TdKvBench(td::string name) {
name_ = std::move(name);
}
td::string get_description() const final {
return name_;
}
class Main final : public td::Actor {
public:
explicit Main(int n) : n_(n) {
}
private:
void loop() final {
KeyValueT::destroy("test_tddb").ignore();
class Worker final : public Actor {
public:
Worker(int n, td::string db_name) : n_(n) {
kv_.init(db_name).ensure();
}
private:
void loop() final {
for (int i = 0; i < n_; i++) {
kv_.set(td::to_string(i % 10), td::to_string(i));
}
td::Scheduler::instance()->finish();
}
int n_;
KeyValueT kv_;
};
td::create_actor_on_scheduler<Worker>("Worker", 0, n_, "test_tddb").release();
}
int n_;
};
void start_up_n(int n) final {
scheduler_ = td::make_unique<td::ConcurrentScheduler>(1, 0);
scheduler_->create_actor_unsafe<Main>(1, "Main", n).release();
}
void run(int n) final {
scheduler_->start();
while (scheduler_->run_main(10)) {
// empty
}
scheduler_->finish();
}
void tear_down() final {
scheduler_.reset();
}
};
template <bool is_encrypted = false>
class SqliteKVBench final : public td::Benchmark {
td::SqliteDb db;
td::string get_description() const final {
return PSTRING() << "SqliteKV " << td::tag("is_encrypted", is_encrypted);
}
void start_up() final {
td::string path = "testdb.sqlite";
td::SqliteDb::destroy(path).ignore();
if (is_encrypted) {
db = td::SqliteDb::change_key(path, true, td::DbKey::password("cucumber"), td::DbKey::empty()).move_as_ok();
} else {
db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
}
db.exec("PRAGMA encoding=\"UTF-8\"").ensure();
db.exec("PRAGMA synchronous=NORMAL").ensure();
db.exec("PRAGMA journal_mode=WAL").ensure();
db.exec("PRAGMA temp_store=MEMORY").ensure();
db.exec("DROP TABLE IF EXISTS KV").ensure();
db.exec("CREATE TABLE IF NOT EXISTS KV (k BLOB PRIMARY KEY, v BLOB)").ensure();
}
void run(int n) final {
auto stmt = db.get_statement("REPLACE INTO KV (k, v) VALUES(?1, ?2)").move_as_ok();
db.exec("BEGIN TRANSACTION").ensure();
for (int i = 0; i < n; i++) {
auto key = td::to_string(i % 10);
auto value = td::to_string(i);
stmt.bind_blob(1, key).ensure();
stmt.bind_blob(2, value).ensure();
stmt.step().ensure();
CHECK(!stmt.can_step());
stmt.reset();
if (i % 10 == 0) {
db.exec("COMMIT TRANSACTION").ensure();
db.exec("BEGIN TRANSACTION").ensure();
}
}
db.exec("COMMIT TRANSACTION").ensure();
}
};
static td::Status init_db(td::SqliteDb &db) {
TRY_STATUS(db.exec("PRAGMA encoding=\"UTF-8\""));
TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
TRY_STATUS(db.exec("PRAGMA synchronous=NORMAL"));
TRY_STATUS(db.exec("PRAGMA temp_store=MEMORY"));
// TRY_STATUS(db.exec("PRAGMA secure_delete=1"));
return td::Status::OK();
}
class SqliteKeyValueAsyncBench final : public td::Benchmark {
public:
td::string get_description() const final {
return "SqliteKeyValueAsync";
}
void start_up() final {
do_start_up().ensure();
scheduler_->start();
}
void run(int n) final {
auto guard = scheduler_->get_main_guard();
for (int i = 0; i < n; i++) {
auto key = td::to_string(i % 10);
auto value = td::to_string(i);
sqlite_kv_async_->set(key, value, td::Auto());
}
}
void tear_down() final {
scheduler_->run_main(0.1);
{
auto guard = scheduler_->get_main_guard();
sqlite_kv_async_.reset();
sqlite_kv_safe_.reset();
sql_connection_->close_and_destroy();
}
scheduler_->finish();
scheduler_.reset();
}
private:
td::unique_ptr<td::ConcurrentScheduler> scheduler_;
std::shared_ptr<td::SqliteConnectionSafe> sql_connection_;
std::shared_ptr<td::SqliteKeyValueSafe> sqlite_kv_safe_;
td::unique_ptr<td::SqliteKeyValueAsyncInterface> sqlite_kv_async_;
td::Status do_start_up() {
scheduler_ = td::make_unique<td::ConcurrentScheduler>(1, 0);
auto guard = scheduler_->get_main_guard();
td::string sql_db_name = "testdb.sqlite";
td::SqliteDb::destroy(sql_db_name).ignore();
td::SqliteDb::open_with_key(sql_db_name, true, td::DbKey::empty()).move_as_ok();
sql_connection_ = std::make_shared<td::SqliteConnectionSafe>(sql_db_name, td::DbKey::empty());
auto &db = sql_connection_->get();
TRY_STATUS(init_db(db));
sqlite_kv_safe_ = std::make_shared<td::SqliteKeyValueSafe>("common", sql_connection_);
sqlite_kv_async_ = create_sqlite_key_value_async(sqlite_kv_safe_, 0);
return td::Status::OK();
}
};
class SeqKvBench final : public td::Benchmark {
td::string get_description() const final {
return "SeqKvBench";
}
td::SeqKeyValue kv;
void run(int n) final {
for (int i = 0; i < n; i++) {
kv.set(PSLICE() << i % 10, PSLICE() << i);
}
}
};
template <bool is_encrypted = false>
class BinlogKeyValueBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "BinlogKeyValue " << td::tag("is_encrypted", is_encrypted);
}
td::BinlogKeyValue<td::Binlog> kv;
void start_up() final {
td::SqliteDb::destroy("test_binlog").ignore();
kv.init("test_binlog", is_encrypted ? td::DbKey::password("cucumber") : td::DbKey::empty()).ensure();
}
void run(int n) final {
for (int i = 0; i < n; i++) {
kv.set(td::to_string(i % 10), td::to_string(i));
}
}
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
bench(TdKvBench<td::BinlogKeyValue<td::Binlog>>("BinlogKeyValue<Binlog>"));
bench(TdKvBench<td::BinlogKeyValue<td::ConcurrentBinlog>>("BinlogKeyValue<ConcurrentBinlog>"));
bench(BinlogKeyValueBench<true>());
bench(BinlogKeyValueBench<false>());
bench(SqliteKVBench<false>());
bench(SqliteKVBench<true>());
bench(SqliteKeyValueAsyncBench());
bench(SeqKvBench());
}

View File

@ -1,8 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
int main() {
}

View File

@ -1,72 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/mtproto/DhCallback.h"
#include "td/mtproto/DhHandshake.h"
#include "td/utils/base64.h"
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include <map>
#if TD_LINUX || TD_ANDROID || TD_TIZEN
#include <semaphore.h>
#endif
static td::int32 g = 3;
static td::string prime_base64 =
"xxyuucaxyQSObFIvcPE_c5gNQCOOPiHBSTTQN1Y9kw9IGYoKp8FAWCKUk9IlMPTb-jNvbgrJJROVQ67UTM58NyD9UfaUWHBaxozU_mtrE6vcl0ZRKW"
"kyhFTxj6-MWV9kJHf-lrsqlB1bzR1KyMxJiAcI-ps3jjxPOpBgvuZ8-aSkppWBEFGQfhYnU7VrD2tBDbp02KhLKhSzFE4O8ShHVP0X7ZUNWWW0ud1G"
"WC2xF40WnGvEZbDW_5yjko_vW5rk5Bj8Feg-vqD4f6n_Xu1wBQ3tKEn0e_lZ2VaFDOkphR8NgRX2NbEF7i5OFdBLJFS_b0-t8DSxBAMRnNjjuS_MW"
"w";
class HandshakeBench final : public td::Benchmark {
td::string get_description() const final {
return "Handshake";
}
class FakeDhCallback final : public td::mtproto::DhCallback {
public:
int is_good_prime(td::Slice prime_str) const final {
auto it = cache.find(prime_str.str());
if (it == cache.end()) {
return -1;
}
return it->second;
}
void add_good_prime(td::Slice prime_str) const final {
cache[prime_str.str()] = 1;
}
void add_bad_prime(td::Slice prime_str) const final {
cache[prime_str.str()] = 0;
}
mutable std::map<td::string, int> cache;
} dh_callback;
void run(int n) final {
td::mtproto::DhHandshake a;
td::mtproto::DhHandshake b;
auto prime = td::base64url_decode(prime_base64).move_as_ok();
td::mtproto::DhHandshake::check_config(g, prime, &dh_callback).ensure();
for (int i = 0; i < n; i += 2) {
a.set_config(g, prime);
b.set_config(g, prime);
b.set_g_a(a.get_g_b());
a.set_g_a(b.get_g_b());
a.run_checks(true, &dh_callback).ensure();
b.run_checks(true, &dh_callback).ensure();
auto a_key = a.gen_key();
auto b_key = b.gen_key();
CHECK(a_key.first == b_key.first);
}
}
};
int main() {
td::bench(HandshakeBench());
}

View File

@ -1,77 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpOutboundConnection.h"
#include "td/net/HttpQuery.h"
#include "td/net/SslStream.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/logging.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Status.h"
#include <atomic>
#include <limits>
std::atomic<int> counter;
class HttpClient final : public td::HttpOutboundConnection::Callback {
void start_up() final {
td::IPAddress addr;
addr.init_ipv4_port("127.0.0.1", 8082).ensure();
auto fd = td::SocketFd::open(addr);
LOG_CHECK(fd.is_ok()) << fd.error();
connection_ = td::create_actor<td::HttpOutboundConnection>(
"Connect", td::BufferedFd<td::SocketFd>(fd.move_as_ok()), td::SslStream{}, std::numeric_limits<size_t>::max(),
0, 0, td::ActorOwn<td::HttpOutboundConnection::Callback>(actor_id(this)));
yield();
cnt_ = 100000;
counter++;
}
void tear_down() final {
if (--counter == 0) {
td::Scheduler::instance()->finish();
}
}
void loop() final {
if (cnt_-- < 0) {
return stop();
}
send_closure(connection_, &td::HttpOutboundConnection::write_next, td::BufferSlice("GET / HTTP/1.1\r\n\r\n"));
send_closure(connection_, &td::HttpOutboundConnection::write_ok);
LOG(INFO) << "SEND";
}
void handle(td::unique_ptr<td::HttpQuery> result) final {
loop();
}
void on_connection_error(td::Status error) final {
LOG(ERROR) << "ERROR: " << error;
}
td::ActorOwn<td::HttpOutboundConnection> connection_;
int cnt_ = 0;
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
auto scheduler = td::make_unique<td::ConcurrentScheduler>(0, 0);
scheduler->create_actor_unsafe<HttpClient>(0, "Client1").release();
scheduler->create_actor_unsafe<HttpClient>(0, "Client2").release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
}

View File

@ -1,119 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"
#include "td/utils/benchmark.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/find_boundary.h"
#include "td/utils/logging.h"
static std::string http_query = "GET / HTTP/1.1\r\nConnection:keep-alive\r\nhost:127.0.0.1:8080\r\n\r\n";
static const size_t block_size = 2500;
class HttpReaderBench final : public td::Benchmark {
std::string get_description() const final {
return "HttpReaderBench";
}
void run(int n) final {
auto cnt = static_cast<int>(block_size / http_query.size());
td::HttpQuery q;
int parsed = 0;
int sent = 0;
for (int i = 0; i < n; i += cnt) {
for (int j = 0; j < cnt; j++) {
writer_.append(http_query);
sent++;
}
reader_.sync_with_writer();
while (true) {
auto wait = http_reader_.read_next(&q).ok();
if (wait != 0) {
break;
}
parsed++;
}
}
CHECK(parsed == sent);
}
td::ChainBufferWriter writer_;
td::ChainBufferReader reader_;
td::HttpReader http_reader_;
void start_up() final {
writer_ = {};
reader_ = writer_.extract_reader();
http_reader_.init(&reader_, 10000, 0);
}
};
class BufferBench final : public td::Benchmark {
std::string get_description() const final {
return "BufferBench";
}
void run(int n) final {
auto cnt = static_cast<int>(block_size / http_query.size());
for (int i = 0; i < n; i += cnt) {
for (int j = 0; j < cnt; j++) {
writer_.append(http_query);
}
reader_.sync_with_writer();
for (int j = 0; j < cnt; j++) {
auto result = reader_.cut_head(http_query.size());
}
}
}
td::ChainBufferWriter writer_;
td::ChainBufferReader reader_;
td::HttpReader http_reader_;
void start_up() final {
writer_ = {};
reader_ = writer_.extract_reader();
}
};
class FindBoundaryBench final : public td::Benchmark {
std::string get_description() const final {
return "FindBoundaryBench";
}
void run(int n) final {
auto cnt = static_cast<int>(block_size / http_query.size());
for (int i = 0; i < n; i += cnt) {
for (int j = 0; j < cnt; j++) {
writer_.append(http_query);
}
reader_.sync_with_writer();
for (int j = 0; j < cnt; j++) {
size_t len = 0;
find_boundary(reader_.clone(), "\r\n\r\n", len);
CHECK(size_t(len) + 4 == http_query.size());
auto result = reader_.cut_head(len + 2);
reader_.advance(2);
}
}
}
td::ChainBufferWriter writer_;
td::ChainBufferReader reader_;
td::HttpReader http_reader_;
void start_up() final {
writer_ = {};
reader_ = writer_.extract_reader();
}
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
td::bench(BufferBench());
td::bench(FindBoundaryBench());
td::bench(HttpReaderBench());
}

View File

@ -1,84 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpHeaderCreator.h"
#include "td/net/HttpInboundConnection.h"
#include "td/net/HttpQuery.h"
#include "td/net/TcpListener.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/logging.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
static int cnt = 0;
class HelloWorld final : public td::HttpInboundConnection::Callback {
public:
void handle(td::unique_ptr<td::HttpQuery> query, td::ActorOwn<td::HttpInboundConnection> connection) final {
// LOG(ERROR) << *query;
td::HttpHeaderCreator hc;
td::Slice content = "hello world";
//auto content = td::BufferSlice("hello world");
hc.init_ok();
hc.set_keep_alive();
hc.set_content_size(content.size());
hc.add_header("Server", "TDLib/test");
hc.add_header("Date", "Thu Dec 14 01:41:50 2017");
hc.add_header("Content-Type:", "text/html");
auto res = hc.finish(content);
LOG_IF(FATAL, res.is_error()) << res.error();
send_closure(connection, &td::HttpInboundConnection::write_next, td::BufferSlice(res.ok()));
send_closure(connection.release(), &td::HttpInboundConnection::write_ok);
}
void hangup() final {
LOG(ERROR) << "CLOSE " << cnt--;
stop();
}
};
const int N = 0;
class Server final : public td::TcpListener::Callback {
public:
void start_up() final {
listener_ =
td::create_actor<td::TcpListener>("Listener", 8082, td::ActorOwn<td::TcpListener::Callback>(actor_id(this)));
}
void accept(td::SocketFd fd) final {
LOG(ERROR) << "ACCEPT " << cnt++;
pos_++;
auto scheduler_id = pos_ % (N != 0 ? N : 1) + (N != 0);
td::create_actor_on_scheduler<td::HttpInboundConnection>(
"HttpInboundConnection", scheduler_id, td::BufferedFd<td::SocketFd>(std::move(fd)), 1024 * 1024, 0, 0,
td::create_actor_on_scheduler<HelloWorld>("HelloWorld", scheduler_id))
.release();
}
void hangup() final {
// may be it should be default?..
LOG(ERROR) << "Hanging up..";
stop();
}
private:
td::ActorOwn<td::TcpListener> listener_;
int pos_{0};
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
auto scheduler = td::make_unique<td::ConcurrentScheduler>(N, 0);
scheduler->create_actor_unsafe<Server>(0, "Server").release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
}

View File

@ -1,132 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpHeaderCreator.h"
#include "td/net/TcpListener.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include <array>
static int cnt = 0;
class HelloWorld final : public td::Actor {
public:
explicit HelloWorld(td::SocketFd socket_fd) : socket_fd_(std::move(socket_fd)) {
}
private:
td::SocketFd socket_fd_;
std::array<char, 1024> read_buf;
size_t read_new_lines{0};
td::string hello_;
td::string write_buf_;
size_t write_pos_{0};
void start_up() final {
td::Scheduler::subscribe(socket_fd_.get_poll_info().extract_pollable_fd(this));
td::HttpHeaderCreator hc;
td::Slice content = "hello world";
//auto content = td::BufferSlice("hello world");
hc.init_ok();
hc.set_keep_alive();
hc.set_content_size(content.size());
hc.add_header("Server", "TDLib/test");
hc.add_header("Date", "Thu Dec 14 01:41:50 2017");
hc.add_header("Content-Type:", "text/html");
hello_ = hc.finish(content).ok().str();
}
void loop() final {
auto status = do_loop();
if (status.is_error()) {
td::Scheduler::unsubscribe(socket_fd_.get_poll_info().get_pollable_fd_ref());
stop();
LOG(ERROR) << "CLOSE: " << status;
}
}
td::Status do_loop() {
sync_with_poll(socket_fd_);
TRY_STATUS(read_loop());
TRY_STATUS(write_loop());
if (can_close_local(socket_fd_)) {
return td::Status::Error("CLOSE");
}
return td::Status::OK();
}
td::Status write_loop() {
while (can_write_local(socket_fd_) && write_pos_ < write_buf_.size()) {
TRY_RESULT(written, socket_fd_.write(td::Slice(write_buf_).substr(write_pos_)));
write_pos_ += written;
if (write_pos_ == write_buf_.size()) {
write_pos_ = 0;
write_buf_.clear();
}
}
return td::Status::OK();
}
td::Status read_loop() {
while (can_read_local(socket_fd_)) {
TRY_RESULT(read_size, socket_fd_.read(td::MutableSlice(read_buf.data(), read_buf.size())));
for (size_t i = 0; i < read_size; i++) {
if (read_buf[i] == '\n') {
read_new_lines++;
if (read_new_lines == 2) {
read_new_lines = 0;
write_buf_.append(hello_);
}
}
}
}
return td::Status::OK();
}
};
const int N = 0;
class Server final : public td::TcpListener::Callback {
public:
void start_up() final {
listener_ =
td::create_actor<td::TcpListener>("Listener", 8082, td::ActorOwn<td::TcpListener::Callback>(actor_id(this)));
}
void accept(td::SocketFd fd) final {
LOG(ERROR) << "ACCEPT " << cnt++;
pos_++;
auto scheduler_id = pos_ % (N != 0 ? N : 1) + (N != 0);
td::create_actor_on_scheduler<HelloWorld>("HelloWorld", scheduler_id, std::move(fd)).release();
}
void hangup() final {
// may be it should be default?..
LOG(ERROR) << "Hanging up..";
stop();
}
private:
td::ActorOwn<td::TcpListener> listener_;
int pos_{0};
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
auto scheduler = td::make_unique<td::ConcurrentScheduler>(N, 0);
scheduler->create_actor_unsafe<Server>(0, "Server").release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
}

View File

@ -1,116 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpHeaderCreator.h"
#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"
#include "td/net/TcpListener.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/logging.h"
#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
class HttpEchoConnection final : public td::Actor {
public:
explicit HttpEchoConnection(td::SocketFd fd) : fd_(std::move(fd)) {
}
private:
td::BufferedFd<td::SocketFd> fd_;
td::HttpReader reader_;
td::HttpQuery query_;
void start_up() final {
td::Scheduler::subscribe(fd_.get_poll_info().extract_pollable_fd(this));
reader_.init(&fd_.input_buffer(), 1024 * 1024, 0);
}
void tear_down() final {
td::Scheduler::unsubscribe_before_close(fd_.get_poll_info().get_pollable_fd_ref());
fd_.close();
}
void handle_query() {
query_ = td::HttpQuery();
td::HttpHeaderCreator hc;
td::Slice content = "hello world";
//auto content = td::BufferSlice("hello world");
hc.init_ok();
hc.set_keep_alive();
hc.set_content_size(content.size());
hc.add_header("Server", "TDLib/test");
hc.add_header("Date", "Thu Dec 14 01:41:50 2017");
hc.add_header("Content-Type:", "text/html");
auto res = hc.finish(content);
fd_.output_buffer().append(res.ok());
}
void loop() final {
sync_with_poll(fd_);
auto status = [&] {
TRY_STATUS(loop_read());
TRY_STATUS(loop_write());
return td::Status::OK();
}();
if (status.is_error() || can_close_local(fd_)) {
stop();
}
}
td::Status loop_read() {
TRY_STATUS(fd_.flush_read());
while (true) {
TRY_RESULT(need, reader_.read_next(&query_));
if (need == 0) {
handle_query();
} else {
break;
}
}
return td::Status::OK();
}
td::Status loop_write() {
TRY_STATUS(fd_.flush_write());
return td::Status::OK();
}
};
const int N = 8;
class Server final : public td::TcpListener::Callback {
public:
void start_up() final {
listener_ =
td::create_actor<td::TcpListener>("Listener", 8082, td::ActorOwn<td::TcpListener::Callback>(actor_id(this)));
}
void accept(td::SocketFd fd) final {
pos_++;
auto scheduler_id = pos_ % (N != 0 ? N : 1) + (N != 0);
td::create_actor_on_scheduler<HttpEchoConnection>("HttpEchoConnection", scheduler_id, std::move(fd)).release();
}
void hangup() final {
LOG(ERROR) << "Hanging up..";
stop();
}
private:
td::ActorOwn<td::TcpListener> listener_;
int pos_{0};
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
auto scheduler = td::make_unique<td::ConcurrentScheduler>(N, 0);
scheduler->create_actor_unsafe<Server>(0, "Server").release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
}

View File

@ -1,160 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include <cstdio>
#include <fstream>
#include <iostream>
#include <ostream>
#include <streambuf>
#include <string>
#include <unistd.h>
std::string create_tmp_file() {
#if TD_ANDROID
std::string name = "/data/local/tmp/large_file.txt";
unlink(name.c_str());
return name;
#else
char file_name[] = "largefileXXXXXX";
int fd = mkstemp(file_name);
if (fd == -1) {
perror("Can't cretate temporary file");
}
CHECK(fd != -1);
close(fd);
return file_name;
#endif
}
class IostreamWriteBench final : public td::Benchmark {
protected:
std::string file_name_;
std::ofstream stream;
static constexpr std::size_t BUFFER_SIZE = 1 << 20;
char buffer[BUFFER_SIZE];
public:
std::string get_description() const final {
return "ostream (to file, no buf, no flush)";
}
void start_up() final {
file_name_ = create_tmp_file();
stream.open(file_name_.c_str());
CHECK(stream.is_open());
// stream.rdbuf()->pubsetbuf(buffer, BUFFER_SIZE);
}
void run(int n) final {
for (int i = 0; i < n; i++) {
stream << "This is just for test" << 987654321 << '\n';
}
}
void tear_down() final {
stream.close();
unlink(file_name_.c_str());
}
};
class FILEWriteBench final : public td::Benchmark {
protected:
std::string file_name_;
FILE *file;
static constexpr std::size_t BUFFER_SIZE = 1 << 20;
char buffer[BUFFER_SIZE];
public:
std::string get_description() const final {
return "std::fprintf (to file, no buf, no flush)";
}
void start_up() final {
file_name_ = create_tmp_file();
file = fopen(file_name_.c_str(), "w");
// setvbuf(file, buffer, _IOFBF, BUFFER_SIZE);
}
void run(int n) final {
for (int i = 0; i < n; i++) {
std::fprintf(file, "This is just for test%d\n", 987654321);
// std::fflush(file);
}
}
void tear_down() final {
std::fclose(file);
unlink(file_name_.c_str());
}
};
#if TD_ANDROID
#include <android/log.h>
#define ALOG(...) __android_log_print(ANDROID_LOG_VERBOSE, "XXX", __VA_ARGS__)
class ALogWriteBench final : public td::Benchmark {
public:
std::string get_description() const final {
return "android_log";
}
void start_up() final {
}
void run(int n) final {
for (int i = 0; i < n; i++) {
ALOG("This is just for test%d\n", 987654321);
}
}
void tear_down() final {
}
};
#endif
class LogWriteBench final : public td::Benchmark {
protected:
std::string file_name_;
std::ofstream stream;
std::streambuf *old_buf;
static constexpr std::size_t BUFFER_SIZE = 1 << 20;
char buffer[BUFFER_SIZE];
public:
std::string get_description() const final {
return "td_log (slow in debug mode)";
}
void start_up() final {
file_name_ = create_tmp_file();
stream.open(file_name_.c_str());
CHECK(stream.is_open());
old_buf = std::cerr.rdbuf(stream.rdbuf());
}
void run(int n) final {
for (int i = 0; i < n; i++) {
LOG(DEBUG) << "This is just for test" << 987654321;
}
}
void tear_down() final {
stream.close();
unlink(file_name_.c_str());
std::cerr.rdbuf(old_buf);
}
};
int main() {
td::bench(LogWriteBench());
#if TD_ANDROID
td::bench(ALogWriteBench());
#endif
td::bench(IostreamWriteBench());
td::bench(FILEWriteBench());
}

View File

@ -1,841 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/telegram_api.hpp"
#include "td/utils/algorithm.h"
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/port/Clocks.h"
#include "td/utils/port/EventFd.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
#include "td/utils/port/RwMutex.h"
#include "td/utils/port/Stat.h"
#include "td/utils/port/thread.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/StackAllocator.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/ThreadSafeCounter.h"
#if !TD_WINDOWS
#include <unistd.h>
#include <utime.h>
#endif
#if TD_LINUX || TD_ANDROID || TD_TIZEN
#include <semaphore.h>
#endif
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <set>
class F {
td::uint32 &sum;
public:
explicit F(td::uint32 &sum) : sum(sum) {
}
template <class T>
void operator()(const T &x) const {
sum += static_cast<td::uint32>(reinterpret_cast<std::uintptr_t>(&x));
}
};
BENCH(TlCall, "TL Call") {
td::tl_object_ptr<td::telegram_api::Function> x = td::make_tl_object<td::telegram_api::account_getWallPapers>(0);
td::uint32 res = 0;
F f(res);
for (int i = 0; i < n; i++) {
downcast_call(*x, f);
}
td::do_not_optimize_away(res);
}
static td::td_api::object_ptr<td::td_api::file> get_file_object() {
return td::td_api::make_object<td::td_api::file>(
12345, 123456, 123456,
td::td_api::make_object<td::td_api::localFile>(
"/android/data/0/data/org.telegram.data/files/photos/12345678901234567890_123.jpg", true, true, false, true,
0, 123456, 123456),
td::td_api::make_object<td::td_api::remoteFile>("abacabadabacabaeabacabadabacabafabacabadabacabaeabacabadabacaba",
"abacabadabacabaeabacabadabacaba", false, true, 123456));
}
BENCH(ToStringIntSmall, "to_string<int> small") {
auto buf = td::StackAllocator::alloc(1000);
td::StringBuilder sb(buf.as_slice());
for (int i = 0; i < n; i++) {
sb << td::Random::fast(0, 100);
sb.clear();
}
}
BENCH(ToStringIntBig, "to_string<int> big") {
auto buf = td::StackAllocator::alloc(1000);
td::StringBuilder sb(buf.as_slice());
for (int i = 0; i < n; i++) {
sb << 1234567890;
sb.clear();
}
}
BENCH(TlToStringUpdateFile, "TL to_string updateFile") {
auto x = td::td_api::make_object<td::td_api::updateFile>(get_file_object());
std::size_t res = 0;
for (int i = 0; i < n; i++) {
res += to_string(x).size();
}
td::do_not_optimize_away(res);
}
BENCH(TlToStringMessage, "TL to_string message") {
auto x = td::td_api::make_object<td::td_api::message>();
x->id_ = 123456000111;
x->sender_id_ = td::td_api::make_object<td::td_api::messageSenderUser>(123456000112);
x->chat_id_ = 123456000112;
x->sending_state_ = td::td_api::make_object<td::td_api::messageSendingStatePending>(0);
x->date_ = 1699999999;
auto photo = td::td_api::make_object<td::td_api::photo>();
for (int i = 0; i < 4; i++) {
photo->sizes_.push_back(td::td_api::make_object<td::td_api::photoSize>(
"a", get_file_object(), 160, 160,
td::vector<td::int32>{10000, 20000, 30000, 50000, 70000, 90000, 120000, 150000, 180000, 220000}));
}
x->content_ = td::td_api::make_object<td::td_api::messagePhoto>(
std::move(photo), td::td_api::make_object<td::td_api::formattedText>(), false, false, false);
std::size_t res = 0;
for (int i = 0; i < n; i++) {
res += to_string(x).size();
}
td::do_not_optimize_away(res);
}
#if !TD_EVENTFD_UNSUPPORTED
BENCH(EventFd, "EventFd") {
td::EventFd fd;
fd.init();
for (int i = 0; i < n; i++) {
fd.release();
fd.acquire();
}
fd.close();
}
#endif
BENCH(NewInt, "new int + delete") {
std::uintptr_t res = 0;
for (int i = 0; i < n; i++) {
int *x = new int;
res += reinterpret_cast<std::uintptr_t>(x);
delete x;
}
td::do_not_optimize_away(res);
}
BENCH(NewObj, "new struct, then delete") {
struct A {
td::int32 a = 0;
td::int32 b = 0;
td::int32 c = 0;
td::int32 d = 0;
};
std::uintptr_t res = 0;
A **ptr = new A *[n];
for (int i = 0; i < n; i++) {
ptr[i] = new A();
res += reinterpret_cast<std::uintptr_t>(ptr[i]);
}
for (int i = 0; i < n; i++) {
delete ptr[i];
}
delete[] ptr;
td::do_not_optimize_away(res);
}
#if !TD_THREAD_UNSUPPORTED
BENCH(ThreadNew, "new struct, then delete in 2 threads") {
NewObjBench a;
NewObjBench b;
td::thread ta([&] { a.run(n / 2); });
td::thread tb([&] { b.run(n - n / 2); });
ta.join();
tb.join();
}
#endif
/*
// Too hard for clang (?)
BENCH(Time, "Clocks::monotonic") {
double res = 0;
for (int i = 0; i < n; i++) {
res += td::Clocks::monotonic();
}
td::do_not_optimize_away(res);
}
*/
#if !TD_WINDOWS
class PipeBench final : public td::Benchmark {
public:
int p[2];
td::string get_description() const final {
return "pipe write + read int32";
}
void start_up() final {
int res = pipe(p);
CHECK(res == 0);
}
void run(int n) final {
int res = 0;
for (int i = 0; i < n; i++) {
int val = 1;
auto write_len = write(p[1], &val, sizeof(val));
CHECK(write_len == sizeof(val));
auto read_len = read(p[0], &val, sizeof(val));
CHECK(read_len == sizeof(val));
res += val;
}
td::do_not_optimize_away(res);
}
void tear_down() final {
close(p[0]);
close(p[1]);
}
};
#endif
#if TD_LINUX || TD_ANDROID || TD_TIZEN
class SemBench final : public td::Benchmark {
sem_t sem;
public:
td::string get_description() const final {
return "sem post + wait";
}
void start_up() final {
int err = sem_init(&sem, 0, 0);
CHECK(err != -1);
}
void run(int n) final {
for (int i = 0; i < n; i++) {
sem_post(&sem);
sem_wait(&sem);
}
}
void tear_down() final {
sem_destroy(&sem);
}
};
#endif
#if !TD_WINDOWS
class UtimeBench final : public td::Benchmark {
public:
void start_up() final {
td::FileFd::open("test", td::FileFd::Create | td::FileFd::Write).move_as_ok().close();
}
td::string get_description() const final {
return "utime";
}
void run(int n) final {
for (int i = 0; i < n; i++) {
int err = utime("test", nullptr);
CHECK(err >= 0);
utimbuf buf;
buf.modtime = 123;
buf.actime = 321;
err = utime("test", &buf);
CHECK(err >= 0);
}
}
};
#endif
BENCH(Pwrite, "pwrite") {
auto fd = td::FileFd::open("test", td::FileFd::Create | td::FileFd::Write).move_as_ok();
for (int i = 0; i < n; i++) {
fd.pwrite("a", 0).ok();
}
fd.close();
}
class CreateFileBench final : public td::Benchmark {
td::string get_description() const final {
return "create_file";
}
void start_up() final {
td::mkdir("A").ensure();
}
void run(int n) final {
for (int i = 0; i < n; i++) {
td::FileFd::open(PSLICE() << "A/" << i, td::FileFd::Write | td::FileFd::Create).move_as_ok().close();
}
}
void tear_down() final {
td::rmrf("A/").ignore();
}
};
class WalkPathBench final : public td::Benchmark {
td::string get_description() const final {
return "walk_path";
}
void start_up_n(int n) final {
td::mkdir("A").ensure();
for (int i = 0; i < n; i++) {
td::FileFd::open(PSLICE() << "A/" << i, td::FileFd::Write | td::FileFd::Create).move_as_ok().close();
}
}
void run(int n) final {
int cnt = 0;
td::walk_path("A/", [&](td::CSlice path, auto type) {
if (type == td::WalkPath::Type::EnterDir) {
return;
}
td::stat(path).ok();
cnt++;
}).ignore();
}
void tear_down() final {
td::rmrf("A/").ignore();
}
};
#if !TD_THREAD_UNSUPPORTED
template <int ThreadN = 2>
class AtomicReleaseIncBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "AtomicReleaseInc" << ThreadN;
}
static std::atomic<td::uint64> a_;
void run(int n) final {
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
a_.fetch_add(1, std::memory_order_release);
}
});
}
for (auto &thread : threads) {
thread.join();
}
}
};
template <int ThreadN>
std::atomic<td::uint64> AtomicReleaseIncBench<ThreadN>::a_;
template <int ThreadN = 2>
class AtomicReleaseCasIncBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "AtomicReleaseCasInc" << ThreadN;
}
static std::atomic<td::uint64> a_;
void run(int n) final {
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
auto value = a_.load(std::memory_order_relaxed);
while (!a_.compare_exchange_strong(value, value + 1, std::memory_order_release, std::memory_order_relaxed)) {
}
}
});
}
for (auto &thread : threads) {
thread.join();
}
}
};
template <int ThreadN>
std::atomic<td::uint64> AtomicReleaseCasIncBench<ThreadN>::a_;
template <int ThreadN>
class RwMutexReadBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "RwMutexRead" << ThreadN;
}
td::RwMutex mutex_;
void run(int n) final {
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
auto lock = mutex_.lock_read();
}
});
}
for (auto &thread : threads) {
thread.join();
}
}
};
template <int ThreadN>
class RwMutexWriteBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "RwMutexWrite" << ThreadN;
}
td::RwMutex mutex_;
void run(int n) final {
td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
auto lock = mutex_.lock_write();
}
});
}
for (auto &thread : threads) {
thread.join();
}
}
};
class ThreadSafeCounterBench final : public td::Benchmark {
static td::ThreadSafeCounter counter_;
int thread_count_;
td::string get_description() const final {
return PSTRING() << "ThreadSafeCounter" << thread_count_;
}
void run(int n) final {
counter_.clear();
td::vector<td::thread> threads;
for (int i = 0; i < thread_count_; i++) {
threads.emplace_back([n] {
for (int i = 0; i < n; i++) {
counter_.add(1);
}
});
}
for (auto &thread : threads) {
thread.join();
}
CHECK(counter_.sum() == n * thread_count_);
}
public:
explicit ThreadSafeCounterBench(int thread_count) : thread_count_(thread_count) {
}
};
td::ThreadSafeCounter ThreadSafeCounterBench::counter_;
template <bool StrictOrder>
class AtomicCounterBench final : public td::Benchmark {
static std::atomic<td::int64> counter_;
int thread_count_;
td::string get_description() const final {
return PSTRING() << "AtomicCounter" << thread_count_;
}
void run(int n) final {
counter_.store(0);
td::vector<td::thread> threads;
for (int i = 0; i < thread_count_; i++) {
threads.emplace_back([n] {
for (int i = 0; i < n; i++) {
counter_.fetch_add(1, StrictOrder ? std::memory_order_seq_cst : std::memory_order_relaxed);
}
});
}
for (auto &thread : threads) {
thread.join();
}
CHECK(counter_.load() == n * thread_count_);
}
public:
explicit AtomicCounterBench(int thread_count) : thread_count_(thread_count) {
}
};
template <bool StrictOrder>
std::atomic<td::int64> AtomicCounterBench<StrictOrder>::counter_;
#endif
class IdDuplicateCheckerOld {
public:
static td::string get_description() {
return "Old";
}
td::Status check(td::uint64 message_id) {
if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS) {
auto oldest_message_id = *saved_message_ids_.begin();
if (message_id < oldest_message_id) {
return td::Status::Error(2, PSLICE() << "Ignore very old message " << message_id
<< " older than the oldest known message " << oldest_message_id);
}
}
if (saved_message_ids_.count(message_id) != 0) {
return td::Status::Error(1, PSLICE() << "Ignore already processed message " << message_id);
}
saved_message_ids_.insert(message_id);
if (saved_message_ids_.size() > MAX_SAVED_MESSAGE_IDS) {
saved_message_ids_.erase(saved_message_ids_.begin());
}
return td::Status::OK();
}
private:
static constexpr size_t MAX_SAVED_MESSAGE_IDS = 1000;
std::set<td::uint64> saved_message_ids_;
};
template <size_t MAX_SAVED_MESSAGE_IDS>
class IdDuplicateCheckerNew {
public:
static td::string get_description() {
return PSTRING() << "New" << MAX_SAVED_MESSAGE_IDS;
}
td::Status check(td::uint64 message_id) {
auto insert_result = saved_message_ids_.insert(message_id);
if (!insert_result.second) {
return td::Status::Error(1, PSLICE() << "Ignore already processed message " << message_id);
}
if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS + 1) {
auto begin_it = saved_message_ids_.begin();
bool is_very_old = begin_it == insert_result.first;
saved_message_ids_.erase(begin_it);
if (is_very_old) {
return td::Status::Error(2, PSLICE() << "Ignore very old message " << message_id
<< " older than the oldest known message " << *saved_message_ids_.begin());
}
}
return td::Status::OK();
}
private:
std::set<td::uint64> saved_message_ids_;
};
class IdDuplicateCheckerNewOther {
public:
static td::string get_description() {
return "NewOther";
}
td::Status check(td::uint64 message_id) {
if (!saved_message_ids_.insert(message_id).second) {
return td::Status::Error(1, PSLICE() << "Ignore already processed message " << message_id);
}
if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS + 1) {
auto begin_it = saved_message_ids_.begin();
bool is_very_old = *begin_it == message_id;
saved_message_ids_.erase(begin_it);
if (is_very_old) {
return td::Status::Error(2, PSLICE() << "Ignore very old message " << message_id
<< " older than the oldest known message " << *saved_message_ids_.begin());
}
}
return td::Status::OK();
}
private:
static constexpr size_t MAX_SAVED_MESSAGE_IDS = 1000;
std::set<td::uint64> saved_message_ids_;
};
class IdDuplicateCheckerNewSimple {
public:
static td::string get_description() {
return "NewSimple";
}
td::Status check(td::uint64 message_id) {
auto insert_result = saved_message_ids_.insert(message_id);
if (!insert_result.second) {
return td::Status::Error(1, "Ignore already processed message");
}
if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS + 1) {
auto begin_it = saved_message_ids_.begin();
bool is_very_old = begin_it == insert_result.first;
saved_message_ids_.erase(begin_it);
if (is_very_old) {
return td::Status::Error(2, "Ignore very old message");
}
}
return td::Status::OK();
}
private:
static constexpr size_t MAX_SAVED_MESSAGE_IDS = 1000;
std::set<td::uint64> saved_message_ids_;
};
template <size_t max_size>
class IdDuplicateCheckerArray {
public:
static td::string get_description() {
return PSTRING() << "Array" << max_size;
}
td::Status check(td::uint64 message_id) {
if (end_pos_ == 2 * max_size) {
std::copy_n(&saved_message_ids_[max_size], max_size, &saved_message_ids_[0]);
end_pos_ = max_size;
}
if (end_pos_ == 0 || message_id > saved_message_ids_[end_pos_ - 1]) {
// fast path
saved_message_ids_[end_pos_++] = message_id;
return td::Status::OK();
}
if (end_pos_ >= max_size && message_id < saved_message_ids_[0]) {
return td::Status::Error(2, PSLICE() << "Ignore very old message " << message_id
<< " older than the oldest known message " << saved_message_ids_[0]);
}
auto it = std::lower_bound(&saved_message_ids_[0], &saved_message_ids_[end_pos_], message_id);
if (*it == message_id) {
return td::Status::Error(1, PSLICE() << "Ignore already processed message " << message_id);
}
std::copy_backward(it, &saved_message_ids_[end_pos_], &saved_message_ids_[end_pos_ + 1]);
*it = message_id;
++end_pos_;
return td::Status::OK();
}
private:
std::array<td::uint64, 2 * max_size> saved_message_ids_;
std::size_t end_pos_ = 0;
};
template <class T>
class DuplicateCheckerBench final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "DuplicateCheckerBench" << T::get_description();
}
void run(int n) final {
T checker_;
for (int i = 0; i < n; i++) {
checker_.check(i).ensure();
}
}
};
template <class T>
class DuplicateCheckerBenchRepeat final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "DuplicateCheckerBenchRepeat" << T::get_description();
}
void run(int n) final {
T checker_;
for (int i = 0; i < n; i++) {
auto iter = i >> 10;
auto pos = i - (iter << 10);
if (pos < 768) {
if (iter >= 3 && pos == 0) {
auto error = checker_.check((iter - 3) * 768 + pos);
CHECK(error.error().code() == 2);
}
checker_.check(iter * 768 + pos).ensure();
} else {
checker_.check(iter * 768 + pos - 256).ensure_error();
}
}
}
};
template <class T>
class DuplicateCheckerBenchRepeatOnly final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "DuplicateCheckerBenchRepeatOnly" << T::get_description();
}
void run(int n) final {
T checker_;
for (int i = 0; i < n; i++) {
auto result = checker_.check(i & 255);
CHECK(result.is_error() == (i >= 256));
}
}
};
template <class T>
class DuplicateCheckerBenchReverse final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "DuplicateCheckerBenchReverseAdd" << T::get_description();
}
void run(int n) final {
T checker_;
for (int i = 0; i < n; i++) {
auto pos = i & 255;
checker_.check(i - pos + (255 - pos)).ensure();
}
}
};
template <class T>
class DuplicateCheckerBenchEvenOdd final : public td::Benchmark {
td::string get_description() const final {
return PSTRING() << "DuplicateCheckerBenchEvenOdd" << T::get_description();
}
void run(int n) final {
T checker_;
for (int i = 0; i < n; i++) {
auto pos = i & 255;
checker_.check(i - pos + (pos * 2) % 256 + (pos * 2) / 256).ensure();
}
}
};
BENCH(AddToTopStd, "add_to_top std") {
td::vector<int> v;
for (int i = 0; i < n; i++) {
for (size_t j = 0; j < 10; j++) {
auto value = td::Random::fast(0, 9);
auto it = std::find(v.begin(), v.end(), value);
if (it == v.end()) {
if (v.size() == 8) {
v.back() = value;
} else {
v.push_back(value);
}
it = v.end() - 1;
}
std::rotate(v.begin(), it, it + 1);
}
}
}
BENCH(AddToTopTd, "add_to_top td") {
td::vector<int> v;
for (int i = 0; i < n; i++) {
for (size_t j = 0; j < 10; j++) {
td::add_to_top(v, 8, td::Random::fast(0, 9));
}
}
}
BENCH(AnyOfStd, "any_of std") {
td::vector<int> v;
for (int i = 0; i < 100; i++) {
v.push_back(i);
}
int res = 0;
for (int i = 0; i < n; i++) {
int rem = td::Random::fast(0, 127);
res += static_cast<int>(std::any_of(v.begin(), v.end(), [rem](int x) { return (x & 127) == rem; }));
}
td::do_not_optimize_away(res);
}
BENCH(AnyOfTd, "any_of td") {
td::vector<int> v;
for (int i = 0; i < 100; i++) {
v.push_back(i);
}
int res = 0;
for (int i = 0; i < n; i++) {
int rem = td::Random::fast(0, 127);
res += static_cast<int>(td::any_of(v, [rem](int x) { return (x & 127) == rem; }));
}
td::do_not_optimize_away(res);
}
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
td::bench(AnyOfStdBench());
td::bench(AnyOfTdBench());
td::bench(ToStringIntSmallBench());
td::bench(ToStringIntBigBench());
td::bench(AddToTopStdBench());
td::bench(AddToTopTdBench());
td::bench(TlToStringUpdateFileBench());
td::bench(TlToStringMessageBench());
td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerNew<1000>>());
td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerNew<300>>());
td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerArray<1000>>());
td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerArray<300>>());
td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerNew<1000>>());
td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerNew<300>>());
td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerArray<1000>>());
td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerArray<300>>());
td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerNew<1000>>());
td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerNew<300>>());
td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerArray<1000>>());
td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerArray<300>>());
td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerOld>());
td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNew<1000>>());
td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNewOther>());
td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNewSimple>());
td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNew<300>>());
td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerArray<1000>>());
td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerArray<300>>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerOld>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<1000>>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerNewOther>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerNewSimple>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<300>>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<100>>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<10>>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerArray<1000>>());
td::bench(DuplicateCheckerBench<IdDuplicateCheckerArray<300>>());
#if !TD_THREAD_UNSUPPORTED
for (int i = 1; i <= 16; i *= 2) {
td::bench(ThreadSafeCounterBench(i));
td::bench(AtomicCounterBench<false>(i));
td::bench(AtomicCounterBench<true>(i));
}
td::bench(AtomicReleaseIncBench<1>());
td::bench(AtomicReleaseIncBench<2>());
td::bench(AtomicReleaseCasIncBench<1>());
td::bench(AtomicReleaseCasIncBench<2>());
td::bench(RwMutexWriteBench<1>());
td::bench(RwMutexReadBench<1>());
td::bench(RwMutexWriteBench<2>());
td::bench(RwMutexReadBench<2>());
#endif
#if !TD_WINDOWS
td::bench(UtimeBench());
#endif
td::bench(WalkPathBench());
td::bench(CreateFileBench());
td::bench(PwriteBench());
td::bench(TlCallBench());
#if !TD_THREAD_UNSUPPORTED
td::bench(ThreadNewBench());
#endif
#if !TD_EVENTFD_UNSUPPORTED
td::bench(EventFdBench());
#endif
td::bench(NewObjBench());
td::bench(NewIntBench());
#if !TD_WINDOWS
td::bench(PipeBench());
#endif
#if TD_LINUX || TD_ANDROID || TD_TIZEN
td::bench(SemBench());
#endif
}

View File

@ -1,932 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/MpscPollableQueue.h"
#include "td/utils/port/sleep.h"
#include "td/utils/port/thread.h"
#include "td/utils/queue.h"
#include "td/utils/Random.h"
// TODO: check system calls
// TODO: all return values must be checked
#include <atomic>
#if TD_PORT_POSIX
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <sys/syscall.h>
#include <unistd.h>
#endif
#if TD_LINUX
#include <sys/eventfd.h>
#endif
#define MODE std::memory_order_relaxed
// void set_affinity(int mask) {
// pid_t pid = gettid();
// int syscallres = syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
// if (syscallres) {
// perror("Failed to set affinity");
// }
// }
using qvalue_t = int;
class Backoff {
int cnt = 0;
public:
bool next() {
cnt++;
if (cnt < 50) {
return true;
} else {
td::usleep_for(1);
return cnt < 500;
}
}
};
#if TD_PORT_POSIX
// Just for testing, not production
class PipeQueue {
int input;
int output;
public:
void init() {
int new_pipe[2];
int res = pipe(new_pipe);
CHECK(res == 0);
output = new_pipe[0];
input = new_pipe[1];
}
void put(qvalue_t value) {
auto len = write(input, &value, sizeof(value));
CHECK(len == sizeof(value));
}
qvalue_t get() {
qvalue_t res;
auto len = read(output, &res, sizeof(res));
CHECK(len == sizeof(res));
return res;
}
void destroy() {
close(input);
close(output);
}
};
class VarQueue {
std::atomic<qvalue_t> data{0};
public:
void init() {
data.store(-1, MODE);
}
void put(qvalue_t value) {
data.store(value, MODE);
}
qvalue_t try_get() {
__sync_synchronize(); // TODO: it is wrong place for barrier, but it results in fastest queue
qvalue_t res = data.load(MODE);
return res;
}
void acquire() {
data.store(-1, MODE);
}
qvalue_t get() {
qvalue_t res;
Backoff backoff;
do {
res = try_get();
} while (res == -1 && (backoff.next(), true));
acquire();
return res;
}
void destroy() {
}
};
class SemQueue {
sem_t sem;
VarQueue q;
public:
void init() {
q.init();
sem_init(&sem, 0, 0);
}
void put(qvalue_t value) {
q.put(value);
sem_post(&sem);
}
qvalue_t get() {
sem_wait(&sem);
qvalue_t res = q.get();
return res;
}
void destroy() {
q.destroy();
sem_destroy(&sem);
}
// HACK for benchmark
void reader_flush() {
}
void writer_flush() {
}
void writer_put(qvalue_t value) {
put(value);
}
int reader_wait() {
return 1;
}
qvalue_t reader_get_unsafe() {
return get();
}
};
#endif
#if TD_LINUX
class EventfdQueue {
int fd;
VarQueue q;
public:
void init() {
q.init();
fd = eventfd(0, 0);
}
void put(qvalue_t value) {
q.put(value);
td::int64 x = 1;
auto len = write(fd, &x, sizeof(x));
CHECK(len == sizeof(x));
}
qvalue_t get() {
td::int64 x;
auto len = read(fd, &x, sizeof(x));
CHECK(len == sizeof(x));
CHECK(x == 1);
return q.get();
}
void destroy() {
q.destroy();
close(fd);
}
};
#endif
const int queue_buf_size = 1 << 10;
class BufferQueue {
struct node {
qvalue_t val;
char pad[64 - sizeof(std::atomic<qvalue_t>)];
};
node q[queue_buf_size];
struct Position {
std::atomic<td::uint32> i{0};
char pad[64 - sizeof(std::atomic<td::uint32>)];
td::uint32 local_read_i;
td::uint32 local_write_i;
char pad2[64 - sizeof(td::uint32) * 2];
void init() {
i = 0;
local_read_i = 0;
local_write_i = 0;
}
};
Position writer;
Position reader;
public:
void init() {
writer.init();
reader.init();
}
bool reader_empty() {
return reader.local_write_i == reader.local_read_i;
}
bool writer_empty() {
return writer.local_write_i == writer.local_read_i + queue_buf_size;
}
int reader_ready() {
return static_cast<int>(reader.local_write_i - reader.local_read_i);
}
int writer_ready() {
return static_cast<int>(writer.local_read_i + queue_buf_size - writer.local_write_i);
}
qvalue_t get_unsafe() {
return q[reader.local_read_i++ & (queue_buf_size - 1)].val;
}
void flush_reader() {
reader.i.store(reader.local_read_i, std::memory_order_release);
}
int update_reader() {
reader.local_write_i = writer.i.load(std::memory_order_acquire);
return reader_ready();
}
void put_unsafe(qvalue_t val) {
q[writer.local_write_i++ & (queue_buf_size - 1)].val = val;
}
void flush_writer() {
writer.i.store(writer.local_write_i, std::memory_order_release);
}
int update_writer() {
writer.local_read_i = reader.i.load(std::memory_order_acquire);
return writer_ready();
}
int wait_reader() {
Backoff backoff;
int res = 0;
while (res == 0) {
backoff.next();
res = update_reader();
}
return res;
}
qvalue_t get_noflush() {
if (!reader_empty()) {
return get_unsafe();
}
Backoff backoff;
while (true) {
backoff.next();
if (update_reader()) {
return get_unsafe();
}
}
}
qvalue_t get() {
qvalue_t res = get_noflush();
flush_reader();
return res;
}
void put_noflush(qvalue_t val) {
if (!writer_empty()) {
put_unsafe(val);
return;
}
if (!update_writer()) {
LOG(FATAL) << "Put strong failed";
}
put_unsafe(val);
}
void put(qvalue_t val) {
put_noflush(val);
flush_writer();
}
void destroy() {
}
};
#if TD_LINUX
class BufferedFdQueue {
int fd;
std::atomic<int> wait_flag{0};
BufferQueue q;
char pad[64];
public:
void init() {
q.init();
fd = eventfd(0, 0);
(void)pad[0];
}
void put(qvalue_t value) {
q.put(value);
td::int64 x = 1;
__sync_synchronize();
if (wait_flag.load(MODE)) {
auto len = write(fd, &x, sizeof(x));
CHECK(len == sizeof(x));
}
}
void put_noflush(qvalue_t value) {
q.put_noflush(value);
}
void flush_writer() {
q.flush_writer();
td::int64 x = 1;
__sync_synchronize();
if (wait_flag.load(MODE)) {
auto len = write(fd, &x, sizeof(x));
CHECK(len == sizeof(x));
}
}
void flush_reader() {
q.flush_reader();
}
qvalue_t get_unsafe_flush() {
qvalue_t res = q.get_unsafe();
q.flush_reader();
return res;
}
qvalue_t get_unsafe() {
return q.get_unsafe();
}
int wait_reader() {
int res = 0;
Backoff backoff;
while (res == 0 && backoff.next()) {
res = q.update_reader();
}
if (res != 0) {
return res;
}
td::int64 x;
wait_flag.store(1, MODE);
__sync_synchronize();
while (!(res = q.update_reader())) {
auto len = read(fd, &x, sizeof(x));
CHECK(len == sizeof(x));
__sync_synchronize();
}
wait_flag.store(0, MODE);
return res;
}
qvalue_t get() {
if (!q.reader_empty()) {
return get_unsafe_flush();
}
Backoff backoff;
while (backoff.next()) {
if (q.update_reader()) {
return get_unsafe_flush();
}
}
td::int64 x;
wait_flag.store(1, MODE);
__sync_synchronize();
while (!q.update_reader()) {
auto len = read(fd, &x, sizeof(x));
CHECK(len == sizeof(x));
__sync_synchronize();
}
wait_flag.store(0, MODE);
return get_unsafe_flush();
}
void destroy() {
q.destroy();
close(fd);
}
};
class FdQueue {
int fd;
std::atomic<int> wait_flag{0};
VarQueue q;
char pad[64];
public:
void init() {
q.init();
fd = eventfd(0, 0);
(void)pad[0];
}
void put(qvalue_t value) {
q.put(value);
td::int64 x = 1;
__sync_synchronize();
if (wait_flag.load(MODE)) {
auto len = write(fd, &x, sizeof(x));
CHECK(len == sizeof(x));
}
}
qvalue_t get() {
// td::int64 x;
// auto len = read(fd, &x, sizeof(x));
// CHECK(len == sizeof(x));
// return q.get();
Backoff backoff;
qvalue_t res = -1;
do {
res = q.try_get();
} while (res == -1 && backoff.next());
if (res != -1) {
q.acquire();
return res;
}
td::int64 x;
wait_flag.store(1, MODE);
__sync_synchronize();
// while (res == -1 && read(fd, &x, sizeof(x)) == sizeof(x)) {
// res = q.try_get();
// }
do {
__sync_synchronize();
res = q.try_get();
} while (res == -1 && read(fd, &x, sizeof(x)) == sizeof(x));
q.acquire();
wait_flag.store(0, MODE);
return res;
}
void destroy() {
q.destroy();
close(fd);
}
};
#endif
#if TD_PORT_POSIX
class SemBackoffQueue {
sem_t sem;
VarQueue q;
public:
void init() {
q.init();
sem_init(&sem, 0, 0);
}
void put(qvalue_t value) {
q.put(value);
sem_post(&sem);
}
qvalue_t get() {
Backoff backoff;
int sem_flag = -1;
do {
sem_flag = sem_trywait(&sem);
} while (sem_flag != 0 && backoff.next());
if (sem_flag != 0) {
sem_wait(&sem);
}
return q.get();
}
void destroy() {
q.destroy();
sem_destroy(&sem);
}
};
class SemCheatQueue {
sem_t sem;
VarQueue q;
public:
void init() {
q.init();
sem_init(&sem, 0, 0);
}
void put(qvalue_t value) {
q.put(value);
sem_post(&sem);
}
qvalue_t get() {
Backoff backoff;
qvalue_t res = -1;
do {
res = q.try_get();
} while (res == -1 && backoff.next());
sem_wait(&sem);
if (res != -1) {
q.acquire();
return res;
}
return q.get();
}
void destroy() {
q.destroy();
sem_destroy(&sem);
}
};
template <class QueueT>
class QueueBenchmark2 final : public td::Benchmark {
QueueT client, server;
int connections_n, queries_n;
int server_active_connections;
int client_active_connections;
td::vector<td::int64> server_conn;
td::vector<td::int64> client_conn;
td::string name;
public:
QueueBenchmark2(int connections_n, td::string name) : connections_n(connections_n), name(std::move(name)) {
}
td::string get_description() const final {
return name;
}
void start_up() final {
client.init();
server.init();
}
void tear_down() final {
client.destroy();
server.destroy();
}
void server_process(qvalue_t value) {
int no = value & 0x00FFFFFF;
auto co = static_cast<int>(static_cast<td::uint32>(value) >> 24);
CHECK(co >= 0 && co < connections_n);
CHECK(no == server_conn[co]++);
client.writer_put(value);
client.writer_flush();
if (no + 1 >= queries_n) {
server_active_connections--;
}
}
void *server_run(void *) {
server_conn = td::vector<td::int64>(connections_n);
server_active_connections = connections_n;
while (server_active_connections > 0) {
int cnt = server.reader_wait();
CHECK(cnt != 0);
while (cnt-- > 0) {
server_process(server.reader_get_unsafe());
server.reader_flush();
}
// client.writer_flush();
server.reader_flush();
}
return nullptr;
}
void client_process(qvalue_t value) {
int no = value & 0x00FFFFFF;
auto co = static_cast<int>(static_cast<td::uint32>(value) >> 24);
CHECK(co >= 0 && co < connections_n);
CHECK(no == client_conn[co]++);
if (no + 1 < queries_n) {
server.writer_put(value + 1);
server.writer_flush();
} else {
client_active_connections--;
}
}
void *client_run(void *) {
client_conn = td::vector<td::int64>(connections_n);
client_active_connections = connections_n;
CHECK(queries_n < (1 << 24));
for (int i = 0; i < connections_n; i++) {
server.writer_put(static_cast<qvalue_t>(i) << 24);
}
server.writer_flush();
while (client_active_connections > 0) {
int cnt = client.reader_wait();
CHECK(cnt != 0);
while (cnt-- > 0) {
client_process(client.reader_get_unsafe());
client.reader_flush();
}
// server.writer_flush();
client.reader_flush();
}
// system("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
return nullptr;
}
static void *client_run_gateway(void *arg) {
return static_cast<QueueBenchmark2 *>(arg)->client_run(nullptr);
}
static void *server_run_gateway(void *arg) {
return static_cast<QueueBenchmark2 *>(arg)->server_run(nullptr);
}
void run(int n) final {
pthread_t client_thread_id;
pthread_t server_thread_id;
queries_n = (n + connections_n - 1) / connections_n;
pthread_create(&client_thread_id, nullptr, client_run_gateway, this);
pthread_create(&server_thread_id, nullptr, server_run_gateway, this);
pthread_join(client_thread_id, nullptr);
pthread_join(server_thread_id, nullptr);
}
};
template <class QueueT>
class QueueBenchmark final : public td::Benchmark {
QueueT client, server;
const int connections_n;
int queries_n;
td::string name;
public:
QueueBenchmark(int connections_n, td::string name) : connections_n(connections_n), name(std::move(name)) {
}
td::string get_description() const final {
return name;
}
void start_up() final {
client.init();
server.init();
}
void tear_down() final {
client.destroy();
server.destroy();
}
void *server_run(void *) {
td::vector<td::int64> conn(connections_n);
int active_connections = connections_n;
while (active_connections > 0) {
qvalue_t value = server.get();
int no = value & 0x00FFFFFF;
auto co = static_cast<int>(value >> 24);
CHECK(co >= 0 && co < connections_n);
CHECK(no == conn[co]++);
client.put(value);
if (no + 1 >= queries_n) {
active_connections--;
}
}
return nullptr;
}
void *client_run(void *) {
td::vector<td::int64> conn(connections_n);
CHECK(queries_n < (1 << 24));
for (int i = 0; i < connections_n; i++) {
server.put(static_cast<qvalue_t>(i) << 24);
}
int active_connections = connections_n;
while (active_connections > 0) {
qvalue_t value = client.get();
int no = value & 0x00FFFFFF;
auto co = static_cast<int>(value >> 24);
CHECK(co >= 0 && co < connections_n);
CHECK(no == conn[co]++);
if (no + 1 < queries_n) {
server.put(value + 1);
} else {
active_connections--;
}
}
// system("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
return nullptr;
}
void *client_run2(void *) {
td::vector<td::int64> conn(connections_n);
CHECK(queries_n < (1 << 24));
for (int query = 0; query < queries_n; query++) {
for (int i = 0; i < connections_n; i++) {
server.put((static_cast<td::int64>(i) << 24) + query);
}
for (int i = 0; i < connections_n; i++) {
qvalue_t value = client.get();
int no = value & 0x00FFFFFF;
auto co = static_cast<int>(value >> 24);
CHECK(co >= 0 && co < connections_n);
CHECK(no == conn[co]++);
}
}
// system("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
return nullptr;
}
static void *client_run_gateway(void *arg) {
return static_cast<QueueBenchmark *>(arg)->client_run(nullptr);
}
static void *server_run_gateway(void *arg) {
return static_cast<QueueBenchmark *>(arg)->server_run(nullptr);
}
void run(int n) final {
pthread_t client_thread_id;
pthread_t server_thread_id;
queries_n = (n + connections_n - 1) / connections_n;
pthread_create(&client_thread_id, nullptr, client_run_gateway, this);
pthread_create(&server_thread_id, nullptr, server_run_gateway, this);
pthread_join(client_thread_id, nullptr);
pthread_join(server_thread_id, nullptr);
}
};
template <class QueueT>
class RingBenchmark final : public td::Benchmark {
static constexpr int QN = 504;
struct Thread {
int int_id;
pthread_t id;
QueueT queue;
Thread *next;
char pad[64];
void *run() {
qvalue_t value;
do {
int cnt = queue.reader_wait();
CHECK(cnt == 1);
value = queue.reader_get_unsafe();
queue.reader_flush();
next->queue.writer_put(value - 1);
next->queue.writer_flush();
} while (value >= QN);
return nullptr;
}
};
Thread q[QN];
public:
static void *run_gateway(void *arg) {
return static_cast<Thread *>(arg)->run();
}
void start_up() final {
for (int i = 0; i < QN; i++) {
q[i].int_id = i;
q[i].queue.init();
q[i].next = &q[(i + 1) % QN];
}
}
void tear_down() final {
for (int i = 0; i < QN; i++) {
q[i].queue.destroy();
}
}
void run(int n) final {
for (int i = 0; i < QN; i++) {
pthread_create(&q[i].id, nullptr, run_gateway, &q[i]);
}
if (n < 1000) {
n = 1000;
}
q[0].queue.writer_put(n);
q[0].queue.writer_flush();
for (int i = 0; i < QN; i++) {
pthread_join(q[i].id, nullptr);
}
}
};
#endif
/*
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
static void test_queue() {
td::vector<td::thread> threads;
static constexpr size_t THREAD_COUNT = 100;
td::vector<td::MpscPollableQueue<int>> queues(THREAD_COUNT);
for (auto &q : queues) {
q.init();
}
for (size_t i = 0; i < THREAD_COUNT; i++) {
threads.emplace_back([&q = queues[i]] {
while (true) {
auto ready_count = q.reader_wait_nonblock();
while (ready_count-- > 0) {
q.reader_get_unsafe();
}
q.reader_get_event_fd().wait(1000);
}
});
}
for (size_t iter = 0; iter < THREAD_COUNT; iter++) {
td::usleep_for(100);
for (int i = 0; i < 5; i++) {
queues[td::Random::fast(0, THREAD_COUNT - 1)].writer_put(1);
}
}
for (size_t i = 0; i < THREAD_COUNT; i++) {
threads[i].join();
}
}
#endif
*/
int main() {
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
// test_queue();
#endif
#if TD_PORT_POSIX
// td::bench(RingBenchmark<SemQueue>());
// td::bench(RingBenchmark<td::PollQueue<qvalue_t>>());
#define BENCH_Q2(Q, N) td::bench(QueueBenchmark2<Q<qvalue_t>>(N, #Q "(" #N ")"))
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
BENCH_Q2(td::InfBackoffQueue, 1);
BENCH_Q2(td::MpscPollableQueue, 1);
BENCH_Q2(td::PollQueue, 1);
BENCH_Q2(td::InfBackoffQueue, 10);
BENCH_Q2(td::MpscPollableQueue, 10);
BENCH_Q2(td::PollQueue, 10);
BENCH_Q2(td::InfBackoffQueue, 100);
BENCH_Q2(td::MpscPollableQueue, 100);
BENCH_Q2(td::PollQueue, 100);
BENCH_Q2(td::PollQueue, 4);
BENCH_Q2(td::PollQueue, 10);
BENCH_Q2(td::PollQueue, 100);
#endif
#define BENCH_Q(Q, N) td::bench(QueueBenchmark<Q>(N, #Q "(" #N ")"))
#if TD_LINUX
BENCH_Q(BufferQueue, 1);
BENCH_Q(BufferedFdQueue, 1);
BENCH_Q(FdQueue, 1);
#endif
BENCH_Q(PipeQueue, 1);
BENCH_Q(SemCheatQueue, 1);
BENCH_Q(SemQueue, 1);
BENCH_Q(VarQueue, 1);
#if TD_LINUX
BENCH_Q(BufferQueue, 4);
BENCH_Q(BufferQueue, 10);
BENCH_Q(BufferQueue, 100);
#endif
#endif
}

View File

@ -1,113 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/DialogId.h"
#include "td/telegram/MessageDb.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/NotificationId.h"
#include "td/telegram/ServerMessageId.h"
#include "td/telegram/UserId.h"
#include "td/db/DbKey.h"
#include "td/db/SqliteConnectionSafe.h"
#include "td/db/SqliteDb.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/benchmark.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/Status.h"
#include <memory>
static td::Status init_db(td::SqliteDb &db) {
TRY_STATUS(db.exec("PRAGMA encoding=\"UTF-8\""));
TRY_STATUS(db.exec("PRAGMA synchronous=NORMAL"));
TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
TRY_STATUS(db.exec("PRAGMA temp_store=MEMORY"));
TRY_STATUS(db.exec("PRAGMA secure_delete=1"));
return td::Status::OK();
}
class MessageDbBench final : public td::Benchmark {
public:
td::string get_description() const final {
return "MessageDb";
}
void start_up() final {
LOG(ERROR) << "START UP";
do_start_up().ensure();
scheduler_->start();
}
void run(int n) final {
auto guard = scheduler_->get_main_guard();
for (int i = 0; i < n; i += 20) {
auto dialog_id = td::DialogId(td::UserId(static_cast<td::int64>(td::Random::fast(1, 100))));
auto message_id_raw = td::Random::fast(1, 100000);
for (int j = 0; j < 20; j++) {
auto message_id = td::MessageId{td::ServerMessageId{message_id_raw + j}};
auto unique_message_id = td::ServerMessageId{i + 1};
auto sender_dialog_id = td::DialogId(td::UserId(static_cast<td::int64>(td::Random::fast(1, 1000))));
auto random_id = i + 1;
auto ttl_expires_at = 0;
auto data = td::BufferSlice(td::Random::fast(100, 299));
// use async on same thread.
message_db_async_->add_message({dialog_id, message_id}, unique_message_id, sender_dialog_id, random_id,
ttl_expires_at, 0, 0, "", td::NotificationId(), td::MessageId(), std::move(data),
td::Promise<>());
}
}
}
void tear_down() final {
scheduler_->run_main(0.1);
{
auto guard = scheduler_->get_main_guard();
sql_connection_.reset();
message_db_sync_safe_.reset();
message_db_async_.reset();
}
scheduler_->finish();
scheduler_.reset();
LOG(ERROR) << "TEAR DOWN";
}
private:
td::unique_ptr<td::ConcurrentScheduler> scheduler_;
std::shared_ptr<td::SqliteConnectionSafe> sql_connection_;
std::shared_ptr<td::MessageDbSyncSafeInterface> message_db_sync_safe_;
std::shared_ptr<td::MessageDbAsyncInterface> message_db_async_;
td::Status do_start_up() {
scheduler_ = td::make_unique<td::ConcurrentScheduler>(1, 0);
auto guard = scheduler_->get_main_guard();
td::string sql_db_name = "testdb.sqlite";
sql_connection_ = std::make_shared<td::SqliteConnectionSafe>(sql_db_name, td::DbKey::empty());
auto &db = sql_connection_->get();
TRY_STATUS(init_db(db));
db.exec("BEGIN TRANSACTION").ensure();
// version == 0 ==> db will be destroyed
TRY_STATUS(init_message_db(db, 0));
db.exec("COMMIT TRANSACTION").ensure();
message_db_sync_safe_ = td::create_message_db_sync(sql_connection_);
message_db_async_ = td::create_message_db_async(message_db_sync_safe_, 0);
return td::Status::OK();
}
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
td::bench(MessageDbBench());
}

View File

@ -1,193 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Client.h"
#include "td/telegram/td_api.h"
#include "td/utils/base64.h"
#include "td/utils/common.h"
#include "td/utils/filesystem.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/TsCerr.h"
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <utility>
static void usage() {
td::TsCerr() << "Tests specified MTProto-proxies, outputs working proxies to stdout; exits with code 0 if a working "
"proxy was found.\n";
td::TsCerr() << "Usage: check_proxy [options] server:port:secret [server2:port2:secret2 ...]\n";
td::TsCerr() << "Options:\n";
td::TsCerr() << " -v<N>\tSet verbosity level to N\n";
td::TsCerr() << " -h/--help\tDisplay this information\n";
td::TsCerr() << " -d/--dc-id\tIdentifier of a datacenter to which try to connect (default is 2)\n";
td::TsCerr() << " -l/--proxy-list\tName of a file with proxies to check; one proxy per line\n";
td::TsCerr() << " -t/--timeout\tMaximum overall timeout for the request (default is 10 seconds)\n";
std::exit(2);
}
int main(int argc, char **argv) {
int new_verbosity_level = VERBOSITY_NAME(FATAL);
td::vector<std::pair<td::string, td::td_api::object_ptr<td::td_api::testProxy>>> requests;
auto add_proxy = [&requests](td::string arg) {
if (arg.empty()) {
return;
}
std::size_t offset = 0;
if (arg[0] == '[') {
auto end_ipv6_pos = arg.find(']');
if (end_ipv6_pos == td::string::npos) {
td::TsCerr() << "Error: failed to find end of IPv6 address in \"" << arg << "\"\n";
usage();
}
offset = end_ipv6_pos;
}
if (std::count(arg.begin() + offset, arg.end(), ':') == 3) {
auto secret_domain_pos = arg.find(':', arg.find(':', offset) + 1) + 1;
auto domain_pos = arg.find(':', secret_domain_pos);
auto secret = arg.substr(secret_domain_pos, domain_pos - secret_domain_pos);
auto domain = arg.substr(domain_pos + 1);
auto r_decoded_secret = td::hex_decode(secret);
if (r_decoded_secret.is_error()) {
r_decoded_secret = td::base64url_decode(secret);
if (r_decoded_secret.is_error()) {
td::TsCerr() << "Error: failed to find proxy port and secret in \"" << arg << "\"\n";
usage();
}
}
arg = arg.substr(0, secret_domain_pos) + td::base64url_encode(r_decoded_secret.ok() + domain);
}
auto secret_pos = arg.rfind(':');
if (secret_pos == td::string::npos) {
td::TsCerr() << "Error: failed to find proxy port and secret in \"" << arg << "\"\n";
usage();
}
auto secret = arg.substr(secret_pos + 1);
auto port_pos = arg.substr(0, secret_pos).rfind(':');
if (port_pos == td::string::npos) {
td::TsCerr() << "Error: failed to find proxy secret in \"" << arg << "\"\n";
usage();
}
auto r_port = td::to_integer_safe<td::int32>(arg.substr(port_pos + 1, secret_pos - port_pos - 1));
if (r_port.is_error()) {
td::TsCerr() << "Error: failed to parse proxy port in \"" << arg << "\"\n";
usage();
}
auto port = r_port.move_as_ok();
auto server = arg.substr(0, port_pos);
if (server[0] == '[' && server.back() == ']') {
server = server.substr(1, server.size() - 2);
}
if (server.empty() || port <= 0 || port > 65536 || secret.empty()) {
td::TsCerr() << "Error: proxy address to check is in wrong format: \"" << arg << "\"\n";
usage();
}
requests.emplace_back(arg,
td::td_api::make_object<td::td_api::testProxy>(
server, port, td::td_api::make_object<td::td_api::proxyTypeMtproto>(secret), -1, -1));
};
td::int32 dc_id = 2;
double timeout = 10.0;
for (int i = 1; i < argc; i++) {
td::string arg(argv[i]);
auto get_next_arg = [&i, &arg, argc, argv](bool is_optional = false) {
CHECK(arg.size() >= 2);
if (arg.size() == 2 || arg[1] == '-') {
if (i + 1 < argc && argv[i + 1][0] != '-') {
return td::string(argv[++i]);
}
} else {
if (arg.size() > 2) {
return arg.substr(2);
}
}
if (!is_optional) {
td::TsCerr() << "Error: value is required after " << arg << "\n";
usage();
}
return td::string();
};
if (td::begins_with(arg, "-v")) {
arg = get_next_arg(true);
int new_verbosity = 1;
while (arg[0] == 'v') {
new_verbosity++;
arg = arg.substr(1);
}
if (!arg.empty()) {
new_verbosity += td::to_integer<int>(arg) - (new_verbosity == 1);
}
new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
} else if (td::begins_with(arg, "-t") || arg == "--timeout") {
timeout = td::to_double(get_next_arg());
} else if (td::begins_with(arg, "-d") || arg == "--dc-id") {
dc_id = td::to_integer<td::int32>(get_next_arg());
} else if (td::begins_with(arg, "-l") || arg == "--proxy-list") {
auto r_proxies = td::read_file_str(get_next_arg());
if (r_proxies.is_error()) {
td::TsCerr() << "Error: wrong file name specified\n";
usage();
}
for (auto &proxy : td::full_split(r_proxies.ok(), '\n')) {
add_proxy(td::trim(proxy));
}
} else if (arg[0] == '-') {
usage();
} else {
add_proxy(arg);
}
}
if (requests.empty()) {
td::TsCerr() << "Error: proxy address to check is not specified\n";
usage();
}
SET_VERBOSITY_LEVEL(new_verbosity_level);
td::ClientManager client_manager;
auto client_id = client_manager.create_client_id();
for (size_t i = 0; i < requests.size(); i++) {
auto &request = requests[i].second;
request->dc_id_ = dc_id;
request->timeout_ = timeout;
client_manager.send(client_id, i + 1, std::move(request));
}
size_t successful_requests = 0;
size_t failed_requests = 0;
while (successful_requests + failed_requests != requests.size()) {
auto response = client_manager.receive(100.0);
CHECK(client_id == response.client_id);
if (1 <= response.request_id && response.request_id <= requests.size()) {
auto &proxy = requests[static_cast<size_t>(response.request_id - 1)].first;
if (response.object->get_id() == td::td_api::error::ID) {
LOG(ERROR) << proxy << ": " << to_string(response.object);
failed_requests++;
} else {
std::cout << proxy << std::endl;
successful_requests++;
}
}
}
if (successful_requests == 0) {
return 1;
}
}

View File

@ -1,336 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/BigNum.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/detail/Iocp.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/sleep.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/port/thread.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
#include <map>
static td::BigNumContext context;
static bool is_quadratic_residue(const td::BigNum &a) {
// 2^255 - 19
td::BigNum mod =
td::BigNum::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed").move_as_ok();
// (mod - 1) / 2 = 2^254 - 10
td::BigNum pow =
td::BigNum::from_hex("3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6").move_as_ok();
td::BigNum r;
td::BigNum::mod_exp(r, a, pow, mod, context);
td::BigNum one = td::BigNum::from_decimal("1").move_as_ok();
td::BigNum::mod_add(r, r, one, mod, context);
td::string result = r.to_decimal();
CHECK(result == "0" || result == "1" || result == "2");
return result == "2";
}
struct TlsInfo {
td::vector<size_t> extension_list;
td::vector<size_t> encrypted_application_data_length;
};
td::Result<TlsInfo> test_tls(const td::string &url) {
td::IPAddress address;
TRY_STATUS(address.init_host_port(url, 443));
TRY_RESULT(socket, td::SocketFd::open(address));
td::string request;
auto add_string = [&](td::Slice data) {
request.append(data.data(), data.size());
};
auto add_random = [&](size_t length) {
while (length-- > 0) {
request += static_cast<char>(td::Random::secure_int32());
}
};
auto add_length = [&](size_t length) {
request += static_cast<char>(length / 256);
request += static_cast<char>(length % 256);
};
auto add_key = [&] {
td::string key(32, '\0');
td::BigNum mod =
td::BigNum::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed").move_as_ok();
while (true) {
td::Random::secure_bytes(key);
key[31] = static_cast<char>(key[31] & 127);
td::BigNum x = td::BigNum::from_le_binary(key);
if (!is_quadratic_residue(x)) {
continue;
}
td::BigNum y = x.clone();
td::BigNum coef = td::BigNum::from_decimal("486662").move_as_ok();
td::BigNum::mod_add(y, y, coef, mod, context);
td::BigNum::mod_mul(y, y, x, mod, context);
td::BigNum one = td::BigNum::from_decimal("1").move_as_ok();
td::BigNum::mod_add(y, y, one, mod, context);
td::BigNum::mod_mul(y, y, x, mod, context);
// y = x^3 + 486662 * x^2 + x
if (is_quadratic_residue(y)) {
break;
}
}
request += key;
};
const size_t MAX_GREASE = 7;
char greases[MAX_GREASE];
td::Random::secure_bytes(td::MutableSlice{greases, MAX_GREASE});
for (auto &grease : greases) {
grease = static_cast<char>((grease & 0xF0) + 0x0A);
}
for (size_t i = 1; i < MAX_GREASE; i += 2) {
if (greases[i] == greases[i - 1]) {
greases[i] = static_cast<char>(0x10 ^ greases[i]);
}
}
auto add_grease = [&](size_t num) {
auto c = greases[num];
request += c;
request += c;
};
add_string("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03");
add_random(32);
add_string("\x20");
add_random(32);
add_string("\x00\x20");
add_grease(0);
add_string(
"\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00"
"\x2f\x00\x35\x01\x00\x01\x93");
add_grease(2);
add_string("\x00\x00\x00\x00");
add_length(url.size() + 5);
add_length(url.size() + 3);
add_string("\x00");
add_length(url.size());
add_string(url);
add_string("\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0a\x00\x08");
add_grease(4);
add_string(
"\x00\x1d\x00\x17\x00\x18\x00\x0b\x00\x02\x01\x00\x00\x23\x00\x00\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68"
"\x74\x74\x70\x2f\x31\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04"
"\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x00\x12\x00\x00\x00\x33\x00\x2b\x00\x29");
add_grease(4);
add_string("\x00\x01\x00\x00\x1d\x00\x20");
add_key();
add_string("\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a");
add_grease(6);
add_string("\x03\x04\x03\x03\x03\x02\x03\x01\x00\x1b\x00\x03\x02\x00\x02");
add_grease(3);
add_string("\x00\x01\x00\x00\x15");
auto padding = 515 - static_cast<int>(request.size());
CHECK(padding >= 0);
add_length(padding);
request.resize(517);
// LOG(ERROR) << td::format::as_hex_dump<0>(td::Slice(request));
TRY_STATUS(socket.write(request));
TlsInfo info;
auto end_time = td::Time::now() + 3;
td::string result;
size_t pos = 0;
size_t server_hello_length = 0;
size_t encrypted_application_data_length_sum = 0;
while (td::Time::now() < end_time) {
static char buf[20000];
TRY_RESULT(res, socket.read(td::MutableSlice{buf, sizeof(buf)}));
if (res > 0) {
auto read_length = [&]() -> size_t {
CHECK(result.size() >= 2 + pos);
pos += 2;
return static_cast<unsigned char>(result[pos - 2]) * 256 + static_cast<unsigned char>(result[pos - 1]);
};
result += td::Slice(buf, res).str();
while (true) {
#define CHECK_LENGTH(length) \
if (pos + (length) > result.size()) { \
break; \
}
#define EXPECT_STR(pos, str, error) \
if (!begins_with(td::Slice(result).substr(pos), str)) { \
return td::Status::Error(error); \
}
if (pos == 0) {
CHECK_LENGTH(3);
EXPECT_STR(0, "\x16\x03\x03", "Non-TLS response or TLS <= 1.1");
pos += 3;
}
if (pos == 3) {
CHECK_LENGTH(2);
server_hello_length = read_length();
if (server_hello_length <= 39) {
return td::Status::Error("Receive too short server hello");
}
}
if (server_hello_length > 0) {
if (pos == 5) {
CHECK_LENGTH(server_hello_length);
EXPECT_STR(5, "\x02\x00", "Non-TLS response 2");
EXPECT_STR(9, "\x03\x03", "Non-TLS response 3");
auto random_id = td::Slice(result.c_str() + 11, 32);
if (random_id ==
"\xcf\x21\xad\x74\xe5\x9a\x61\x11\xbe\x1d\x8c\x02\x1e\x65\xb8\x91\xc2\xa2\x11\x16\x7a\xbb\x8c\x5e\x07"
"\x9e\x09\xe2\xc8\xa8\x33\x9c") {
return td::Status::Error("TLS 1.3 servers returning HelloRetryRequest are not supported");
}
if (result[43] == '\x00') {
return td::Status::Error("TLS <= 1.2: empty session_id");
}
EXPECT_STR(43, "\x20", "Non-TLS response 4");
if (server_hello_length <= 75) {
return td::Status::Error("Receive too short server hello 2");
}
EXPECT_STR(44, request.substr(44, 32), "TLS <= 1.2: expected mirrored session_id");
EXPECT_STR(76, "\x13\x01\x00", "TLS <= 1.2: expected 0x1301 as a chosen cipher");
pos += 74;
size_t extensions_length = read_length();
if (extensions_length + 76 != server_hello_length) {
return td::Status::Error("Receive wrong extensions length");
}
while (pos < 5 + server_hello_length - 4) {
info.extension_list.push_back(read_length());
size_t extension_length = read_length();
if (pos + extension_length > 5 + server_hello_length) {
return td::Status::Error("Receive wrong extension length");
}
pos += extension_length;
}
if (pos != 5 + server_hello_length) {
return td::Status::Error("Receive wrong extensions list");
}
}
if (pos == 5 + server_hello_length) {
CHECK_LENGTH(6);
EXPECT_STR(pos, "\x14\x03\x03\x00\x01\x01", "Expected dummy ChangeCipherSpec");
pos += 6;
}
if (pos == 11 + server_hello_length + encrypted_application_data_length_sum) {
if (pos == result.size()) {
return info;
}
CHECK_LENGTH(3);
EXPECT_STR(pos, "\x17\x03\x03", "Expected encrypted application data");
pos += 3;
}
if (pos == 14 + server_hello_length + encrypted_application_data_length_sum) {
CHECK_LENGTH(2);
size_t encrypted_application_data_length = read_length();
info.encrypted_application_data_length.push_back(encrypted_application_data_length);
if (encrypted_application_data_length == 0) {
return td::Status::Error("Receive empty encrypted application data");
}
}
if (pos == 16 + server_hello_length + encrypted_application_data_length_sum) {
CHECK_LENGTH(info.encrypted_application_data_length.back());
pos += info.encrypted_application_data_length.back();
encrypted_application_data_length_sum += info.encrypted_application_data_length.back() + 5;
}
}
}
} else {
td::usleep_for(10000);
}
}
// LOG(ERROR) << url << ":" << td::format::as_hex_dump<0>(td::Slice(result));
return td::Status::Error("Failed to get response in 3 seconds");
}
int main(int argc, char *argv[]) {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
#if TD_PORT_WINDOWS
auto iocp = td::make_unique<td::detail::Iocp>();
iocp->init();
auto iocp_thread = td::thread([&iocp] { iocp->loop(); });
td::detail::Iocp::Guard iocp_guard(iocp.get());
#endif
td::vector<td::string> urls;
for (int i = 1; i < argc; i++) {
urls.emplace_back(argv[i]);
}
for (auto &url : urls) {
const int MAX_TRIES = 100;
td::vector<std::map<size_t, int>> length_count;
td::vector<size_t> extension_list;
for (int i = 0; i < MAX_TRIES; i++) {
auto r_tls_info = test_tls(url);
if (r_tls_info.is_error()) {
LOG(ERROR) << url << ": " << r_tls_info.error();
break;
} else {
auto tls_info = r_tls_info.move_as_ok();
if (length_count.size() < tls_info.encrypted_application_data_length.size()) {
length_count.resize(tls_info.encrypted_application_data_length.size());
}
for (size_t t = 0; t < tls_info.encrypted_application_data_length.size(); t++) {
length_count[t][tls_info.encrypted_application_data_length[t]]++;
}
if (i == 0) {
extension_list = tls_info.extension_list;
} else {
if (extension_list != tls_info.extension_list) {
LOG(ERROR) << url << ": TLS 1.3.0 extension list has changed from " << extension_list << " to "
<< tls_info.extension_list;
break;
}
}
}
if (i == MAX_TRIES - 1) {
if (extension_list != td::vector<size_t>{51, 43} && extension_list != td::vector<size_t>{43, 51}) {
LOG(ERROR) << url << ": TLS 1.3.0 unsupported extension list " << extension_list;
} else {
td::string length_distribution = "|";
for (size_t t = 0; t < length_count.size(); t++) {
for (auto it : length_count[t]) {
length_distribution += PSTRING()
<< it.first << " : " << static_cast<int>(it.second * 100.0 / MAX_TRIES) << "%|";
}
if (t + 1 != length_count.size()) {
length_distribution += " + |";
}
}
LOG(ERROR) << url << ": TLS 1.3.0 with extensions " << extension_list << " and "
<< (length_count.size() != 1 ? "unsupported " : "")
<< "encrypted application data length distribution " << length_distribution;
}
}
}
}
#if TD_PORT_WINDOWS
iocp->interrupt_loop();
iocp_thread.join();
#endif
}

View File

@ -1,545 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/FlatHashMap.h"
#ifdef SCOPE_EXIT
#undef SCOPE_EXIT
#endif
#include <absl/container/flat_hash_map.h>
#include <array>
#include <folly/container/F14Map.h>
#include <map>
#include <unordered_map>
#define test_map td::FlatHashMap
//#define test_map folly::F14FastMap
//#define test_map absl::flat_hash_map
//#define test_map std::map
//#define test_map std::unordered_map
//#define CREATE_MAP(num) CREATE_MAP_IMPL(num)
#define CREATE_MAP(num)
#define CREATE_MAP_IMPL(num) \
int f_##num() { \
test_map<td::int32, std::array<char, num>> m; \
m.emplace(1, std::array<char, num>{}); \
int sum = 0; \
for (auto &it : m) { \
sum += it.first; \
} \
auto it = m.find(1); \
sum += it->first; \
m.erase(it); \
return sum; \
} \
int x_##num = f_##num()
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
CREATE_MAP(__LINE__);
int main() {
}

View File

@ -1,193 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#if USE_MEMPROF
#include "memprof/memprof_stat.h"
#endif
#include "td/utils/common.h"
#include "td/utils/FlatHashMap.h"
#include "td/utils/FlatHashMapChunks.h"
#include "td/utils/FlatHashTable.h"
#include "td/utils/HashTableUtils.h"
#include "td/utils/logging.h"
#include "td/utils/MapNode.h"
#include "td/utils/misc.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
#ifdef SCOPE_EXIT
#undef SCOPE_EXIT
#endif
#include <absl/container/flat_hash_map.h>
#include <array>
#include <folly/container/F14Map.h>
#include <functional>
#include <map>
#include <unordered_map>
static int mem_stat_i = -1;
static int mem_stat_cur = 0;
static bool use_memprof() {
#if USE_MEMPROF
return mem_stat_i < 0 && is_memprof_on();
#else
return mem_stat_i < 0;
#endif
}
static td::uint64 get_memory() {
#if USE_MEMPROF
if (use_memprof()) {
return get_used_memory_size();
}
#endif
CHECK(!use_memprof());
return td::mem_stat().ok().resident_size_;
}
template <class T>
class Generator {
public:
T next() {
UNREACHABLE();
}
static size_t dyn_size() {
UNREACHABLE();
}
};
template <class T>
class IntGenerator {
public:
T next() {
return ++value;
}
static size_t dyn_size() {
return 0;
}
private:
T value{};
};
template <>
class Generator<td::int32> final : public IntGenerator<td::int32> {};
template <>
class Generator<td::int64> final : public IntGenerator<td::int64> {};
template <class T>
class Generator<td::unique_ptr<T>> {
public:
td::unique_ptr<T> next() {
return td::make_unique<T>();
}
static std::size_t dyn_size() {
return sizeof(T);
}
};
template <class T, class KeyT, class ValueT>
static void measure(td::StringBuilder &sb, td::Slice name, td::Slice key_name, td::Slice value_name) {
mem_stat_cur++;
if (mem_stat_i >= 0 && mem_stat_cur != mem_stat_i) {
return;
}
sb << name << "<" << key_name << "," << value_name << "> " << (use_memprof() ? "memprof" : "os") << "\n";
std::size_t ideal_size = sizeof(KeyT) + sizeof(ValueT) + Generator<ValueT>::dyn_size();
sb << "\tempty:" << sizeof(T);
struct Stat {
int pi;
double min_ratio;
double max_ratio;
};
td::vector<Stat> stat;
stat.reserve(1024);
for (std::size_t size : {1000000u}) {
Generator<KeyT> key_generator;
Generator<ValueT> value_generator;
auto start_mem = get_memory();
T ht;
auto ratio = [&] {
auto end_mem = get_memory();
auto used_mem = end_mem - start_mem;
return static_cast<double>(used_mem) / (static_cast<double>(ideal_size) * static_cast<double>(ht.size()));
};
double min_ratio;
double max_ratio;
auto reset = [&] {
min_ratio = 1e100;
max_ratio = 0;
};
auto update = [&] {
auto x = ratio();
min_ratio = td::min(min_ratio, x);
max_ratio = td::max(max_ratio, x);
};
reset();
int p = 10;
int pi = 1;
for (std::size_t i = 0; i < size; i++) {
ht.emplace(key_generator.next(), value_generator.next());
update();
if ((i + 1) % p == 0) {
stat.push_back(Stat{pi, min_ratio, max_ratio});
reset();
pi++;
p *= 10;
}
}
}
for (auto &s : stat) {
sb << " 10^" << s.pi << ":" << s.min_ratio << "->" << s.max_ratio;
}
sb << '\n';
}
template <std::size_t size>
using Bytes = std::array<char, size>;
template <template <typename... Args> class T>
void print_memory_stats(td::Slice name) {
td::string big_buff(1 << 16, '\0');
td::StringBuilder sb(big_buff, false);
#define MEASURE(KeyT, ValueT) measure<T<KeyT, ValueT>, KeyT, ValueT>(sb, name, #KeyT, #ValueT);
MEASURE(td::int32, td::int32);
MEASURE(td::int64, td::unique_ptr<Bytes<360>>);
if (!sb.as_cslice().empty()) {
LOG(PLAIN) << '\n' << sb.as_cslice() << '\n';
}
}
template <class KeyT, class ValueT, class HashT = td::Hash<KeyT>, class EqT = std::equal_to<KeyT>>
using FlatHashMapImpl = td::FlatHashTable<td::MapNode<KeyT, ValueT>, HashT, EqT>;
#define FOR_EACH_TABLE(F) \
F(FlatHashMapImpl) \
F(folly::F14FastMap) \
F(absl::flat_hash_map) \
F(std::unordered_map) \
F(std::map)
#define BENCHMARK_MEMORY(T) print_memory_stats<T>(#T);
int main(int argc, const char *argv[]) {
// Usage:
// % benchmark/memory-hashset-os 0
// Number of benchmarks = 10
// % for i in {1..10}; do ./benchmark/memory-hashset-os $i; done
if (argc > 1) {
mem_stat_i = td::to_integer<td::int32>(td::Slice(argv[1]));
}
FOR_EACH_TABLE(BENCHMARK_MEMORY);
if (mem_stat_i <= 0) {
LOG(PLAIN) << "Number of benchmarks = " << mem_stat_cur << "\n";
}
}

View File

@ -1,45 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/port/path.h"
#include "td/utils/Slice.h"
int main(int argc, char *argv[]) {
if (argc < 1) {
return 1;
}
td::CSlice dir(argv[1]);
int cnt = 0;
auto status = td::walk_path(dir, [&](td::CSlice path, auto type) {
if (type != td::WalkPath::Type::EnterDir) {
cnt++;
}
auto type_name = [&] {
switch (type) {
case td::WalkPath::Type::EnterDir:
return td::CSlice("Open");
case td::WalkPath::Type::ExitDir:
return td::CSlice("Exit");
case td::WalkPath::Type::RegularFile:
return td::CSlice("File");
case td::WalkPath::Type::Symlink:
return td::CSlice("Link");
default:
UNREACHABLE();
return td::CSlice();
}
}();
LOG(INFO) << type_name << ' ' << path;
//if (is_dir) {
// td::rmdir(path);
//} else {
// td::unlink(path);
//}
});
LOG(INFO) << status << ": " << cnt;
}

View File

@ -1,43 +0,0 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpQuery.h"
#include "td/net/Wget.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/Promise.h"
#include "td/utils/Status.h"
int main(int argc, char *argv[]) {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
td::VERBOSITY_NAME(fd) = VERBOSITY_NAME(INFO);
td::string url = (argc > 1 ? argv[1] : "https://telegram.org");
auto timeout = 10;
auto ttl = 3;
auto prefer_ipv6 = (argc > 2 && td::string(argv[2]) == "-6");
auto scheduler = td::make_unique<td::ConcurrentScheduler>(0, 0);
scheduler
->create_actor_unsafe<td::Wget>(0, "Client",
td::PromiseCreator::lambda([](td::Result<td::unique_ptr<td::HttpQuery>> res) {
if (res.is_error()) {
LOG(FATAL) << res.error();
}
LOG(ERROR) << *res.ok();
td::Scheduler::instance()->finish();
}),
url, td::Auto(), timeout, ttl, prefer_ipv6)
.release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
}

File diff suppressed because it is too large Load Diff

View File

@ -1,321 +0,0 @@
# TDLib usage and build examples
This directory contains basic examples of TDLib usage from different programming languages and examples of library building for different platforms.
If you are looking for documentation of all available TDLib methods, see the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the
automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of all available TDLib
[methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
Also, take a look at our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
TDLib can be easily used from almost any programming language on any platform. See a [TDLib build instructions generator](https://tdlib.github.io/td/build.html) for detailed instructions on how to build TDLib.
Choose your preferred programming language to see examples of usage and a detailed description:
- [Python](#python)
- [JavaScript](#javascript)
- [Go](#go)
- [Java](#java)
- [Kotlin](#kotlin)
- [C#](#csharp)
- [C++](#cxx)
- [Swift](#swift)
- [Objective-C](#objective-c)
- [Object Pascal](#object-pascal)
- [Dart](#dart)
- [Rust](#rust)
- [Erlang](#erlang)
- [PHP](#php)
- [Lua](#lua)
- [Ruby](#ruby)
- [Crystal](#crystal)
- [Haskell](#haskell)
- [Nim](#nim)
- [Clojure](#clojure)
- [Emacs Lisp](#emacslisp)
- [D](#d)
- [Elixir](#elixir)
- [Vala](#vala)
- [1С](#1s)
- [C](#c)
- [G](#g)
- [Other](#other)
<a name="python"></a>
## Using TDLib in Python projects
TDLib can be used from Python through the [JSON](https://github.com/tdlib/td#using-json) interface.
Convenient Python wrappers already exist for our JSON interface.
If you use Python >= 3.6, take a look at [python-telegram](https://github.com/alexander-akhmetov/python-telegram).
The wrapper uses the full power of asyncio, has a good documentation and has several examples. It can be installed through pip or used in a Docker container.
You can also try a fork [python-telegram](https://github.com/iTeam-co/pytglib) of this library.
If you want to use TDLib with asyncio and Python >= 3.9, take a look at [aiotdlib](https://github.com/pylakey/aiotdlib) or [Pytdbot](https://github.com/pytdbot/client).
For older Python versions you can use [pytdlib](https://github.com/pytdlib/pytdlib).
This wrapper contains generator for TDLib API classes and basic interface for interaction with TDLib.
You can also check out [example/python/tdjson_example.py](https://github.com/tdlib/td/blob/master/example/python/tdjson_example.py),
[tdlib-python](https://github.com/JunaidBabu/tdlib-python), or [Python Wrapper TDLib](https://github.com/alvhix/pywtdlib) for some basic examples of TDLib JSON interface integration with Python.
<a name="javascript"></a>
## Using TDLib in JavaScript projects
TDLib can be compiled to WebAssembly and used in a browser from JavaScript. See [tdweb](https://github.com/tdlib/td/tree/master/example/web) as a convenient wrapper for TDLib in a browser
and [telegram-react](https://github.com/evgeny-nadymov/telegram-react) as an example of a TDLib-based Telegram client.
See also [Svelte-tdweb-starter](https://github.com/gennadypolakov/svelte-tdweb-starter) - Svelte wrapper for tdweb, and [Telegram-Photoframe](https://github.com/lukefx/telegram-photoframe) - a web application that displays your preferred group or channel as Photoframe.
TDLib can be used from Node.js through the [JSON](https://github.com/tdlib/td#using-json) interface.
Convenient Node.js wrappers already exist for our JSON interface.
For example, take a look at [Airgram](https://github.com/airgram/airgram) modern TDLib framework for TypeScript/JavaScript, or
at [tdl](https://github.com/eilvelia/tdl), which provides a convenient, fully-asynchronous interface for interaction with TDLib and contains a bunch of examples.
You can also see [TdNode](https://github.com/puppy0cam/TdNode), [tglib](https://github.com/nodegin/tglib), [node-tdlib](https://github.com/wfjsw/node-tdlib), [tdlnode](https://github.com/fonbah/tdlnode),
[Paper Plane](https://github.com/par6n/paper-plane), or [node-tlg](https://github.com/dilongfa/node-tlg) for other examples of TDLib JSON interface integration with Node.js.
See also the source code of [DIBgram](https://github.com/DIBgram/DIBgram) - an unofficial Telegram web application which looks like Telegram Desktop.
TDLib can be used also from NativeScript through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [nativescript-tglib](https://github.com/arpit2438735/nativescript-tglib) as an example of a NativeScript library for building Telegram clients.
<a name="go"></a>
## Using TDLib in Go projects
TDLib can be used from the Go programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and Cgo, and can be linked either statically or dynamically.
Convenient Go wrappers already exist for our JSON interface.
For example, take a look at [github.com/zelenin/go-tdlib](https://github.com/zelenin/go-tdlib) or [github.com/Arman92/go-tdlib](https://github.com/Arman92/go-tdlib), which provide a convenient TDLib client, a generator for TDLib API classes and contain many examples.
You can also see [github.com/aliforever/go-tdlib](https://github.com/aliforever/go-tdlib) for another examples of TDLib JSON interface integration with Go.
<a name="java"></a>
## Using TDLib in Java projects
TDLib can be used from the Java programming language through native [JNI](https://github.com/tdlib/td#using-java) binding.
We provide a generator for JNI bridge methods and Java classes for all TDLib API methods and objects.
See [example/java](https://github.com/tdlib/td/tree/master/example/java) for an example of using TDLib from desktop Java along with detailed building and usage instructions.
See [example/android](https://github.com/tdlib/td/tree/master/example/android) for detailed build instructions for Android.
<a name="kotlin"></a>
## Using TDLib in Kotlin projects
TDLib can be used from the Kotlin/JVM programming language through same way as in [Java](#java).
You can also use [ktd](https://github.com/whyoleg/ktd) library with Kotlin-specific bindings.
See also [td-ktx](https://github.com/tdlibx/td-ktx) - Kotlin coroutines wrapper for TDLib.
<a name="csharp"></a>
## Using TDLib in C# projects
TDLib provides a native [.NET](https://github.com/tdlib/td#using-dotnet) interface through `C++/CLI` and `C++/CX`.
See [tdlib-netcore](https://github.com/dantmnf/tdlib-netcore) for a SWIG-like binding with automatically generated classes for TDLib API.
See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for an example of building TDLib SDK for the Universal Windows Platform and an example of its usage from C#.
See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for an example of building TDLib with `C++/CLI` support and an example of TDLib usage from C# on Windows.
If you want to write a cross-platform C# application using .NET Core, see [tdsharp](https://github.com/egramtel/tdsharp). It uses our [JSON](https://github.com/tdlib/td#using-json) interface,
provides an asynchronous interface for interaction with TDLib, automatically generated classes for TDLib API and has some examples.
Also, see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months,
[egram.tel](https://github.com/egramtel/egram.tel) a cross-platform Telegram client written in C#, .NET Core, ReactiveUI and Avalonia, or
[telewear](https://github.com/telewear/telewear) - a Telegram client for Samsung watches.
<a name="cxx"></a>
## Using TDLib in C++ projects
TDLib has a simple and convenient C++11-interface for sending and receiving requests and can be statically linked to your application.
See [example/cpp](https://github.com/tdlib/td/tree/master/example/cpp) for an example of TDLib usage from C++.
[td_example.cpp](https://github.com/tdlib/td/blob/master/example/cpp/td_example.cpp) contains an example of authorization, processing new incoming messages, getting a list of chats and sending a text message.
See also the source code of [Fernschreiber](https://github.com/Wunderfitz/harbour-fernschreiber) and [Depecher](https://github.com/blacksailer/depecher) Telegram apps for Sailfish OS,
[TELEports](https://gitlab.com/ubports/development/apps/teleports) a Qt-client for Ubuntu Touch, [tdlib-purple](https://github.com/ars3niy/tdlib-purple) - Telegram plugin for Pidgin,
or [MeeGram](https://github.com/qtinsider/meegram2) - a Telegram client for Nokia N9,
[TDLib Native Sciter Extension](https://github.com/EricKotato/TDLibNSE) - a Sciter native extension for TDLib's JSON interface, all of which are based on TDLib.
<a name="swift"></a>
## Using TDLib in Swift projects
TDLib can be used from the Swift programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, visionOS, and macOS.
See [TDLibKit](https://github.com/Swiftgram/TDLibKit), [tdlib-swift](https://github.com/modestman/tdlib-swift), or [TDLib-iOS](https://github.com/leoMehlig/TDLib-iOS), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects.
See also the source code of [Moc](https://github.com/mock-foundation/moc) - a native and powerful macOS and iPadOS Telegram client, optimized for moderating large communities and personal use.
See [example/swift](https://github.com/tdlib/td/tree/master/example/swift) for an example of a macOS Swift application.
<a name="objective-c"></a>
## Using TDLib in Objective-C projects
TDLib can be used from the Objective-C programming language through [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, visionOS, and macOS.
<a name="object-pascal"></a>
## Using TDLib in Object Pascal projects with Delphi and Lazarus
TDLib can be used from the Object Pascal programming language through the [JSON](https://github.com/tdlib/td#using-json).
See [tdlib-delphi](https://github.com/dieletro/tdlib-delphi) for an example of TDLib usage from Delphi.
See [tdlib-lazarus](https://github.com/dieletro/tdlib-lazarus) for an example of TDLib usage from Lazarus.
<a name="dart"></a>
## Using TDLib in Dart projects
TDLib can be used from the Dart programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and a Dart Native Extension or Dart FFI.
See [tdlib-dart](https://github.com/ivk1800/tdlib-dart), which provide convenient TDLib client with automatically generated and fully-documented classes for all TDLib API methods and objects.
See also [dart_tdlib](https://github.com/periodicaidan/dart_tdlib), [flutter_libtdjson](https://github.com/up9cloud/flutter_libtdjson), [Dart wrapper for TDLib](https://github.com/tdlib/td/pull/708/commits/237060abd4c205768153180e9f814298d1aa9d49), or [tdlib_bindings](https://github.com/lesnitsky/tdlib_bindings) for an example of a TDLib Dart bindings through FFI.
See [Telegram Client library](https://github.com/azkadev/telegram_client), [project.scarlet](https://github.com/aaugmentum/project.scarlet), [tdlib](https://github.com/i-Naji/tdlib),
[tdlib-dart](https://github.com/drewpayment/tdlib-dart), [FluGram](https://github.com/triedcatched/tdlib-dart), or [telegram-service](https://github.com/igorder-dev/telegram-service) for examples of using TDLib from Dart.
See also [telegram-flutter](https://github.com/ivk1800/telegram-flutter) - Telegram client written in Dart, and [f-Telegram](https://github.com/evgfilim1/ftg) - Flutter Telegram client.
<a name="rust"></a>
## Using TDLib in Rust projects
TDLib can be used from the Rust programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [rust-tdlib](https://github.com/antonio-antuan/rust-tdlib), or [tdlib](https://github.com/paper-plane-developers/tdlib-rs), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects.
See [rtdlib](https://github.com/fewensa/rtdlib), [tdlib-rs](https://github.com/d653/tdlib-rs), [tdlib-futures](https://github.com/yuri91/tdlib-futures),
[tdlib-sys](https://github.com/nuxeh/tdlib-sys), [tdjson-rs](https://github.com/mersinvald/tdjson-rs), [rust-tdlib](https://github.com/vhaoran/rust-tdlib), or [tdlib-json-sys](https://github.com/aykxt/tdlib-json-sys) for examples of TDLib Rust bindings.
Also, see [Paper Plane](https://github.com/paper-plane-developers/paper-plane) a Telegram client written in Rust and GTK.
<a name="erlang"></a>
## Using TDLib in Erlang projects
TDLib can be used from the Erlang programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [erl-tdlib](https://github.com/lattenwald/erl-tdlib) for an example of TDLib Erlang bindings.
<a name="php"></a>
## Using TDLib in PHP projects
If you use modern PHP >= 7.4, you can use TDLib via a PHP FFI extension. For example, take a look at [ffi-tdlib](https://github.com/aurimasniekis/php-ffi-tdlib), or [tdlib-php-ffi](https://github.com/thisismzm/tdlib-php-ffi) - FFI-based TDLib wrappers.
See also [tdlib-schema](https://github.com/aurimasniekis/php-tdlib-schema) - a generator for TDLib API classes.
For older PHP versions you can use TDLib by wrapping its functionality in a PHP extension.
See [phptdlib](https://github.com/yaroslavche/phptdlib), [tdlib](https://github.com/aurimasniekis/php-ext-tdlib), or [PIF-TDPony](https://github.com/danog/pif-tdpony)
for examples of such extensions which provide access to TDLib from PHP.
See [tdlib-bundle](https://github.com/yaroslavche/tdlib-bundle) a Symfony bundle based on [phptdlib](https://github.com/yaroslavche/phptdlib).
<a name="lua"></a>
## Using TDLib in Lua projects
TDLib can be used from the Lua programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [luajit-tdlib](https://github.com/Rami-Sabbagh/luajit-tdlib), [tdlua](https://github.com/giuseppeM99/tdlua), or
[luajit-tdlib](https://github.com/Playermet/luajit-tdlib) for examples of TDLib Lua bindings and basic usage examples.
See also [tdbot](https://github.com/vysheng/tdbot), which makes all TDLib features available from Lua scripts.
<a name="d"></a>
## Using TDLib in D projects
TDLib can be used from the D programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [d-tdlib-service](https://github.com/Lord-Evil/d-tdlib-service) for an example of TDLib D bindings.
<a name="ruby"></a>
## Using TDLib in Ruby projects
TDLib can be used from the Ruby programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [tdlib-ruby](https://github.com/southbridgeio/tdlib-ruby) for examples of Ruby bindings and a client for TDLib.
<a name="Crystal"></a>
## Using TDLib in Crystal projects
TDLib can be used from the Crystal programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [Proton](https://github.com/protoncr/proton) for examples of Crystal bindings with automatically generated types for all TDLib API methods and objects.
<a name="haskell"></a>
## Using TDLib in Haskell projects
TDLib can be used from the Haskell programming language.
See [haskell-tdlib](https://github.com/mejgun/haskell-tdlib) or [tdlib](https://github.com/poscat0x04/tdlib) for examples of such usage and Haskell wrappers for TDLib.
This library contains automatically generated Haskell types for all TDLib API methods and objects.
<a name="nim"></a>
## Using TDLib in Nim projects
TDLib can be used from the Nim programming language.
See [telenim](https://github.com/Ethosa/telenim) for example of such usage and a Nim wrapper for TDLib.
<a name="clojure"></a>
## Using TDLib in Clojure projects
TDLib can be used from the Clojure programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [clojure-tdlib-json-wrapper](https://github.com/MityaSaray/clojure-tdlib-json) for an example of TDLib Clojure bindings.
<a name="emacslisp"></a>
## Using TDLib in Emacs Lisp projects
TDLib can be used from the Emacs Lisp programming language.
See [telega.el](https://github.com/zevlg/telega.el) for an example of a GNU Emacs Telegram client.
<a name="elixir"></a>
## Using TDLib in Elixir projects
TDLib can be used from the Elixir programming language.
See [Elixir TDLib](https://github.com/QuantLayer/elixir-tdlib) for an example of such usage and an Elixir client for TDLib.
The library contains automatically generated and fully-documented classes for all TDLib API methods and objects.
<a name="vala"></a>
## Using TDLib in Vala projects
TDLib can be used from the Vala programming language.
See [TDLib Vala](https://github.com/AYMENJD/td-vala) for an example of such usage.
<a name="1s"></a>
## Using TDLib from 1С:Enterprise
TDLib can be used from the 1С programming language.
See [TDLib bindings for 1С:Enterprise](https://github.com/Infactum/telegram-native) and [e1c.tAgents](https://github.com/fedbka/e1c.tAgents) for examples of such usage.
<a name="c"></a>
## Using TDLib in C projects
TDLib can be used from the C programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
See [easy-tg](https://github.com/Trumeet/easy-tg) for an example of such usage.
You can also try to use our [C](https://github.com/tdlib/td/blob/master/td/telegram/td_c_client.h) client, which was used by the private TDLib-based version of [telegram-cli](https://github.com/vysheng/tg).
<a name="g"></a>
## Using TDLib from G projects
TDLib can be used from the G graphical programming language in LabVIEW development environment.
See [TDLib bindings for LabVIEW](https://github.com/IvanLisRus/Telegram-Client_TDLib) for examples of such usage.
<a name="other"></a>
## Using TDLib from other programming languages
You can use TDLib from any other programming language using [tdbot](https://github.com/vysheng/tdbot) or [TDLib JSON CLI](https://github.com/oott123/tdlib-json-cli),
which provide a command line tool for interaction with TDLIb using the [JSON](https://github.com/tdlib/td#using-json) interface through stdin and stdout.
You can use this method to use TDLib, for example, from Brainfuck (unfortunately, we haven't seen examples of sending a Telegram message through TDLib on Brainfuck yet).
Alternatively, you can use the TDLib [JSON](https://github.com/tdlib/td#using-json) interface directly from your programming language.
Feel free to create an issue, if you have created a valuable TDLib binding or a TDLib client in some programming language and want it to be added to this list of examples.

View File

@ -1,3 +0,0 @@
/SDK*
/tdlib*
/third-party*

View File

@ -1,51 +0,0 @@
<?php
if ($argc !== 2) {
exit();
}
$file = file_get_contents($argv[1]);
if (strpos($file, 'androidx.annotation.IntDef') !== false) {
exit();
}
$file = str_replace('import androidx.annotation.Nullable;', 'import androidx.annotation.IntDef;'.PHP_EOL.
'import androidx.annotation.Nullable;'.PHP_EOL.
PHP_EOL.
'import java.lang.annotation.Retention;'.PHP_EOL.
'import java.lang.annotation.RetentionPolicy;'.PHP_EOL, $file);
preg_match_all('/public static class ([A-Za-z0-9]+) extends ([A-Za-z0-9]+)/', $file, $matches, PREG_SET_ORDER);
$children = [];
foreach ($matches as $val) {
if ($val[2] === 'Object') {
continue;
}
$children[$val[2]][] = ' '.$val[1].'.CONSTRUCTOR';
}
$file = preg_replace_callback('/public abstract static class ([A-Za-z0-9]+)(<R extends Object>)? extends Object [{]/',
function ($val) use ($children) {
$values = implode(','.PHP_EOL, $children[$val[1]]);
return $val[0].<<<EOL
/**
* Describes possible values returned by getConstructor().
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
$values
})
public @interface Constructors {}
/**
* @return identifier uniquely determining type of the object.
*/
@Constructors
@Override
public abstract int getConstructor();
EOL;
},
$file);
file_put_contents($argv[1], $file);

View File

@ -1,72 +0,0 @@
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(TdAndroid VERSION 1.0 LANGUAGES CXX)
set(TD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
option(TD_ANDROID_JSON "Use \"ON\" to build JSON interface.")
option(TD_ANDROID_JSON_JAVA "Use \"ON\" to build Java wrapper for JSON API.")
if (TD_ANDROID_JSON)
if (CMAKE_CROSSCOMPILING)
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -flto=thin -Oz")
list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
endif()
add_subdirectory(${TD_DIR} td)
return()
endif()
if (NOT TD_ANDROID_JSON_JAVA)
option(TD_ENABLE_JNI "Enable JNI-compatible TDLib API" ON)
endif()
if (CMAKE_CROSSCOMPILING)
set(CMAKE_MODULE_PATH "${TD_DIR}/CMake")
include(TdSetUpCompiler)
td_set_up_compiler()
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -flto=thin -Oz")
list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
add_subdirectory(${TD_DIR} td)
add_library(tdjni SHARED "${TD_DIR}/example/java/td_jni.cpp")
if (TD_ANDROID_JSON_JAVA)
target_link_libraries(tdjni PRIVATE Td::TdJsonStatic)
target_compile_definitions(tdjni PRIVATE TD_JSON_JAVA=1)
set_target_properties(tdjni PROPERTIES OUTPUT_NAME "tdjsonjava")
else()
target_link_libraries(tdjni PRIVATE Td::TdStatic)
endif()
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="org/drinkless/tdlib")
add_custom_command(TARGET tdjni POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rename $<TARGET_FILE:tdjni> $<TARGET_FILE:tdjni>.debug
COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded $<TARGET_FILE:tdjni>.debug -o $<TARGET_FILE:tdjni>)
else()
add_subdirectory(${TD_DIR} td)
if (TD_ANDROID_JSON_JAVA)
return()
endif()
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
set(TD_API_JAVA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${TD_API_JAVA_PACKAGE}/TdApi.java")
set(TD_API_TLO_PATH "${TD_DIR}/td/generate/auto/tlo/td_api.tlo")
set(TD_API_TL_PATH "${TD_DIR}/td/generate/scheme/td_api.tl")
set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH "${TD_DIR}/td/generate/JavadocTlDocumentationGenerator.php")
set(GENERATE_JAVA_CMD td_generate_java_api TdApi ${TD_API_TLO_PATH} ${CMAKE_CURRENT_SOURCE_DIR} ${TD_API_JAVA_PACKAGE})
if (PHP_EXECUTABLE)
set(GENERATE_JAVA_CMD ${GENERATE_JAVA_CMD} &&
${PHP_EXECUTABLE} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH} ${TD_API_TL_PATH} ${TD_API_JAVA_PATH} androidx.annotation.Nullable @Nullable &&
${PHP_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/AddIntDef.php ${TD_API_JAVA_PATH})
endif()
file(MAKE_DIRECTORY ${TD_API_JAVA_PACKAGE})
add_custom_target(tl_generate_java
COMMAND ${GENERATE_JAVA_CMD}
COMMENT "Generate Java TL source files"
DEPENDS td_generate_java_api tl_generate_tlo ${TD_API_TLO_PATH} ${TD_API_TL_PATH}
)
endif()

Some files were not shown because too many files have changed in this diff Show More