Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,103 @@
import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
public enum CachedChannelAdminRank: Codable, Equatable {
case owner
case admin
case custom(String)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
let value: Int32 = try container.decode(Int32.self, forKey: "v")
switch value {
case 0:
self = .owner
case 1:
self = .admin
case 2:
self = .custom(try container.decode(String.self, forKey: "s"))
default:
self = .admin
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
switch self {
case .owner:
try container.encode(0 as Int32, forKey: "v")
case .admin:
try container.encode(1 as Int32, forKey: "v")
case let .custom(rank):
try container.encode(2 as Int32, forKey: "v")
try container.encode(rank, forKey: "s")
}
}
}
public final class CachedChannelAdminRanks: Codable {
private struct DictionaryKey: Codable, Hashable {
var key: PeerId
init(_ key: PeerId) {
self.key = key
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.key = PeerId(try container.decode(Int64.self, forKey: "k"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.key.toInt64(), forKey: "k")
}
}
public let ranks: [PeerId: CachedChannelAdminRank]
public init(ranks: [PeerId: CachedChannelAdminRank]) {
self.ranks = ranks
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
let dict = try container.decode([DictionaryKey: CachedChannelAdminRank].self, forKey: "ranks")
var mappedDict: [PeerId: CachedChannelAdminRank] = [:]
for (key, value) in dict {
mappedDict[key.key] = value
}
self.ranks = mappedDict
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
var mappedDict: [DictionaryKey: CachedChannelAdminRank] = [:]
for (k, v) in self.ranks {
mappedDict[DictionaryKey(k)] = v
}
try container.encode(mappedDict, forKey: "ranks")
}
public static func cacheKey(peerId: PeerId) -> ValueBoxKey {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: peerId.toInt64())
return key
}
}
public func cachedChannelAdminRanksEntryId(peerId: PeerId) -> ItemCacheEntryId {
return ItemCacheEntryId(collectionId: 100, key: CachedChannelAdminRanks.cacheKey(peerId: peerId))
}
func updateCachedChannelAdminRanks(engine: TelegramEngine, peerId: PeerId, ranks: Dictionary<PeerId, CachedChannelAdminRank>) -> Signal<Never, NoError> {
return engine.itemCache.put(collectionId: 100, id: CachedChannelAdminRanks.cacheKey(peerId: peerId), item: CachedChannelAdminRanks(ranks: ranks))
}
@@ -0,0 +1,824 @@
import Foundation
import TelegramCore
import Postbox
import SwiftSignalKit
private let initialBatchSize: Int32 = 64
private let defaultEmptyTimeout: Double = 2.0 * 60.0
private let headUpdateTimeout: Double = 30.0
private let requestBatchSize: Int32 = 64
public enum ChannelMemberListLoadingState: Equatable {
case loading(initial: Bool)
case ready(hasMore: Bool)
}
public extension ChannelParticipant {
var adminInfo: ChannelParticipantAdminInfo? {
switch self {
case .creator:
return nil
case let .member(_, _, adminInfo, _, _, _):
return adminInfo
}
}
var banInfo: ChannelParticipantBannedInfo? {
switch self {
case .creator:
return nil
case let .member(_, _, _, banInfo, _, _):
return banInfo
}
}
func canBeBannedBy(peerId: PeerId) -> Bool {
switch self {
case .creator:
return false
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
if adminInfo.promotedBy != peerId {
return false
}
}
}
return true
}
}
public struct ChannelMemberListState {
public var list: [RenderedChannelParticipant]
public var peerStoryStats: [EnginePeer.Id: PeerStoryStats]
public var loadingState: ChannelMemberListLoadingState
}
enum ChannelMemberListCategory {
case recent
case recentSearch(String)
case mentions(MessageId?, String?)
case admins(String?)
case contacts(String?)
case bots(String?)
case restricted(String?)
case banned(String?)
}
private protocol ChannelMemberCategoryListContext {
//var listStateValue: ChannelMemberListState { get }
var listState: Signal<ChannelMemberListState, NoError> { get }
func loadMore()
func reset(_ force: Bool)
func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)])
func forceUpdateHead()
}
private func isParticipantMember(_ participant: ChannelParticipant, infoIsMember: Bool?) -> Bool {
if let banInfo = participant.banInfo {
return !banInfo.rights.flags.contains(.banReadMessages) && banInfo.isMember
} else if let infoIsMember = infoIsMember {
return infoIsMember
} else {
return true
}
}
private extension CachedChannelAdminRank {
init(participant: ChannelParticipant) {
switch participant {
case let .creator(_, _, rank):
if let rank = rank {
self = .custom(rank)
} else {
self = .owner
}
case let .member(_, _, _, _, rank, _):
if let rank = rank {
self = .custom(rank)
} else {
self = .admin
}
}
}
}
private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategoryListContext {
private let engine: TelegramEngine
private let postbox: Postbox
private let network: Network
private let accountPeerId: PeerId
private let peerId: PeerId
private let batchCount: Int32?
private let category: ChannelMemberListCategory
var listStateValue: ChannelMemberListState {
didSet {
self.listStatePromise.set(.single(self.listStateValue))
if case .admins(nil) = self.category, case .ready = self.listStateValue.loadingState {
let ranks: [PeerId: CachedChannelAdminRank] = self.listStateValue.list.reduce([:]) { (ranks, participant) in
var ranks = ranks
ranks[participant.participant.peerId] = CachedChannelAdminRank(participant: participant.participant)
return ranks
}
let previousRanks: [PeerId: CachedChannelAdminRank] = oldValue.list.reduce([:]) { (ranks, participant) in
var ranks = ranks
ranks[participant.participant.peerId] = CachedChannelAdminRank(participant: participant.participant)
return ranks
}
if ranks != previousRanks {
let _ = updateCachedChannelAdminRanks(engine: self.engine, peerId: self.peerId, ranks: ranks).start()
}
}
}
}
private var listStatePromise: Promise<ChannelMemberListState>
var listState: Signal<ChannelMemberListState, NoError> {
let postbox = self.postbox
return self.listStatePromise.get()
|> mapToSignal { state -> Signal<ChannelMemberListState, NoError> in
let key: PostboxViewKey = .peerStoryStats(peerIds: Set(state.list.map(\.peer.id)))
return postbox.combinedView(keys: [key])
|> map { views -> ChannelMemberListState in
var state = state
if let view = views.views[key] as? PeerStoryStatsView {
state.peerStoryStats = view.storyStats
}
return state
}
}
}
private let loadingDisposable = MetaDisposable()
private let headUpdateDisposable = MetaDisposable()
private var headUpdateTimer: SwiftSignalKit.Timer?
init(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, batchCount: Int32?, category: ChannelMemberListCategory) {
self.engine = engine
self.postbox = postbox
self.network = network
self.accountPeerId = accountPeerId
self.peerId = peerId
self.batchCount = batchCount
self.category = category
self.listStateValue = ChannelMemberListState(list: [], peerStoryStats: [:], loadingState: .ready(hasMore: true))
self.listStatePromise = Promise(self.listStateValue)
self.loadMoreInternal(initial: true)
}
deinit {
self.loadingDisposable.dispose()
self.headUpdateDisposable.dispose()
self.headUpdateTimer?.invalidate()
}
func loadMore() {
self.loadMoreInternal(initial: false)
}
private func loadMoreInternal(initial: Bool) {
guard case .ready(true) = self.listStateValue.loadingState else {
return
}
let loadCount: Int32
if case .ready(true) = self.listStateValue.loadingState, self.listStateValue.list.isEmpty {
loadCount = self.batchCount ?? initialBatchSize
} else {
loadCount = self.batchCount ?? requestBatchSize
}
self.listStateValue.loadingState = .loading(initial: initial)
self.loadingDisposable.set((self.loadMoreSignal(count: loadCount)
|> deliverOnMainQueue).start(next: { [weak self] members in
self?.appendMembersAndFinishLoading(members)
}))
}
func reset(_ force: Bool) {
if case .loading = self.listStateValue.loadingState, self.listStateValue.list.isEmpty {
} else {
var list = self.listStateValue.list
var loadingState: ChannelMemberListLoadingState = .ready(hasMore: true)
let batchSize = self.batchCount ?? initialBatchSize
if list.count > Int(batchSize) && !force {
list.removeSubrange(Int(batchSize) ..< list.count)
loadingState = .ready(hasMore: true)
}
self.loadingDisposable.set(nil)
var listState = self.listStateValue
listState.loadingState = loadingState
listState.list = list
self.listStateValue = listState
}
}
private func loadSignal(offset: Int32, count: Int32, hash: Int64) -> Signal<[RenderedChannelParticipant]?, NoError> {
let requestCategory: ChannelMembersCategory
var adminQuery: String? = nil
switch self.category {
case .recent:
requestCategory = .recent(.all)
case let .recentSearch(query):
requestCategory = .recent(.search(query))
case let .mentions(threadId, query):
if let query = query, !query.isEmpty {
requestCategory = .mentions(threadId: threadId, filter: .search(query))
} else {
requestCategory = .mentions(threadId: threadId, filter: .all)
}
case let .admins(query):
requestCategory = .admins
adminQuery = query
case let .contacts(query):
requestCategory = .contacts(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all)
case let .bots(query):
requestCategory = .bots(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all)
case let .restricted(query):
requestCategory = .restricted(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all)
case let .banned(query):
requestCategory = .banned(query.flatMap(ChannelMembersCategoryFilter.search) ?? .all)
}
return self.engine.peers.channelMembers(peerId: self.peerId, category: requestCategory, offset: offset, limit: count, hash: hash) |> map { members in
switch requestCategory {
case .admins:
if let query = adminQuery {
return members?.filter({$0.peer.debugDisplayTitle.lowercased().components(separatedBy: " ").contains(where: {$0.hasPrefix(query.lowercased())})})
}
default:
break
}
return members
}
}
private func loadMoreSignal(count: Int32) -> Signal<[RenderedChannelParticipant], NoError> {
return self.loadSignal(offset: Int32(self.listStateValue.list.count), count: count, hash: 0)
|> map { value -> [RenderedChannelParticipant] in
return value ?? []
}
}
private func updateHeadMembers(_ headMembers: [RenderedChannelParticipant]?) {
if let headMembers = headMembers {
var existingIds = Set<PeerId>()
var list = headMembers
for member in list {
existingIds.insert(member.peer.id)
}
for member in self.listStateValue.list {
if !existingIds.contains(member.peer.id) {
list.append(member)
}
}
self.loadingDisposable.set(nil)
var listState = self.listStateValue
listState.list = list
self.listStateValue = listState
if case .loading = self.listStateValue.loadingState {
self.loadMore()
}
}
self.headUpdateTimer?.invalidate()
self.headUpdateTimer = nil
self.checkUpdateHead()
}
private func appendMembersAndFinishLoading(_ members: [RenderedChannelParticipant]) {
var firstLoad = false
if case .loading = self.listStateValue.loadingState, self.listStateValue.list.isEmpty {
firstLoad = true
}
var existingIds = Set<PeerId>()
var list = self.listStateValue.list
for member in list {
existingIds.insert(member.peer.id)
}
for member in members {
if !existingIds.contains(member.peer.id) {
list.append(member)
}
}
var listState = self.listStateValue
listState.loadingState = .ready(hasMore: members.count >= requestBatchSize)
listState.list = list
self.listStateValue = listState
if firstLoad {
self.checkUpdateHead()
}
}
func forceUpdateHead() {
self.headUpdateTimer = nil
self.checkUpdateHead()
}
private func checkUpdateHead() {
if self.listStateValue.list.isEmpty {
return
}
if self.headUpdateTimer == nil {
let headUpdateTimer = SwiftSignalKit.Timer(timeout: headUpdateTimeout, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
var acc: UInt64 = 0
let batchSize = strongSelf.batchCount ?? initialBatchSize
for i in 0 ..< min(strongSelf.listStateValue.list.count, Int(batchSize)) {
let peerId = strongSelf.listStateValue.list[i].peer.id
combineInt64Hash(&acc, with: peerId)
}
let hashResult = finalizeInt64Hash(acc)
strongSelf.headUpdateDisposable.set((strongSelf.loadSignal(offset: 0, count: batchSize, hash: hashResult)
|> deliverOnMainQueue).start(next: { members in
self?.updateHeadMembers(members)
}))
}, queue: Queue.mainQueue())
self.headUpdateTimer = headUpdateTimer
headUpdateTimer.start()
}
}
fileprivate func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) {
var list = self.listStateValue.list
var updatedList = false
for (maybePrevious, updated, infoIsMember) in updates {
var previous: ChannelParticipant? = maybePrevious
if let participantId = maybePrevious?.peerId ?? updated?.peer.id {
inner: for participant in list {
if participant.peer.id == participantId {
previous = participant.participant
break inner
}
}
}
switch self.category {
case let .admins(query):
if let updated = updated, (query == nil || updated.peer.indexName.matchesByTokens(query!)) {
if case let .member(_, _, adminInfo, _, _, _) = updated.participant, adminInfo == nil {
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list.remove(at: i)
updatedList = true
break loop
}
}
} else {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
list.insert(updated, at: 0)
updatedList = true
}
}
} else if let previous = previous, let _ = previous.adminInfo {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
if let updated = updated, case .creator = updated.participant {
list.insert(updated, at: 0)
updatedList = true
}
}
case .restricted:
if let updated = updated, let banInfo = updated.participant.banInfo, !banInfo.rights.flags.contains(.banReadMessages) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
list.insert(updated, at: 0)
updatedList = true
}
} else if let previous = previous, let banInfo = previous.banInfo, !banInfo.rights.flags.contains(.banReadMessages) {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
}
case .banned:
if let updated = updated, let banInfo = updated.participant.banInfo, banInfo.rights.flags.contains(.banReadMessages) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
list.insert(updated, at: 0)
updatedList = true
}
} else if let previous = previous, let banInfo = previous.banInfo, banInfo.rights.flags.contains(.banReadMessages) {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
}
case .recent:
if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
list.insert(updated, at: 0)
updatedList = true
}
} else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
}
case let .contacts(query):
if query == nil {
if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
//list.insert(updated, at: 0)
//updatedList = true
}
} else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
}
}
case let .bots(query):
if query == nil {
if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
//list.insert(updated, at: 0)
//updatedList = true
}
} else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
}
}
case let .recentSearch(query):
if let updated = updated, isParticipantMember(updated.participant, infoIsMember: infoIsMember), updated.peer.indexName.matchesByTokens(query) {
var found = false
loop: for i in 0 ..< list.count {
if list[i].peer.id == updated.peer.id {
list[i] = updated
found = true
updatedList = true
break loop
}
}
if !found {
list.insert(updated, at: 0)
updatedList = true
}
} else if let previous = previous, isParticipantMember(previous, infoIsMember: nil) {
loop: for i in 0 ..< list.count {
if list[i].peer.id == previous.peerId {
list.remove(at: i)
updatedList = true
break loop
}
}
}
case .mentions:
break
}
}
if updatedList {
var listState = self.listStateValue
listState.list = list
self.listStateValue = listState
}
}
}
private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategoryListContext {
private var contexts: [ChannelMemberSingleCategoryListContext] = []
/*var listStateValue: ChannelMemberListState {
return ChannelMemberMultiCategoryListContext.reduceListStates(self.contexts.map { $0.listStateValue })
}*/
private static func reduceListStates(_ listStates: [ChannelMemberListState]) -> ChannelMemberListState {
var allReady = true
for listState in listStates {
if case .loading(true) = listState.loadingState, listState.list.isEmpty {
allReady = false
break
}
}
if !allReady {
return ChannelMemberListState(list: [], peerStoryStats: [:], loadingState: .loading(initial: true))
}
var list: [RenderedChannelParticipant] = []
var existingIds = Set<PeerId>()
var loadingState: ChannelMemberListLoadingState = .ready(hasMore: false)
var peerStoryStats: [PeerId: PeerStoryStats] = [:]
loop: for i in 0 ..< listStates.count {
for item in listStates[i].list {
if !existingIds.contains(item.peer.id) {
existingIds.insert(item.peer.id)
list.append(item)
}
}
for (id, value) in listStates[i].peerStoryStats {
peerStoryStats[id] = value
}
switch listStates[i].loadingState {
case let .loading(initial):
loadingState = .loading(initial: initial)
break loop
case let .ready(hasMore):
if hasMore {
loadingState = .ready(hasMore: true)
break loop
}
}
}
return ChannelMemberListState(list: list, peerStoryStats: peerStoryStats, loadingState: loadingState)
}
var listState: Signal<ChannelMemberListState, NoError> {
let signals: [Signal<ChannelMemberListState, NoError>] = self.contexts.map { context in
return context.listState
}
return combineLatest(signals) |> map { listStates -> ChannelMemberListState in
return ChannelMemberMultiCategoryListContext.reduceListStates(listStates)
}
}
init(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, categories: [ChannelMemberListCategory]) {
self.contexts = categories.map { category in
return ChannelMemberSingleCategoryListContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, batchCount: nil, category: category)
}
}
func loadMore() {
loop: for context in self.contexts {
switch context.listStateValue.loadingState {
case .loading:
break loop
case let .ready(hasMore):
if hasMore {
context.loadMore()
}
}
}
}
func reset(_ force: Bool) {
for context in self.contexts {
context.reset(force)
}
}
func forceUpdateHead() {
for context in self.contexts {
context.forceUpdateHead()
}
}
func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) {
for context in self.contexts {
context.replayUpdates(updates)
}
}
}
public struct PeerChannelMemberCategoryControl {
let key: PeerChannelMemberContextKey
}
private final class PeerChannelMemberContextWithSubscribers {
let context: ChannelMemberCategoryListContext
private let emptyTimeout: Double
private let subscribers = Bag<(ChannelMemberListState) -> Void>()
private let disposable = MetaDisposable()
private let becameEmpty: () -> Void
private var currentValue: ChannelMemberListState?
private var emptyTimer: SwiftSignalKit.Timer?
init(context: ChannelMemberCategoryListContext, emptyTimeout: Double, becameEmpty: @escaping () -> Void) {
self.context = context
self.emptyTimeout = emptyTimeout
self.becameEmpty = becameEmpty
self.disposable.set((context.listState
|> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self {
strongSelf.currentValue = value
for f in strongSelf.subscribers.copyItems() {
f(value)
}
}
}))
}
deinit {
self.disposable.dispose()
self.emptyTimer?.invalidate()
}
private func resetAndBeginEmptyTimer() {
self.context.reset(false)
self.emptyTimer?.invalidate()
let emptyTimer = SwiftSignalKit.Timer(timeout: self.emptyTimeout, repeat: false, completion: { [weak self] in
if let strongSelf = self {
if strongSelf.subscribers.isEmpty {
strongSelf.becameEmpty()
}
}
}, queue: Queue.mainQueue())
self.emptyTimer = emptyTimer
emptyTimer.start()
}
func subscribe(requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> Disposable {
let wasEmpty = self.subscribers.isEmpty
let index = self.subscribers.add(updated)
if let currentValue = self.currentValue {
updated(currentValue)
}
if wasEmpty {
self.emptyTimer?.invalidate()
if requestUpdate {
self.context.forceUpdateHead()
}
}
return ActionDisposable { [weak self] in
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.subscribers.remove(index)
if strongSelf.subscribers.isEmpty {
strongSelf.resetAndBeginEmptyTimer()
}
}
}
}
}
}
final class PeerChannelMemberCategoriesContext {
private let engine: TelegramEngine
private let postbox: Postbox
private let network: Network
private let accountPeerId: PeerId
private let peerId: PeerId
private let batchCount: Int32?
private var becameEmpty: (Bool) -> Void
private var contexts: [PeerChannelMemberContextKey: PeerChannelMemberContextWithSubscribers] = [:]
init(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, batchCount: Int32?, becameEmpty: @escaping (Bool) -> Void) {
self.engine = engine
self.postbox = postbox
self.network = network
self.accountPeerId = accountPeerId
self.peerId = peerId
self.batchCount = batchCount
self.becameEmpty = becameEmpty
}
func reset(_ key: PeerChannelMemberContextKey) {
for (contextKey, context) in contexts {
if contextKey == key {
context.context.reset(true)
context.context.loadMore()
}
}
}
func getContext(key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) {
assert(Queue.mainQueue().isCurrent())
if let current = self.contexts[key] {
return (current.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key))
}
let context: ChannelMemberCategoryListContext
let emptyTimeout: Double
switch key {
case .admins(nil), .banned(nil), .recentSearch(""), .restricted(nil), .restrictedAndBanned(nil), .recent, .contacts:
emptyTimeout = defaultEmptyTimeout
default:
emptyTimeout = 0.0
}
switch key {
case .recent, .recentSearch, .admins, .contacts, .bots, .mentions:
let mappedCategory: ChannelMemberListCategory
switch key {
case .recent:
mappedCategory = .recent
case let .recentSearch(query):
mappedCategory = .recentSearch(query)
case let .admins(query):
mappedCategory = .admins(query)
case let .contacts(query):
mappedCategory = .contacts(query)
case let .bots(query):
mappedCategory = .bots(query)
case let .mentions(threadId, query):
mappedCategory = .mentions(threadId, query)
default:
mappedCategory = .recent
}
context = ChannelMemberSingleCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, batchCount: self.batchCount, category: mappedCategory)
case let .restrictedAndBanned(query):
context = ChannelMemberMultiCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, categories: [.restricted(query), .banned(query)])
case let .restricted(query):
context = ChannelMemberSingleCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, batchCount: nil, category: .restricted(query))
case let .banned(query):
context = ChannelMemberSingleCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, batchCount: nil, category: .banned(query))
}
let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, emptyTimeout: emptyTimeout, becameEmpty: { [weak self] in
assert(Queue.mainQueue().isCurrent())
if let strongSelf = self {
strongSelf.contexts.removeValue(forKey: key)
}
})
self.contexts[key] = contextWithSubscribers
return (contextWithSubscribers.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key))
}
func loadMore(_ control: PeerChannelMemberCategoryControl) {
assert(Queue.mainQueue().isCurrent())
if let context = self.contexts[control.key] {
context.context.loadMore()
}
}
func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) {
for (_, context) in self.contexts {
context.context.replayUpdates(updates)
}
}
}
@@ -0,0 +1,632 @@
import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
import TelegramStringFormatting
enum PeerChannelMemberContextKey: Equatable, Hashable {
case recent
case recentSearch(String)
case mentions(threadId: MessageId?, query: String?)
case admins(String?)
case contacts(String?)
case bots(String?)
case restrictedAndBanned(String?)
case restricted(String?)
case banned(String?)
}
private final class PeerChannelMembersOnlineContext {
let subscribers = Bag<(Int32) -> Void>()
let disposable: Disposable
var value: Int32?
var emptyTimer: SwiftSignalKit.Timer?
init(disposable: Disposable) {
self.disposable = disposable
}
}
private final class ProfileDataPreloadContext {
let subscribers = Bag<() -> Void>()
let disposable: Disposable
var emptyTimer: SwiftSignalKit.Timer?
init(disposable: Disposable) {
self.disposable = disposable
}
}
private final class ProfileDataPhotoPreloadContext {
let subscribers = Bag<(Any?) -> Void>()
let disposable: Disposable
var value: Any?
var emptyTimer: SwiftSignalKit.Timer?
init(disposable: Disposable) {
self.disposable = disposable
}
}
private final class PeerChannelMemberCategoriesContextsManagerImpl {
fileprivate var contexts: [PeerId: PeerChannelMemberCategoriesContext] = [:]
fileprivate var onlineContexts: [PeerId: PeerChannelMembersOnlineContext] = [:]
fileprivate var profileDataPreloadContexts: [PeerId: ProfileDataPreloadContext] = [:]
fileprivate var profileDataPhotoPreloadContexts: [PeerId: ProfileDataPhotoPreloadContext] = [:]
func getContext(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, count: Int32? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) {
if let current = self.contexts[peerId] {
return current.getContext(key: key, requestUpdate: requestUpdate, updated: updated)
} else {
var becameEmptyImpl: ((Bool) -> Void)?
let context = PeerChannelMemberCategoriesContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, batchCount: count, becameEmpty: { value in
becameEmptyImpl?(value)
})
becameEmptyImpl = { [weak self, weak context] value in
assert(Queue.mainQueue().isCurrent())
if let strongSelf = self {
if let current = strongSelf.contexts[peerId], current === context {
strongSelf.contexts.removeValue(forKey: peerId)
}
}
}
self.contexts[peerId] = context
return context.getContext(key: key, requestUpdate: requestUpdate, updated: updated)
}
}
func recentOnline(account: Account, accountPeerId: PeerId, peerId: PeerId, updated: @escaping (Int32) -> Void) -> Disposable {
let context: PeerChannelMembersOnlineContext
if let current = self.onlineContexts[peerId] {
context = current
} else {
let disposable = MetaDisposable()
context = PeerChannelMembersOnlineContext(disposable: disposable)
self.onlineContexts[peerId] = context
let signal = (
TelegramEngine(account: account).peers.chatOnlineMembers(peerId: peerId)
|> then(
.complete()
|> delay(30.0, queue: .mainQueue())
)
) |> restart
disposable.set(signal.start(next: { [weak context] value in
guard let context = context else {
return
}
context.value = value
for f in context.subscribers.copyItems() {
f(value)
}
}))
}
if let emptyTimer = context.emptyTimer {
emptyTimer.invalidate()
context.emptyTimer = nil
}
let index = context.subscribers.add({ next in
updated(next)
})
updated(context.value ?? 0)
return ActionDisposable { [weak self, weak context] in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if let current = strongSelf.onlineContexts[peerId], let context = context, current === context {
current.subscribers.remove(index)
if current.subscribers.isEmpty {
if current.emptyTimer == nil {
let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in
if let current = strongSelf.onlineContexts[peerId], let context = context, current === context {
if current.subscribers.isEmpty {
strongSelf.onlineContexts.removeValue(forKey: peerId)
current.disposable.dispose()
}
}
}, queue: Queue.mainQueue())
current.emptyTimer = timer
timer.start()
}
}
}
}
}
}
func loadMore(peerId: PeerId, control: PeerChannelMemberCategoryControl) {
if let context = self.contexts[peerId] {
context.loadMore(control)
}
}
func reset(peerId: PeerId, control: PeerChannelMemberCategoryControl) {
if let context = self.contexts[peerId] {
context.reset(control.key)
}
}
func profileData(postbox: Postbox, network: Network, peerId: PeerId, customData: Signal<Never, NoError>?) -> Disposable {
let context: ProfileDataPreloadContext
if let current = self.profileDataPreloadContexts[peerId] {
context = current
} else {
let disposable = DisposableSet()
context = ProfileDataPreloadContext(disposable: disposable)
self.profileDataPreloadContexts[peerId] = context
if let customData = customData {
disposable.add(customData.startStrict())
}
/*disposable.set(signal.start(next: { [weak context] value in
guard let context = context else {
return
}
context.value = value
for f in context.subscribers.copyItems() {
f(value)
}
}))*/
}
if let emptyTimer = context.emptyTimer {
emptyTimer.invalidate()
context.emptyTimer = nil
}
let index = context.subscribers.add({
})
//updated(context.value ?? 0)
return ActionDisposable { [weak self, weak context] in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if let current = strongSelf.profileDataPreloadContexts[peerId], let context = context, current === context {
current.subscribers.remove(index)
if current.subscribers.isEmpty {
if current.emptyTimer == nil {
let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak context] in
if let current = strongSelf.profileDataPreloadContexts[peerId], let context = context, current === context {
if current.subscribers.isEmpty {
strongSelf.profileDataPreloadContexts.removeValue(forKey: peerId)
current.disposable.dispose()
}
}
}, queue: Queue.mainQueue())
current.emptyTimer = timer
timer.start()
}
}
}
}
}
}
func profilePhotos(postbox: Postbox, network: Network, peerId: PeerId, fetch: Signal<Any, NoError>, updated: @escaping (Any?) -> Void) -> Disposable {
let context: ProfileDataPhotoPreloadContext
if let current = self.profileDataPhotoPreloadContexts[peerId] {
context = current
} else {
let disposable = MetaDisposable()
context = ProfileDataPhotoPreloadContext(disposable: disposable)
self.profileDataPhotoPreloadContexts[peerId] = context
disposable.set(fetch.start(next: { [weak context] value in
Queue.mainQueue().async {
guard let context = context else {
return
}
context.value = value
for f in context.subscribers.copyItems() {
f(value)
}
}
}))
}
if let emptyTimer = context.emptyTimer {
emptyTimer.invalidate()
context.emptyTimer = nil
}
let index = context.subscribers.add({ next in
updated(next)
})
updated(context.value)
return ActionDisposable { [weak self, weak context] in
Queue.mainQueue().async {
guard let strongSelf = self else {
return
}
if let current = strongSelf.profileDataPhotoPreloadContexts[peerId], let context = context, current === context {
current.subscribers.remove(index)
if current.subscribers.isEmpty {
if current.emptyTimer == nil {
let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in
if let current = strongSelf.profileDataPhotoPreloadContexts[peerId], let context = context, current === context {
if current.subscribers.isEmpty {
strongSelf.profileDataPhotoPreloadContexts.removeValue(forKey: peerId)
current.disposable.dispose()
}
}
}, queue: Queue.mainQueue())
current.emptyTimer = timer
timer.start()
}
}
}
}
}
}
}
public final class PeerChannelMemberCategoriesContextsManager {
private let impl: QueueLocalObject<PeerChannelMemberCategoriesContextsManagerImpl>
private let removedChannelMembersPipe = ValuePipe<[(PeerId, PeerId)]>()
public var removedChannelMembers: Signal<[(PeerId, PeerId)], NoError> {
return self.removedChannelMembersPipe.signal()
}
public init() {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return PeerChannelMemberCategoriesContextsManagerImpl()
})
}
public func loadMore(peerId: PeerId, control: PeerChannelMemberCategoryControl?) {
if let control = control {
self.impl.with { impl in
impl.loadMore(peerId: peerId, control: control)
}
}
}
public func reset(peerId: PeerId, control: PeerChannelMemberCategoryControl) {
self.impl.with { impl in
impl.reset(peerId: peerId, control: control)
}
}
private func getContext(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, count: Int32? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
assert(Queue.mainQueue().isCurrent())
let (disposable, control) = self.impl.syncWith({ impl in
return impl.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, count: count, updated: updated)
})
return (disposable, control)
}
public func externallyAdded(peerId: PeerId, participant: RenderedChannelParticipant) {
self.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if contextPeerId == peerId {
context.replayUpdates([(nil, participant, nil)])
}
}
}
}
public func externallyRemoved(peerId: PeerId, memberId: PeerId) {
self.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if contextPeerId == peerId {
context.replayUpdates([(.member(id: memberId, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), nil, nil)])
}
}
}
}
public func recent(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, requestUpdate: Bool = true, count: Int32? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
let key: PeerChannelMemberContextKey
if let searchQuery = searchQuery {
key = .recentSearch(searchQuery)
} else {
key = .recent
}
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, count: count, updated: updated)
}
public func mentions(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadMessageId: MessageId?, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
let key: PeerChannelMemberContextKey = .mentions(threadId: threadMessageId, query: searchQuery)
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated)
}
public func admins(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated)
}
public func contacts(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .contacts(searchQuery), requestUpdate: true, updated: updated)
}
public func bots(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .bots(searchQuery), requestUpdate: true, updated: updated)
}
public func restricted(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restricted(searchQuery), requestUpdate: true, updated: updated)
}
public func banned(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .banned(searchQuery), requestUpdate: true, updated: updated)
}
public func restrictedAndBanned(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated)
}
public func updateMemberBannedRights(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChatBannedRights?) -> Signal<Void, NoError> {
return engine.peers.updateChannelMemberBannedRights(peerId: peerId, memberId: memberId, rights: bannedRights)
|> deliverOnMainQueue
|> beforeNext { [weak self] (previous, updated, isMember) in
if let strongSelf = self {
strongSelf.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.replayUpdates([(previous, updated, isMember)])
}
}
}
if !isMember {
strongSelf.removedChannelMembersPipe.putNext([(peerId, memberId)])
}
}
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
public func updateMemberAdminRights(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, adminRights: TelegramChatAdminRights?, rank: String?) -> Signal<Void, UpdateChannelAdminRightsError> {
return engine.peers.updateChannelAdminRights(peerId: peerId, adminId: memberId, rights: adminRights, rank: rank)
|> map(Optional.init)
|> deliverOnMainQueue
|> beforeNext { [weak self] result in
if let strongSelf = self, let (previous, updated) = result {
strongSelf.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.replayUpdates([(previous, updated, nil)])
}
}
}
}
}
|> mapToSignal { _ -> Signal<Void, UpdateChannelAdminRightsError> in
return .complete()
}
}
public func transferOwnership(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, password: String) -> Signal<Void, ChannelOwnershipTransferError> {
return engine.peers.updateChannelOwnership(channelId: peerId, memberId: memberId, password: password)
|> map(Optional.init)
|> deliverOnMainQueue
|> beforeNext { [weak self] results in
if let strongSelf = self, let results = results {
strongSelf.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.replayUpdates(results.map { ($0.0, $0.1, nil) })
}
}
}
}
}
|> mapToSignal { _ -> Signal<Void, ChannelOwnershipTransferError> in
return .complete()
}
}
public func join(engine: TelegramEngine, peerId: PeerId, hash: String?) -> Signal<Never, JoinChannelError> {
return engine.peers.joinChannel(peerId: peerId, hash: hash)
|> deliverOnMainQueue
|> beforeNext { [weak self] result in
if let strongSelf = self, let updated = result {
strongSelf.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.replayUpdates([(nil, updated, nil)])
}
}
}
}
}
|> ignoreValues
}
public func addMember(engine: TelegramEngine, peerId: PeerId, memberId: PeerId) -> Signal<Never, AddChannelMemberError> {
return engine.peers.addChannelMember(peerId: peerId, memberId: memberId)
|> deliverOnMainQueue
|> beforeNext { [weak self] result in
if let strongSelf = self {
let (previous, updated) = result
strongSelf.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.replayUpdates([(previous, updated, nil)])
}
}
}
}
}
|> ignoreValues
}
public func addMembers(engine: TelegramEngine, peerId: PeerId, memberIds: [PeerId]) -> Signal<Void, AddChannelMemberError> {
let signals: [Signal<(ChannelParticipant?, RenderedChannelParticipant)?, AddChannelMemberError>] = memberIds.map({ memberId in
return engine.peers.addChannelMember(peerId: peerId, memberId: memberId)
|> map(Optional.init)
|> `catch` { error -> Signal<(ChannelParticipant?, RenderedChannelParticipant)?, AddChannelMemberError> in
return .fail(error)
}
})
return combineLatest(signals)
|> deliverOnMainQueue
|> beforeNext { [weak self] results in
if let strongSelf = self {
strongSelf.impl.with { impl in
for result in results {
if let (previous, updated) = result {
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.replayUpdates([(previous, updated, nil)])
}
}
}
}
}
}
}
|> mapToSignal { _ -> Signal<Void, AddChannelMemberError> in
return .complete()
}
}
public func addMembersAllowPartial(engine: TelegramEngine, peerId: PeerId, memberIds: [PeerId]) -> Signal<[(PeerId, AddChannelMemberError)], NoError> {
let signals: [Signal<((ChannelParticipant?, RenderedChannelParticipant)?, PeerId, AddChannelMemberError?), NoError>] = memberIds.map({ memberId in
return engine.peers.addChannelMember(peerId: peerId, memberId: memberId)
|> map { result -> ((ChannelParticipant?, RenderedChannelParticipant)?, PeerId, AddChannelMemberError?) in
return (result, memberId, nil)
}
|> `catch` { error -> Signal<((ChannelParticipant?, RenderedChannelParticipant)?, PeerId, AddChannelMemberError?), NoError> in
return .single((nil, memberId, error))
}
})
return combineLatest(signals)
|> deliverOnMainQueue
|> beforeNext { [weak self] results in
if let strongSelf = self {
strongSelf.impl.with { impl in
for (result, _, _) in results {
if let (previous, updated) = result {
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.replayUpdates([(previous, updated, nil)])
}
}
}
}
}
}
}
|> map { results -> [(PeerId, AddChannelMemberError)] in
var failedIds: [(PeerId, AddChannelMemberError)] = []
for (_, memberId, error) in results {
if let error = error {
failedIds.append((memberId, error))
}
}
return failedIds
}
}
public func recentOnline(account: Account, accountPeerId: PeerId, peerId: PeerId) -> Signal<Int32, NoError> {
return Signal { [weak self] subscriber in
guard let strongSelf = self else {
subscriber.putNext(0)
subscriber.putCompletion()
return EmptyDisposable
}
let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
return impl.recentOnline(account: account, accountPeerId: accountPeerId, peerId: peerId, updated: { value in
subscriber.putNext(value)
})
})
return disposable
}
|> runOn(Queue.mainQueue())
}
public func recentOnlineSmall(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal<(total: Int32, recent: Int32), NoError> {
return Signal { [weak self] subscriber in
var previousIds: Set<PeerId>?
let statusesDisposable = MetaDisposable()
let disposableAndControl = self?.recent(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, updated: { state in
var idList: [PeerId] = []
for item in state.list {
idList.append(item.peer.id)
if idList.count >= 200 {
break
}
}
let updatedIds = Set(idList)
if previousIds != updatedIds {
previousIds = updatedIds
statusesDisposable.set((engine.data.subscribe(EngineDataMap(
updatedIds.map(TelegramEngine.EngineData.Item.Peer.Presence.init)
))
|> map { presenceMap -> Int32 in
var count: Int32 = 0
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
for (_, presence) in presenceMap {
if let presence = presence {
let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp))
switch relativeStatus {
case .online:
count += 1
default:
break
}
}
}
return count
}
|> distinctUntilChanged
|> deliverOnMainQueue).start(next: { count in
subscriber.putNext((Int32(updatedIds.count), count))
}))
}
})
return ActionDisposable {
disposableAndControl?.0.dispose()
statusesDisposable.dispose()
}
}
|> runOn(Queue.mainQueue())
}
public func profileData(postbox: Postbox, network: Network, peerId: PeerId, customData: Signal<Never, NoError>?) -> Signal<Never, NoError> {
return Signal { [weak self] subscriber in
guard let strongSelf = self else {
subscriber.putCompletion()
return EmptyDisposable
}
let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
return impl.profileData(postbox: postbox, network: network, peerId: peerId, customData: customData)
})
return disposable
}
|> runOn(Queue.mainQueue())
}
public func profilePhotos(postbox: Postbox, network: Network, peerId: PeerId, fetch: Signal<Any, NoError>) -> Signal<Any?, NoError> {
return Signal { [weak self] subscriber in
guard let strongSelf = self else {
subscriber.putNext(0)
subscriber.putCompletion()
return EmptyDisposable
}
let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
return impl.profilePhotos(postbox: postbox, network: network, peerId: peerId, fetch: fetch, updated: { value in
subscriber.putNext(value)
})
})
return disposable
}
|> runOn(Queue.mainQueue())
}
}