diff --git a/Postbox.xcodeproj/project.pbxproj b/Postbox.xcodeproj/project.pbxproj index 9b906435ff..ef606e8c71 100644 --- a/Postbox.xcodeproj/project.pbxproj +++ b/Postbox.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ C2A315BE1E2E733900D89000 /* PeerMergedOperationLogIndexTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D010B6191E1E463900C3E282 /* PeerMergedOperationLogIndexTable.swift */; }; C2A315BF1E2E733900D89000 /* PeerMergedOperationLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F019FC1E1DA0CC00F05AB3 /* PeerMergedOperationLogView.swift */; }; C2AC9C131E1E5D200085C7DE /* UnreadMessageCountsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A18D661E16874D004C6734 /* UnreadMessageCountsView.swift */; }; + D000CADA22006C6C0011B15D /* SharedAccountMediaManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000CAD922006C6C0011B15D /* SharedAccountMediaManager.swift */; }; + D000CADB22006C6C0011B15D /* SharedAccountMediaManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000CAD922006C6C0011B15D /* SharedAccountMediaManager.swift */; }; D001388620BD942B007C9721 /* PostboxUpgrade_16to17.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001388520BD942B007C9721 /* PostboxUpgrade_16to17.swift */; }; D001388720BD942B007C9721 /* PostboxUpgrade_16to17.swift in Sources */ = {isa = PBXBuildFile; fileRef = D001388520BD942B007C9721 /* PostboxUpgrade_16to17.swift */; }; D003E4E61B38DBDB00C22CBC /* MessageHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D003E4E51B38DBDB00C22CBC /* MessageHistoryView.swift */; }; @@ -396,6 +398,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + D000CAD922006C6C0011B15D /* SharedAccountMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedAccountMediaManager.swift; sourceTree = ""; }; D001388520BD942B007C9721 /* PostboxUpgrade_16to17.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostboxUpgrade_16to17.swift; sourceTree = ""; }; D003E4E51B38DBDB00C22CBC /* MessageHistoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageHistoryView.swift; sourceTree = ""; }; D0079F591D592E8B00A27A2C /* ContactTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactTable.swift; sourceTree = ""; }; @@ -703,6 +706,9 @@ D05D8B33218F1EBB0064586F /* AccountManagerSharedDataTable.swift */, D0BEAF6F1E54BC1E00BD963D /* AccountRecordsView.swift */, D05D8B30218F1D3D0064586F /* AccountSharedData.swift */, + D0575AE21E9ECBB2006F2541 /* AccessChallengeDataView.swift */, + D000CAD922006C6C0011B15D /* SharedAccountMediaManager.swift */, + D06ECFC420B796DC00C576C2 /* NoticeEntryView.swift */, ); name = "Account Manager"; sourceTree = ""; @@ -873,7 +879,6 @@ D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */, D0FA0ACC1E781067005BB9B7 /* Views.swift */, D0F53BF21E794C6700117362 /* PeerChatStateView.swift */, - D0575AE21E9ECBB2006F2541 /* AccessChallengeDataView.swift */, D0E1D30E1ECA53F900FCEEF1 /* GlobalMessageTagsView.swift */, D07047A41F3CF63800F6A8D4 /* MessageHistoryTagSummaryView.swift */, D07047AA1F3DD8D100F6A8D4 /* PendingMessageActionsView.swift */, @@ -891,7 +896,6 @@ D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */, D0BE3033206026C800FBE6D8 /* MessagesView.swift */, D048B4AE20A5EEAE00C79D31 /* AdditionalChatListItemsView.swift */, - D06ECFC420B796DC00C576C2 /* NoticeEntryView.swift */, D037178A20D923CA004773C8 /* CachedItemView.swift */, D029AA512167BC22006D4947 /* OrderedContactsView.swift */, D039FB1A21714D9800BD1BAD /* PeerPresencesView.swift */, @@ -1166,6 +1170,7 @@ C2A315BF1E2E733900D89000 /* PeerMergedOperationLogView.swift in Sources */, C2A315BD1E2E732000D89000 /* PeerOperationLogMetadataTable.swift in Sources */, D0C26D731FE2E7A8004ABF18 /* GroupFeedReadStateTable.swift in Sources */, + D000CADB22006C6C0011B15D /* SharedAccountMediaManager.swift in Sources */, D0575AE41E9ECBB2006F2541 /* AccessChallengeDataView.swift in Sources */, C2A315BC1E2E730400D89000 /* PeerOperationLogTable.swift in Sources */, D0B2F75E204F551D00D3BFB9 /* DeviceContactImportInfoTable.swift in Sources */, @@ -1347,6 +1352,7 @@ D0575AE31E9ECBB2006F2541 /* AccessChallengeDataView.swift in Sources */, D0CE8CF31F70249400AA2DB0 /* PostboxUpgrade_13to14.swift in Sources */, D0E3A79E1B28B50400A402D9 /* Message.swift in Sources */, + D000CADA22006C6C0011B15D /* SharedAccountMediaManager.swift in Sources */, D0C674C81CBB11C600183765 /* MessageHistoryReadStateTable.swift in Sources */, D0DF0C8F1D81A350008AEB01 /* PeerView.swift in Sources */, D0B2F75D204F551D00D3BFB9 /* DeviceContactImportInfoTable.swift in Sources */, diff --git a/Postbox/AccessChallengeDataView.swift b/Postbox/AccessChallengeDataView.swift index 74913071ee..adfbb45e35 100644 --- a/Postbox/AccessChallengeDataView.swift +++ b/Postbox/AccessChallengeDataView.swift @@ -1,16 +1,16 @@ import Foundation -final class MutableAccessChallengeDataView: MutablePostboxView { +final class MutableAccessChallengeDataView { var data: PostboxAccessChallengeData - init(postbox: Postbox) { - self.data = postbox.metadataTable.accessChallengeData() + init(data: PostboxAccessChallengeData) { + self.data = data } - func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { + func replay(updatedData: PostboxAccessChallengeData?) -> Bool { var updated = false - if let data = transaction.updatedAccessChallengeData { + if let data = updatedData { if self.data != data { self.data = data updated = true @@ -19,10 +19,6 @@ final class MutableAccessChallengeDataView: MutablePostboxView { return updated } - - func immutableView() -> PostboxView { - return AccessChallengeDataView(self) - } } public final class AccessChallengeDataView: PostboxView { diff --git a/Postbox/AccountManager.swift b/Postbox/AccountManager.swift index 24d3ba0e45..6d84d61b4f 100644 --- a/Postbox/AccountManager.swift +++ b/Postbox/AccountManager.swift @@ -10,9 +10,19 @@ public struct AccountManagerModifier { public let updateRecord: (AccountRecordId, (AccountRecord?) -> (AccountRecord?)) -> Void public let getCurrent: () -> (AccountRecordId, [AccountRecordAttribute])? public let setCurrentId: (AccountRecordId) -> Void + public let getCurrentAuth: () -> AuthAccountRecord? + public let createAuth: ([AccountRecordAttribute]) -> AuthAccountRecord? + public let removeAuth: () -> Void public let createRecord: ([AccountRecordAttribute]) -> AccountRecordId - public let getSharedData: (ValueBoxKey) -> AccountSharedData? - public let updateSharedData: (ValueBoxKey, (AccountSharedData?) -> AccountSharedData?) -> Void + public let getSharedData: (ValueBoxKey) -> PreferencesEntry? + public let updateSharedData: (ValueBoxKey, (PreferencesEntry?) -> PreferencesEntry?) -> Void + public let getAccessChallengeData: () -> PostboxAccessChallengeData + public let setAccessChallengeData: (PostboxAccessChallengeData) -> Void + public let getVersion: () -> Int32 + public let setVersion: (Int32) -> Void + public let getNotice: (NoticeEntryKey) -> NoticeEntry? + public let setNotice: (NoticeEntryKey, NoticeEntry?) -> Void + public let clearNotices: () -> Void } final class AccountManagerImpl { @@ -26,14 +36,19 @@ final class AccountManagerImpl { private let metadataTable: AccountManagerMetadataTable private let recordTable: AccountManagerRecordTable let sharedDataTable: AccountManagerSharedDataTable + let noticeTable: NoticeTable private var currentRecordOperations: [AccountManagerRecordOperation] = [] private var currentMetadataOperations: [AccountManagerMetadataOperation] = [] private var currentUpdatedSharedDataKeys = Set() + private var currentUpdatedNoticeEntryKeys = Set() + private var currentUpdatedAccessChallengeData: PostboxAccessChallengeData? private var recordsViews = Bag<(MutableAccountRecordsView, ValuePipe)>() private var sharedDataViews = Bag<(MutableAccountSharedDataView, ValuePipe)>() + private var noticeEntryViews = Bag<(MutableNoticeEntryView, ValuePipe)>() + private var accessChallengeDataViews = Bag<(MutableAccessChallengeDataView, ValuePipe)>() fileprivate init(queue: Queue, basePath: String, temporarySessionId: Int64) { self.queue = queue @@ -45,17 +60,21 @@ final class AccountManagerImpl { self.metadataTable = AccountManagerMetadataTable(valueBox: self.valueBox, table: AccountManagerMetadataTable.tableSpec(0)) self.recordTable = AccountManagerRecordTable(valueBox: self.valueBox, table: AccountManagerRecordTable.tableSpec(1)) self.sharedDataTable = AccountManagerSharedDataTable(valueBox: self.valueBox, table: AccountManagerSharedDataTable.tableSpec(2)) + self.noticeTable = NoticeTable(valueBox: self.valueBox, table: NoticeTable.tableSpec(3)) + + postboxLog("AccountManager: currentAccountId = \(String(describing: self.metadataTable.getCurrentAccountId()))") self.tables.append(self.metadataTable) self.tables.append(self.recordTable) self.tables.append(self.sharedDataTable) + self.tables.append(self.noticeTable) } deinit { assert(self.queue.isCurrent()) } - fileprivate func transaction(_ f: @escaping (AccountManagerModifier) -> T) -> Signal { + fileprivate func transaction(ignoreDisabled: Bool, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { return Signal { subscriber in self.queue.justDispatch { self.valueBox.begin() @@ -77,6 +96,18 @@ final class AccountManagerImpl { } }, setCurrentId: { id in self.metadataTable.setCurrentAccountId(id, operations: &self.currentMetadataOperations) + }, getCurrentAuth: { + if let id = self.metadataTable.getCurrentAuthAccount() { + return id + } else { + return nil + } + }, createAuth: { attributes in + let record = AuthAccountRecord(id: generateAccountRecordId(), attributes: attributes) + self.metadataTable.setCurrentAuthAccount(record, operations: &self.currentMetadataOperations) + return record + }, removeAuth: { + self.metadataTable.setCurrentAuthAccount(nil, operations: &self.currentMetadataOperations) }, createRecord: { attributes in let id = generateAccountRecordId() let record = AccountRecord(id: id, attributes: attributes, temporarySessionId: nil) @@ -87,6 +118,22 @@ final class AccountManagerImpl { }, updateSharedData: { key, f in let updated = f(self.sharedDataTable.get(key: key)) self.sharedDataTable.set(key: key, value: updated, updatedKeys: &self.currentUpdatedSharedDataKeys) + }, getAccessChallengeData: { + return self.metadataTable.getAccessChallengeData() + }, setAccessChallengeData: { data in + self.currentUpdatedAccessChallengeData = data + self.metadataTable.setAccessChallengeData(data) + }, getVersion: { + return self.metadataTable.getVersion() + }, setVersion: { version in + self.metadataTable.setVersion(version) + }, getNotice: { key in + self.noticeTable.get(key: key) + }, setNotice: { key, value in + self.noticeTable.set(key: key, value: value) + self.currentUpdatedNoticeEntryKeys.insert(key) + }, clearNotices: { + self.noticeTable.clear() }) let result = f(transaction) @@ -119,9 +166,27 @@ final class AccountManagerImpl { } } + if !self.currentUpdatedNoticeEntryKeys.isEmpty { + for (view, pipe) in self.noticeEntryViews.copyItems() { + if view.replay(accountManagerImpl: self, updatedKeys: self.currentUpdatedNoticeEntryKeys) { + pipe.putNext(NoticeEntryView(view)) + } + } + } + + if let data = self.currentUpdatedAccessChallengeData { + for (view, pipe) in self.accessChallengeDataViews.copyItems() { + if view.replay(updatedData: data) { + pipe.putNext(AccessChallengeDataView(view)) + } + } + } + self.currentRecordOperations.removeAll() self.currentMetadataOperations.removeAll() self.currentUpdatedSharedDataKeys.removeAll() + self.currentUpdatedNoticeEntryKeys.removeAll() + self.currentUpdatedAccessChallengeData = nil for table in self.tables { table.beforeCommit() @@ -129,23 +194,37 @@ final class AccountManagerImpl { } fileprivate func accountRecords() -> Signal { - return self.transaction { transaction -> Signal in + return self.transaction(ignoreDisabled: false, { transaction -> Signal in return self.accountRecordsInternal(transaction: transaction) - } + }) |> switchToLatest } fileprivate func sharedData(keys: Set) -> Signal { - return self.transaction { transaction -> Signal in + return self.transaction(ignoreDisabled: false, { transaction -> Signal in return self.sharedDataInternal(transaction: transaction, keys: keys) - } + }) + |> switchToLatest + } + + fileprivate func noticeEntry(key: NoticeEntryKey) -> Signal { + return self.transaction(ignoreDisabled: false, { transaction -> Signal in + return self.noticeEntryInternal(transaction: transaction, key: key) + }) + |> switchToLatest + } + + fileprivate func accessChallengeData() -> Signal { + return self.transaction(ignoreDisabled: false, { transaction -> Signal in + return self.accessChallengeDataInternal(transaction: transaction) + }) |> switchToLatest } private func accountRecordsInternal(transaction: AccountManagerModifier) -> Signal { let mutableView = MutableAccountRecordsView(getRecords: { return self.recordTable.getRecords() - }, currentId: self.metadataTable.getCurrentAccountId()) + }, currentId: self.metadataTable.getCurrentAccountId(), currentAuth: self.metadataTable.getCurrentAuthAccount()) let pipe = ValuePipe() let index = self.recordsViews.add((mutableView, pipe)) @@ -184,8 +263,48 @@ final class AccountManagerImpl { } } + private func noticeEntryInternal(transaction: AccountManagerModifier, key: NoticeEntryKey) -> Signal { + let mutableView = MutableNoticeEntryView(accountManagerImpl: self, key: key) + let pipe = ValuePipe() + let index = self.noticeEntryViews.add((mutableView, pipe)) + + let queue = self.queue + return (.single(NoticeEntryView(mutableView)) + |> then(pipe.signal())) + |> `catch` { _ -> Signal in + return .complete() + } + |> afterDisposed { [weak self] in + queue.async { + if let strongSelf = self { + strongSelf.noticeEntryViews.remove(index) + } + } + } + } + + private func accessChallengeDataInternal(transaction: AccountManagerModifier) -> Signal { + let mutableView = MutableAccessChallengeDataView(data: transaction.getAccessChallengeData()) + let pipe = ValuePipe() + let index = self.accessChallengeDataViews.add((mutableView, pipe)) + + let queue = self.queue + return (.single(AccessChallengeDataView(mutableView)) + |> then(pipe.signal())) + |> `catch` { _ -> Signal in + return .complete() + } + |> afterDisposed { [weak self] in + queue.async { + if let strongSelf = self { + strongSelf.accessChallengeDataViews.remove(index) + } + } + } + } + fileprivate func currentAccountRecord(allocateIfNotExists: Bool) -> Signal<(AccountRecordId, [AccountRecordAttribute])?, NoError> { - return self.transaction { transaction -> Signal<(AccountRecordId, [AccountRecordAttribute])?, NoError> in + return self.transaction(ignoreDisabled: false, { transaction -> Signal<(AccountRecordId, [AccountRecordAttribute])?, NoError> in let current = transaction.getCurrent() let record: (AccountRecordId, [AccountRecordAttribute])? if let current = current { @@ -211,7 +330,7 @@ final class AccountManagerImpl { } return signal - } + }) |> switchToLatest |> distinctUntilChanged(isEqual: { lhs, rhs in if let lhs = lhs, let rhs = rhs { @@ -237,7 +356,7 @@ final class AccountManagerImpl { func allocatedTemporaryAccountId() -> Signal { let temporarySessionId = self.temporarySessionId - return self.transaction { transaction -> Signal in + return self.transaction(ignoreDisabled: false, { transaction -> Signal in let id = generateAccountRecordId() transaction.updateRecord(id, { _ in @@ -245,7 +364,7 @@ final class AccountManagerImpl { }) return .single(id) - } + }) |> switchToLatest |> distinctUntilChanged(isEqual: { lhs, rhs in return lhs == rhs @@ -254,11 +373,14 @@ final class AccountManagerImpl { } public final class AccountManager { + public let basePath: String + public let mediaBox: MediaBox private let queue = Queue() private let impl: QueueLocalObject public let temporarySessionId: Int64 - fileprivate init(basePath: String) { + public init(basePath: String) { + self.basePath = basePath var temporarySessionId: Int64 = 0 arc4random_buf(&temporarySessionId, 8) self.temporarySessionId = temporarySessionId @@ -266,13 +388,14 @@ public final class AccountManager { self.impl = QueueLocalObject(queue: queue, generate: { return AccountManagerImpl(queue: queue, basePath: basePath, temporarySessionId: temporarySessionId) }) + self.mediaBox = MediaBox(basePath: basePath + "/media") } - public func transaction(_ f: @escaping (AccountManagerModifier) -> T) -> Signal { + public func transaction(ignoreDisabled: Bool = false, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in - disposable.set(impl.transaction(f).start(next: { next in + disposable.set(impl.transaction(ignoreDisabled: ignoreDisabled, f).start(next: { next in subscriber.putNext(next) }, completed: { subscriber.putCompletion() @@ -310,6 +433,34 @@ public final class AccountManager { } } + public func noticeEntry(key: NoticeEntryKey) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.noticeEntry(key: key).start(next: { next in + subscriber.putNext(next) + }, completed: { + subscriber.putCompletion() + })) + } + return disposable + } + } + + public func accessChallengeData() -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.accessChallengeData().start(next: { next in + subscriber.putNext(next) + }, completed: { + subscriber.putCompletion() + })) + } + return disposable + } + } + public func currentAccountRecord(allocateIfNotExists: Bool) -> Signal<(AccountRecordId, [AccountRecordAttribute])?, NoError> { return Signal { subscriber in let disposable = MetaDisposable() @@ -338,11 +489,3 @@ public final class AccountManager { } } } - -public func accountManager(basePath: String) -> Signal { - return Signal { subscriber in - subscriber.putNext(AccountManager(basePath: basePath)) - subscriber.putCompletion() - return EmptyDisposable - } -} diff --git a/Postbox/AccountManagerMetadataTable.swift b/Postbox/AccountManagerMetadataTable.swift index 3e31283f42..6b9a3a5895 100644 --- a/Postbox/AccountManagerMetadataTable.swift +++ b/Postbox/AccountManagerMetadataTable.swift @@ -1,11 +1,149 @@ import Foundation +public struct AccessChallengeAttempts: PostboxCoding, Equatable { + public let count: Int32 + public let timestamp: Int32 + + public init(count: Int32, timestamp: Int32) { + self.count = count + self.timestamp = timestamp + } + + public init(decoder: PostboxDecoder) { + self.count = decoder.decodeInt32ForKey("c", orElse: 0) + self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.count, forKey: "c") + encoder.encodeInt32(self.timestamp, forKey: "t") + } +} + +public enum PostboxAccessChallengeData: PostboxCoding, Equatable { + case none + case numericalPassword(value: String, timeout: Int32?, attempts: AccessChallengeAttempts?) + case plaintextPassword(value: String, timeout: Int32?, attempts: AccessChallengeAttempts?) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("r", orElse: 0) { + case 0: + self = .none + case 1: + self = .numericalPassword(value: decoder.decodeStringForKey("t", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("a"), attempts: decoder.decodeObjectForKey("att", decoder: { AccessChallengeAttempts(decoder: $0) }) as? AccessChallengeAttempts) + case 2: + self = .plaintextPassword(value: decoder.decodeStringForKey("t", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("a"), attempts: decoder.decodeObjectForKey("att", decoder: { AccessChallengeAttempts(decoder: $0) }) as? AccessChallengeAttempts) + default: + assertionFailure() + self = .none + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case .none: + encoder.encodeInt32(0, forKey: "r") + case let .numericalPassword(text, timeout, attempts): + encoder.encodeInt32(1, forKey: "r") + encoder.encodeString(text, forKey: "t") + if let timeout = timeout { + encoder.encodeInt32(timeout, forKey: "a") + } else { + encoder.encodeNil(forKey: "a") + } + if let attempts = attempts { + encoder.encodeObject(attempts, forKey: "att") + } else { + encoder.encodeNil(forKey: "att") + } + case let .plaintextPassword(text, timeout, attempts): + encoder.encodeInt32(2, forKey: "r") + encoder.encodeString(text, forKey: "t") + if let timeout = timeout { + encoder.encodeInt32(timeout, forKey: "a") + } else { + encoder.encodeNil(forKey: "a") + } + if let attempts = attempts { + encoder.encodeObject(attempts, forKey: "att") + } else { + encoder.encodeNil(forKey: "att") + } + } + } + + public var isLockable: Bool { + if case .none = self { + return false + } else { + return true + } + } + + public var autolockDeadline: Int32? { + switch self { + case .none: + return nil + case let .numericalPassword(_, timeout, _): + return timeout + case let .plaintextPassword(_, timeout, _): + return timeout + } + } + + public var attempts: AccessChallengeAttempts? { + switch self { + case .none: + return nil + case let .numericalPassword(_, _, attempts): + return attempts + case let .plaintextPassword(_, _, attempts): + return attempts + } + } + + public func withUpdatedAutolockDeadline(_ autolockDeadline: Int32?) -> PostboxAccessChallengeData { + switch self { + case .none: + return self + case let .numericalPassword(value, _, attempts): + return .numericalPassword(value: value, timeout: autolockDeadline, attempts: attempts) + case let .plaintextPassword(value, _, attempts): + return .plaintextPassword(value: value, timeout: autolockDeadline, attempts: attempts) + } + } +} + +public struct AuthAccountRecord: PostboxCoding { + public let id: AccountRecordId + public let attributes: [AccountRecordAttribute] + + init(id: AccountRecordId, attributes: [AccountRecordAttribute]) { + self.id = id + self.attributes = attributes + } + + public init(decoder: PostboxDecoder) { + self.id = AccountRecordId(rawValue: decoder.decodeOptionalInt64ForKey("id")!) + self.attributes = decoder.decodeObjectArrayForKey("attributes").compactMap({ $0 as? AccountRecordAttribute }) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.id.rawValue, forKey: "id") + encoder.encodeGenericObjectArray(self.attributes.map { $0 as PostboxCoding }, forKey: "attributes") + } +} + enum AccountManagerMetadataOperation { case updateCurrentAccountId(AccountRecordId) + case updateCurrentAuthAccountRecord(AuthAccountRecord?) } private enum MetadataKey: Int64 { case currentAccountId = 0 + case currentAuthAccount = 1 + case accessChallenge = 2 + case version = 3 } final class AccountManagerMetadataTable: Table { @@ -19,6 +157,21 @@ final class AccountManagerMetadataTable: Table { return result } + func getVersion() -> Int32 { + if let value = self.valueBox.get(self.table, key: self.key(.version)) { + var id: Int32 = 0 + value.read(&id, offset: 0, length: 4) + return id + } else { + return 0 + } + } + + func setVersion(_ version: Int32) { + var value: Int32 = version + self.valueBox.set(self.table, key: self.key(.version), value: MemoryBuffer(memory: &value, capacity: 4, length: 4, freeWhenDone: false)) + } + func getCurrentAccountId() -> AccountRecordId? { if let value = self.valueBox.get(self.table, key: self.key(.currentAccountId)) { var id: Int64 = 0 @@ -34,4 +187,41 @@ final class AccountManagerMetadataTable: Table { self.valueBox.set(self.table, key: self.key(.currentAccountId), value: MemoryBuffer(memory: &rawValue, capacity: 8, length: 8, freeWhenDone: false)) operations.append(.updateCurrentAccountId(id)) } + + func getCurrentAuthAccount() -> AuthAccountRecord? { + if let value = self.valueBox.get(self.table, key: self.key(.currentAuthAccount)), let object = PostboxDecoder(buffer: value).decodeRootObject() as? AuthAccountRecord { + return object + } else { + return nil + } + } + + func setCurrentAuthAccount(_ record: AuthAccountRecord?, operations: inout [AccountManagerMetadataOperation]) { + if let record = record { + let encoder = PostboxEncoder() + encoder.encodeRootObject(record) + withExtendedLifetime(encoder, { + self.valueBox.set(self.table, key: self.key(.currentAuthAccount), value: encoder.readBufferNoCopy()) + }) + } else { + self.valueBox.remove(self.table, key: self.key(.currentAuthAccount)) + } + operations.append(.updateCurrentAuthAccountRecord(record)) + } + + func getAccessChallengeData() -> PostboxAccessChallengeData { + if let value = self.valueBox.get(self.table, key: self.key(.accessChallenge)) { + return PostboxAccessChallengeData(decoder: PostboxDecoder(buffer: value)) + } else { + return .none + } + } + + func setAccessChallengeData(_ data: PostboxAccessChallengeData) { + let encoder = PostboxEncoder() + data.encode(encoder) + withExtendedLifetime(encoder, { + self.valueBox.set(self.table, key: self.key(.accessChallenge), value: encoder.readBufferNoCopy()) + }) + } } diff --git a/Postbox/AccountManagerSharedDataTable.swift b/Postbox/AccountManagerSharedDataTable.swift index 101ee5b1ba..46b8dde81c 100644 --- a/Postbox/AccountManagerSharedDataTable.swift +++ b/Postbox/AccountManagerSharedDataTable.swift @@ -5,15 +5,15 @@ final class AccountManagerSharedDataTable: Table { return ValueBoxTable(id: id, keyType: .binary) } - func get(key: ValueBoxKey) -> AccountSharedData? { - if let value = self.valueBox.get(self.table, key: key), let object = PostboxDecoder(buffer: value).decodeRootObject() as? AccountSharedData { + func get(key: ValueBoxKey) -> PreferencesEntry? { + if let value = self.valueBox.get(self.table, key: key), let object = PostboxDecoder(buffer: value).decodeRootObject() as? PreferencesEntry { return object } else { return nil } } - func set(key: ValueBoxKey, value: AccountSharedData?, updatedKeys: inout Set) { + func set(key: ValueBoxKey, value: PreferencesEntry?, updatedKeys: inout Set) { if let value = value { if let current = self.get(key: key), current.isEqual(to: value) { return diff --git a/Postbox/AccountRecordsView.swift b/Postbox/AccountRecordsView.swift index 44dcb79b05..3a86acaf50 100644 --- a/Postbox/AccountRecordsView.swift +++ b/Postbox/AccountRecordsView.swift @@ -3,10 +3,12 @@ import Foundation final class MutableAccountRecordsView { fileprivate var records: [AccountRecord] fileprivate var currentId: AccountRecordId? + fileprivate var currentAuth: AuthAccountRecord? - init(getRecords: () -> [AccountRecord], currentId: AccountRecordId?) { + init(getRecords: () -> [AccountRecord], currentId: AccountRecordId?, currentAuth: AuthAccountRecord?) { self.records = getRecords() self.currentId = currentId + self.currentAuth = currentAuth } func replay(operations: [AccountManagerRecordOperation], metadataOperations: [AccountManagerMetadataOperation]) -> Bool { @@ -49,6 +51,9 @@ final class MutableAccountRecordsView { case let .updateCurrentAccountId(id): updated = true self.currentId = id + case let .updateCurrentAuthAccountRecord(record): + updated = true + self.currentAuth = record } } @@ -59,6 +64,7 @@ final class MutableAccountRecordsView { public final class AccountRecordsView { public let records: [AccountRecord] public let currentRecord: AccountRecord? + public let currentAuthAccount: AuthAccountRecord? init(_ view: MutableAccountRecordsView) { self.records = view.records @@ -74,5 +80,6 @@ public final class AccountRecordsView { } else { self.currentRecord = nil } + self.currentAuthAccount = view.currentAuth } } diff --git a/Postbox/AccountSharedData.swift b/Postbox/AccountSharedData.swift index 0e3db23b8c..eccca9a837 100644 --- a/Postbox/AccountSharedData.swift +++ b/Postbox/AccountSharedData.swift @@ -1,12 +1,8 @@ import Foundation -public protocol AccountSharedData: PostboxCoding { - func isEqual(to other: AccountSharedData) -> Bool -} - final class MutableAccountSharedDataView { private let keys: Set - fileprivate var entries: [ValueBoxKey: AccountSharedData] = [:] + fileprivate var entries: [ValueBoxKey: PreferencesEntry] = [:] init(accountManagerImpl: AccountManagerImpl, keys: Set) { self.keys = keys @@ -32,7 +28,7 @@ final class MutableAccountSharedDataView { } public final class AccountSharedDataView { - public let entries: [ValueBoxKey: AccountSharedData] + public let entries: [ValueBoxKey: PreferencesEntry] init(_ view: MutableAccountSharedDataView) { self.entries = view.entries diff --git a/Postbox/ChatListIndexTable.swift b/Postbox/ChatListIndexTable.swift index 2ed91c1d00..c7c4f1a01e 100644 --- a/Postbox/ChatListIndexTable.swift +++ b/Postbox/ChatListIndexTable.swift @@ -396,37 +396,37 @@ final class ChatListIndexTable: Table { let initialReadState = alteredInitialPeerCombinedReadStates[peerId] ?? postbox.readStateTable.getCombinedState(peerId) let currentReadState = postbox.readStateTable.getCombinedState(peerId) - var initialValue: (Int32, Bool) = (0, false) - var currentValue: (Int32, Bool) = (0, false) + var initialValue: (Int32, Bool, Bool) = (0, false, false) + var currentValue: (Int32, Bool, Bool) = (0, false, false) if addedChatListPeerIds.contains(peerId) { if let currentReadState = currentReadState { - currentValue = (currentReadState.count, currentReadState.isUnread) + currentValue = (currentReadState.count, currentReadState.isUnread, currentReadState.markedUnread) } } else if removedChatListPeerIds.contains(peerId) { if let initialReadState = initialReadState { - initialValue = (initialReadState.count, initialReadState.isUnread) + initialValue = (initialReadState.count, initialReadState.isUnread, initialReadState.markedUnread) } } else { if self.get(peerId: peerId).includedIndex(peerId: peerId) != nil { if let initialReadState = initialReadState { - initialValue = (initialReadState.count, initialReadState.isUnread) + initialValue = (initialReadState.count, initialReadState.isUnread, initialReadState.markedUnread) } if let currentReadState = currentReadState { - currentValue = (currentReadState.count, currentReadState.isUnread) + currentValue = (currentReadState.count, currentReadState.isUnread, currentReadState.markedUnread) } } } - var initialFilteredValue: (Int32, Bool) = initialValue - var currentFilteredValue: (Int32, Bool) = currentValue + var initialFilteredValue: (Int32, Bool, Bool) = initialValue + var currentFilteredValue: (Int32, Bool, Bool) = currentValue if transactionParticipationInTotalUnreadCountUpdates.added.contains(peerId) { - initialFilteredValue = (0, false) + initialFilteredValue = (0, false, false) } else if transactionParticipationInTotalUnreadCountUpdates.removed.contains(peerId) { - currentFilteredValue = (0, false) + currentFilteredValue = (0, false, false) } else { if let notificationSettings = postbox.peerNotificationSettingsTable.getEffective(notificationPeerId), !notificationSettings.isRemovedFromTotalUnreadCount { } else { - initialFilteredValue = (0, false) - currentFilteredValue = (0, false) + initialFilteredValue = (0, false, false) + currentFilteredValue = (0, false, false) } } @@ -443,10 +443,16 @@ final class ChatListIndexTable: Table { if initialValue.1 { absolute.chatCount -= 1 } + if initialValue.2 && initialValue.0 == 0 { + absolute.messageCount -= 1 + } filtered.messageCount -= initialFilteredValue.0 if initialFilteredValue.1 { filtered.chatCount -= 1 } + if initialFilteredValue.2 && initialFilteredValue.0 == 0 { + filtered.messageCount -= 1 + } return (absolute, filtered) }) } @@ -455,6 +461,9 @@ final class ChatListIndexTable: Table { var absolute = absolute var filtered = filtered absolute.messageCount += currentValue.0 + if currentValue.2 && currentValue.0 == 0 { + absolute.messageCount += 1 + } if currentValue.1 { absolute.chatCount += 1 } @@ -462,6 +471,9 @@ final class ChatListIndexTable: Table { if currentFilteredValue.1 { filtered.chatCount += 1 } + if currentFilteredValue.2 && currentFilteredValue.0 == 0 { + filtered.messageCount += 1 + } return (absolute, filtered) }) } @@ -478,7 +490,10 @@ final class ChatListIndexTable: Table { } else { chatDifference = 0 } - let messageDifference = currentValue.0 - initialValue.0 + + let currentUnreadMark: Int32 = currentValue.2 ? 1 : 0 + let initialUnreadMark: Int32 = initialValue.2 ? 1 : 0 + let messageDifference = max(currentValue.0, currentUnreadMark) - max(initialValue.0, initialUnreadMark) let chatFilteredDifference: Int32 if initialFilteredValue.1 != currentFilteredValue.1 { @@ -486,7 +501,9 @@ final class ChatListIndexTable: Table { } else { chatFilteredDifference = 0 } - let messageFilteredDifference = currentFilteredValue.0 - initialFilteredValue.0 + let currentFilteredUnreadMark: Int32 = currentFilteredValue.2 ? 1 : 0 + let initialFilteredUnreadMark: Int32 = initialFilteredValue.2 ? 1 : 0 + let messageFilteredDifference = max(currentFilteredValue.0, currentFilteredUnreadMark) - max(initialFilteredValue.0, initialFilteredUnreadMark) absolute.messageCount += messageDifference absolute.chatCount += chatDifference @@ -516,16 +533,14 @@ final class ChatListIndexTable: Table { totalUnreadState.filteredCounters.removeValue(forKey: tag) } - #if DEBUG - #if targetEnvironment(simulator) + /*#if DEBUG && targetEnvironment(simulator) let reindexedCounts = self.debugReindexUnreadCounts(postbox: postbox) if reindexedCounts != totalUnreadState { print("reindexedCounts \(reindexedCounts) != totalUnreadState \(totalUnreadState)") totalUnreadState = reindexedCounts } - #endif - #endif + #endif*/ if self.metadataTable.getChatListTotalUnreadState() != totalUnreadState { self.metadataTable.setChatListTotalUnreadState(totalUnreadState) @@ -614,10 +629,13 @@ final class ChatListIndexTable: Table { if messageCount < 0 { messageCount = 0 } - state.absoluteCounters[tag]!.messageCount = messageCount if combinedState.isUnread { state.absoluteCounters[tag]!.chatCount += 1 } + if combinedState.markedUnread { + messageCount = max(1, messageCount) + } + state.absoluteCounters[tag]!.messageCount = messageCount } if let notificationSettings = notificationSettings, !notificationSettings.isRemovedFromTotalUnreadCount { @@ -630,10 +648,13 @@ final class ChatListIndexTable: Table { if messageCount < 0 { messageCount = 0 } - state.filteredCounters[tag]!.messageCount = messageCount if combinedState.isUnread { state.filteredCounters[tag]!.chatCount += 1 } + if combinedState.markedUnread { + messageCount = max(1, messageCount) + } + state.filteredCounters[tag]!.messageCount = messageCount } } } diff --git a/Postbox/FileSize.swift b/Postbox/FileSize.swift index b366746e60..1234e5c60b 100644 --- a/Postbox/FileSize.swift +++ b/Postbox/FileSize.swift @@ -1,6 +1,17 @@ import Foundation -public func fileSize(_ path: String) -> Int? { +public func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int? { + if useTotalFileAllocatedSize { + let url = URL(fileURLWithPath: path) + if let values = (try? url.resourceValues(forKeys: Set([.isRegularFileKey, .totalFileAllocatedSizeKey]))) { + if values.isRegularFile ?? false { + if let fileSize = values.totalFileAllocatedSize { + return fileSize + } + } + } + } + var value = stat() if stat(path, &value) == 0 { return Int(value.st_size) diff --git a/Postbox/ManagedFile.swift b/Postbox/ManagedFile.swift index 0b56f3abbd..f6b6468d40 100644 --- a/Postbox/ManagedFile.swift +++ b/Postbox/ManagedFile.swift @@ -25,6 +25,7 @@ public final class ManagedFile { private let mode: ManagedFileMode public init?(queue: Queue, path: String, mode: ManagedFileMode) { + assert(queue.isCurrent()) self.queue = queue self.mode = mode let fileMode: Int32 @@ -63,15 +64,28 @@ public final class ManagedFile { return wrappedRead(self.fd, data, count) } + public func readData(count: Int) -> Data { + assert(self.queue.isCurrent()) + var result = Data(count: count) + result.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + let readCount = self.read(bytes, count) + assert(readCount == count) + } + return result + } + public func seek(position: Int64) { + assert(self.queue.isCurrent()) lseek(self.fd, position, SEEK_SET) } public func truncate(count: Int64) { + assert(self.queue.isCurrent()) ftruncate(self.fd, count) } public func getSize() -> Int? { + assert(self.queue.isCurrent()) var value = stat() if fstat(self.fd, &value) == 0 { return Int(value.st_size) @@ -81,6 +95,7 @@ public final class ManagedFile { } public func sync() { + assert(self.queue.isCurrent()) fsync(self.fd) } } diff --git a/Postbox/MediaBox.swift b/Postbox/MediaBox.swift index d0f22d1198..c2a2c0ea4e 100644 --- a/Postbox/MediaBox.swift +++ b/Postbox/MediaBox.swift @@ -525,12 +525,15 @@ public final class MediaBox { return } - let dataDisposable = fileContext.data(range: Int32(range.lowerBound) ..< Int32(range.upperBound), waitUntilAfterInitialFetch: false, next: { result in - if let data = try? Data(contentsOf: URL(fileURLWithPath: result.path), options: .mappedRead) { + let range = Int32(range.lowerBound) ..< Int32(range.upperBound) + + let dataDisposable = fileContext.data(range: range, waitUntilAfterInitialFetch: false, next: { result in + if let file = ManagedFile(queue: self.dataQueue, path: result.path, mode: .read), let fileSize = file.getSize() { if result.complete { - if result.offset + result.size <= data.count { - if data.count >= result.offset + result.size { - let resultData = data.subdata(in: result.offset ..< (result.offset + result.size)) + if result.offset + result.size <= fileSize { + if fileSize >= result.offset + result.size { + file.seek(position: Int64(result.offset)) + let resultData = file.readData(count: result.size) subscriber.putNext(resultData) subscriber.putCompletion() } else { @@ -763,7 +766,7 @@ public final class MediaBox { let paths = self.storePathsForId(id) if let size = fileSize(paths.complete) { result[wrappedId] = Int64(size) - } else if let size = fileSize(paths.partial) { + } else if let size = fileSize(paths.partial, useTotalFileAllocatedSize: true) { result[wrappedId] = Int64(size) } } @@ -774,13 +777,13 @@ public final class MediaBox { } } - public func collectOtherResourceUsage(excludeIds: Set) -> Signal<(Int64, [String], Int64), NoError> { + public func collectOtherResourceUsage(excludeIds: Set, combinedExcludeIds: Set) -> Signal<(Int64, [String], Int64), NoError> { return Signal { subscriber in self.dataQueue.async { var result: Int64 = 0 var excludeNames = Set() - for id in excludeIds { + for id in combinedExcludeIds { let partial = "\(self.fileNameForId(id.id))_partial" let meta = "\(self.fileNameForId(id.id))_meta" let complete = self.fileNameForId(id.id) @@ -819,10 +822,22 @@ public final class MediaBox { var cacheResult: Int64 = 0 + var excludePrefixes = Set() + for id in excludeIds { + let cachedRepresentationPrefix = self.fileNameForId(id.id) + + excludePrefixes.insert(cachedRepresentationPrefix) + } + if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath + "/cache"), includingPropertiesForKeys: [.fileSizeKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { loop: for url in enumerator { if let url = url as? URL { + if let prefix = url.lastPathComponent.components(separatedBy: ":").first, excludePrefixes.contains(prefix) { + continue loop + } + if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 { + paths.append("cache/" + url.lastPathComponent) cacheResult += Int64(value) } } @@ -842,13 +857,6 @@ public final class MediaBox { for path in paths { unlink(self.basePath + "/" + path) } - if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath + "/cache"), includingPropertiesForKeys: [], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { - loop: for url in enumerator { - if let url = url as? URL { - unlink(url.path) - } - } - } subscriber.putCompletion() } return EmptyDisposable diff --git a/Postbox/MessageHistoryView.swift b/Postbox/MessageHistoryView.swift index bacff78a83..7fd7cfea33 100644 --- a/Postbox/MessageHistoryView.swift +++ b/Postbox/MessageHistoryView.swift @@ -93,16 +93,24 @@ public struct MessageHistoryViewId: Equatable { } } +public struct MutableMessageHistoryEntryAttributes: Equatable { + public var authorIsContact: Bool + + public init(authorIsContact: Bool) { + self.authorIsContact = authorIsContact + } +} + enum MutableMessageHistoryEntry { case IntermediateMessageEntry(IntermediateMessage, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?) - case MessageEntry(Message, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?) + case MessageEntry(Message, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?, MutableMessageHistoryEntryAttributes) case HoleEntry(MessageHistoryHole, MessageHistoryEntryLocation?, lowerIndex: MessageIndex?) var index: MessageIndex { switch self { case let .IntermediateMessageEntry(message, _, _): return MessageIndex(id: message.id, timestamp: message.timestamp) - case let .MessageEntry(message, _, _): + case let .MessageEntry(message, _, _, _): return MessageIndex(id: message.id, timestamp: message.timestamp) case let .HoleEntry(hole, _, _): return hole.maxIndex @@ -113,7 +121,7 @@ enum MutableMessageHistoryEntry { switch self { case let .IntermediateMessageEntry(message, _, _): return message.tags - case let .MessageEntry(message, _, _): + case let .MessageEntry(message, _, _, _): return message.tags case let .HoleEntry(hole, _, _): return MessageTags(rawValue: hole.tags) @@ -124,8 +132,8 @@ enum MutableMessageHistoryEntry { switch self { case let .IntermediateMessageEntry(message, _, monthLocation): return .IntermediateMessageEntry(message, location, monthLocation) - case let .MessageEntry(message, _, monthLocation): - return .MessageEntry(message, location, monthLocation) + case let .MessageEntry(message, _, monthLocation, attributes): + return .MessageEntry(message, location, monthLocation, attributes) case let .HoleEntry(hole, _, lowerIndex): return .HoleEntry(hole, location, lowerIndex: lowerIndex) } @@ -135,8 +143,8 @@ enum MutableMessageHistoryEntry { switch self { case let .IntermediateMessageEntry(message, location, _): return .IntermediateMessageEntry(message, location, monthLocation) - case let .MessageEntry(message, location, _): - return .MessageEntry(message, location, monthLocation) + case let .MessageEntry(message, location, _, attributes): + return .MessageEntry(message, location, monthLocation, attributes) case .HoleEntry: return self } @@ -164,12 +172,12 @@ enum MutableMessageHistoryEntry { } else { return self } - case let .MessageEntry(message, location, monthLocation): + case let .MessageEntry(message, location, monthLocation, attributes): if let location = location { if MessageIndex(id: message.id, timestamp: message.timestamp) > index { - return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation) + return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation, attributes) } else { - return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index, count: location.count + 1), monthLocation) + return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index, count: location.count + 1), monthLocation, attributes) } } else { return self @@ -186,7 +194,6 @@ enum MutableMessageHistoryEntry { //assert(location.count != 0) return .HoleEntry(hole, MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), lowerIndex: lowerIndex) } else { - //assert(location.count != 0) return .HoleEntry(hole, MessageHistoryEntryLocation(index: location.index, count: max(0, location.count - 1)), lowerIndex: lowerIndex) } } else { @@ -205,15 +212,15 @@ enum MutableMessageHistoryEntry { } else { return self } - case let .MessageEntry(message, location, monthLocation): + case let .MessageEntry(message, location, monthLocation, attributes): if let location = location { if MessageIndex(id: message.id, timestamp: message.timestamp) > index { //assert(location.index > 0) //assert(location.count != 0) - return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation) + return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation, attributes) } else { //assert(location.count != 0) - return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation) + return .MessageEntry(message, MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation, attributes) } } else { return self @@ -226,9 +233,9 @@ enum MutableMessageHistoryEntry { case let .IntermediateMessageEntry(message, location, monthLocation): let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia) return .IntermediateMessageEntry(updatedMessage, location, monthLocation) - case let .MessageEntry(message, location, monthLocation): + case let .MessageEntry(message, location, monthLocation, attributes): let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) - return .MessageEntry(updatedMessage, location, monthLocation) + return .MessageEntry(updatedMessage, location, monthLocation, attributes) case let .HoleEntry(hole, location, lowerIndex): let updatedHole = MessageHistoryHole(stableId: hole.stableId, maxIndex: MessageIndex(id: hole.maxIndex.id, timestamp: timestamp), min: hole.min, tags: hole.tags) return .HoleEntry(updatedHole, location, lowerIndex: lowerIndex) @@ -266,12 +273,12 @@ public struct MessageHistoryEntryMonthLocation: Equatable { } public enum MessageHistoryEntry: Comparable { - case MessageEntry(Message, Bool, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?) + case MessageEntry(Message, Bool, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?, MutableMessageHistoryEntryAttributes) case HoleEntry(MessageHistoryHole, MessageHistoryEntryLocation?) public var index: MessageIndex { switch self { - case let .MessageEntry(message, _, _, _): + case let .MessageEntry(message, _, _, _, _): return MessageIndex(id: message.id, timestamp: message.timestamp) case let .HoleEntry(hole, _): return hole.maxIndex @@ -281,12 +288,12 @@ public enum MessageHistoryEntry: Comparable { public func ==(lhs: MessageHistoryEntry, rhs: MessageHistoryEntry) -> Bool { switch lhs { - case let .MessageEntry(lhsMessage, lhsRead, lhsLocation, lhsMonthLocation): + case let .MessageEntry(lhsMessage, lhsRead, lhsLocation, lhsMonthLocation, lhsAttributes): switch rhs { case .HoleEntry: return false - case let .MessageEntry(rhsMessage, rhsRead, rhsLocation, rhsMonthLocation): - if MessageIndex(lhsMessage) == MessageIndex(rhsMessage) && lhsMessage.flags == rhsMessage.flags && lhsLocation == rhsLocation && lhsRead == rhsRead && lhsMonthLocation != rhsMonthLocation { + case let .MessageEntry(rhsMessage, rhsRead, rhsLocation, rhsMonthLocation, rhsAttributes): + if MessageIndex(lhsMessage) == MessageIndex(rhsMessage) && lhsMessage.flags == rhsMessage.flags && lhsLocation == rhsLocation && lhsRead == rhsRead && lhsMonthLocation == rhsMonthLocation && lhsAttributes == rhsAttributes { return true } return false @@ -467,7 +474,7 @@ private func clipMessages(peerIds: MessageHistoryViewPeerIds, entries: [MutableM var result: [MutableMessageHistoryEntry] = [] for entry in entries { switch entry { - case let .MessageEntry(message, _, _): + case let .MessageEntry(message, _, _, _): if message.id.peerId == associatedId.peerId { if message.id.namespace == associatedId.namespace && message.id.id < associatedId.id { result.append(entry) @@ -885,8 +892,8 @@ final class MutableMessageHistoryView { case let .IntermediateMessageEntry(message, location, monthLocation): self.entries[i] = .IntermediateMessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation) hasChanges = true - case let .MessageEntry(message, location, monthLocation): - self.entries[i] = .MessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation) + case let .MessageEntry(message, location, monthLocation, attributes): + self.entries[i] = .MessageEntry(message.withUpdatedGroupInfo(groupInfo), location, monthLocation, attributes) hasChanges = true case .HoleEntry: assertionFailure() @@ -921,7 +928,7 @@ final class MutableMessageHistoryView { if !updatedMedia.isEmpty { for i in 0 ..< self.entries.count { switch self.entries[i] { - case let .MessageEntry(message, location, monthLocation): + case let .MessageEntry(message, location, monthLocation, attributes): var rebuild = false for media in message.media { if let mediaId = media.id, let _ = updatedMedia[mediaId] { @@ -942,7 +949,7 @@ final class MutableMessageHistoryView { } } let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: messageMedia, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) - self.entries[i] = .MessageEntry(updatedMessage, location, monthLocation) + self.entries[i] = .MessageEntry(updatedMessage, location, monthLocation, attributes) hasChanges = true } case let .IntermediateMessageEntry(message, location, monthLocation): @@ -978,14 +985,14 @@ final class MutableMessageHistoryView { case let .InsertMessage(intermediateMessage): for i in 0 ..< self.entries.count { switch self.entries[i] { - case let .MessageEntry(message, location, monthLocation): + case let .MessageEntry(message, location, monthLocation, attributes): if message.associatedMessageIds.count != message.associatedMessages.count { if message.associatedMessageIds.contains(intermediateMessage.id) && message.associatedMessages[intermediateMessage.id] == nil { var updatedAssociatedMessages = message.associatedMessages let renderedMessage = renderIntermediateMessage(intermediateMessage) updatedAssociatedMessages[intermediateMessage.id] = renderedMessage let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: updatedAssociatedMessages, associatedMessageIds: message.associatedMessageIds) - self.entries[i] = .MessageEntry(updatedMessage, location, monthLocation) + self.entries[i] = .MessageEntry(updatedMessage, location, monthLocation, attributes) hasChanges = true } } @@ -1247,10 +1254,10 @@ final class MutableMessageHistoryView { hasChanges = true } else { switch entry { - case let .MessageEntry(message, location, monthLocation): + case let .MessageEntry(message, location, monthLocation, attributes): if let updatedAssociatedMessages = message.associatedMessages.filteredOut(keysIn: ids) { let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: updatedAssociatedMessages, associatedMessageIds: message.associatedMessageIds) - self.entries[i] = .MessageEntry(updatedMessage, location, monthLocation) + self.entries[i] = .MessageEntry(updatedMessage, location, monthLocation, attributes) } default: break @@ -1417,17 +1424,22 @@ final class MutableMessageHistoryView { } } - func render(_ renderIntermediateMessage: (IntermediateMessage) -> Message) { + func render(_ renderIntermediateMessage: (IntermediateMessage) -> Message, postbox: Postbox) { if let earlier = self.earlier, case let .IntermediateMessageEntry(intermediateMessage, location, monthLocation) = earlier { - self.earlier = .MessageEntry(renderIntermediateMessage(intermediateMessage), location, monthLocation) + self.earlier = .MessageEntry(renderIntermediateMessage(intermediateMessage), location, monthLocation, MutableMessageHistoryEntryAttributes(authorIsContact: false)) } if let later = self.later, case let .IntermediateMessageEntry(intermediateMessage, location, monthLocation) = later { - self.later = .MessageEntry(renderIntermediateMessage(intermediateMessage), location, monthLocation) + self.later = .MessageEntry(renderIntermediateMessage(intermediateMessage), location, monthLocation, MutableMessageHistoryEntryAttributes(authorIsContact: false)) } for i in 0 ..< self.entries.count { if case let .IntermediateMessageEntry(intermediateMessage, location, monthLocation) = self.entries[i] { - self.entries[i] = .MessageEntry(renderIntermediateMessage(intermediateMessage), location, monthLocation) + let message = renderIntermediateMessage(intermediateMessage) + var authorIsContact = false + if let author = message.author { + authorIsContact = postbox.contactsTable.isContact(peerId: author.id) + } + self.entries[i] = .MessageEntry(message, location, monthLocation, MutableMessageHistoryEntryAttributes(authorIsContact: authorIsContact)) } } @@ -1585,7 +1597,7 @@ public final class MessageHistoryView { } else { entries.append(.HoleEntry(hole, location)) } - case let .MessageEntry(message, location, monthLocation): + case let .MessageEntry(message, location, monthLocation, attributes): let read: Bool if message.flags.contains(.Incoming) { read = false @@ -1594,7 +1606,7 @@ public final class MessageHistoryView { } else { read = false } - entries.append(.MessageEntry(message, read, location, monthLocation)) + entries.append(.MessageEntry(message, read, location, monthLocation, attributes)) case .IntermediateMessageEntry: assertionFailure("unexpected IntermediateMessageEntry in MessageHistoryView.init()") } @@ -1608,8 +1620,8 @@ public final class MessageHistoryView { } else { entries.append(.HoleEntry(hole, location)) } - case let .MessageEntry(message, location, monthLocation): - entries.append(.MessageEntry(message, false, location, monthLocation)) + case let .MessageEntry(message, location, monthLocation, attributes): + entries.append(.MessageEntry(message, false, location, monthLocation, attributes)) case .IntermediateMessageEntry: assertionFailure("unexpected IntermediateMessageEntry in MessageHistoryView.init()") } @@ -1636,7 +1648,7 @@ public final class MessageHistoryView { } } break - } else if case let .MessageEntry(message, _, _, _) = entries[i] { + } else if case let .MessageEntry(message, _, _, _, _) = entries[i] { if let groupInfo = message.groupInfo { if let groupStart = groupStart, groupStart.1 == groupInfo { } else { @@ -1664,7 +1676,7 @@ public final class MessageHistoryView { } } break - } else if case let .MessageEntry(message, _, _, _) = entries[i] { + } else if case let .MessageEntry(message, _, _, _, _) = entries[i] { if let groupInfo = message.groupInfo { if let groupStart = groupStart, groupStart.1 == groupInfo { } else { @@ -1742,7 +1754,7 @@ public final class MessageHistoryView { } if let _ = maxNamespaceIndex , index + 1 < entries.count { for i in index + 1 ..< entries.count { - if case let .MessageEntry(message, _, _, _) = entries[i], !message.flags.contains(.Incoming) { + if case let .MessageEntry(message, _, _, _, _) = entries[i], !message.flags.contains(.Incoming) { maxNamespaceIndex = MessageIndex(message) } else { break @@ -1776,7 +1788,7 @@ public final class MessageHistoryView { } if let _ = maxIndex, index + 1 < entries.count { for i in index + 1 ..< entries.count { - if case let .MessageEntry(message, _, _, _) = entries[i], !message.flags.contains(.Incoming) { + if case let .MessageEntry(message, _, _, _, _) = entries[i], !message.flags.contains(.Incoming) { maxIndex = MessageIndex(message) } else { break diff --git a/Postbox/MetadataTable.swift b/Postbox/MetadataTable.swift index f08ddb1982..b7255b19f5 100644 --- a/Postbox/MetadataTable.swift +++ b/Postbox/MetadataTable.swift @@ -5,151 +5,9 @@ private enum MetadataKey: Int32 { case State = 2 case TransactionStateVersion = 3 case MasterClientId = 4 - case AccessChallenge = 5 case RemoteContactCount = 6 } -public struct AccessChallengeAttempts: PostboxCoding, Equatable { - public let count: Int32 - public let timestamp: Int32 - - public init(count: Int32, timestamp: Int32) { - self.count = count - self.timestamp = timestamp - } - - public init(decoder: PostboxDecoder) { - self.count = decoder.decodeInt32ForKey("c", orElse: 0) - self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0) - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(self.count, forKey: "c") - encoder.encodeInt32(self.timestamp, forKey: "t") - } - - public static func ==(lhs: AccessChallengeAttempts, rhs: AccessChallengeAttempts) -> Bool { - return lhs.count == rhs.count && lhs.timestamp == rhs.timestamp - } -} - -public enum PostboxAccessChallengeData: PostboxCoding, Equatable { - case none - case numericalPassword(value: String, timeout: Int32?, attempts: AccessChallengeAttempts?) - case plaintextPassword(value: String, timeout: Int32?, attempts: AccessChallengeAttempts?) - - public init(decoder: PostboxDecoder) { - switch decoder.decodeInt32ForKey("r", orElse: 0) { - case 0: - self = .none - case 1: - self = .numericalPassword(value: decoder.decodeStringForKey("t", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("a"), attempts: decoder.decodeObjectForKey("att", decoder: { AccessChallengeAttempts(decoder: $0) }) as? AccessChallengeAttempts) - case 2: - self = .plaintextPassword(value: decoder.decodeStringForKey("t", orElse: ""), timeout: decoder.decodeOptionalInt32ForKey("a"), attempts: decoder.decodeObjectForKey("att", decoder: { AccessChallengeAttempts(decoder: $0) }) as? AccessChallengeAttempts) - default: - assertionFailure() - self = .none - } - } - - public func encode(_ encoder: PostboxEncoder) { - switch self { - case .none: - encoder.encodeInt32(0, forKey: "r") - case let .numericalPassword(text, timeout, attempts): - encoder.encodeInt32(1, forKey: "r") - encoder.encodeString(text, forKey: "t") - if let timeout = timeout { - encoder.encodeInt32(timeout, forKey: "a") - } else { - encoder.encodeNil(forKey: "a") - } - if let attempts = attempts { - encoder.encodeObject(attempts, forKey: "att") - } else { - encoder.encodeNil(forKey: "att") - } - case let .plaintextPassword(text, timeout, attempts): - encoder.encodeInt32(2, forKey: "r") - encoder.encodeString(text, forKey: "t") - if let timeout = timeout { - encoder.encodeInt32(timeout, forKey: "a") - } else { - encoder.encodeNil(forKey: "a") - } - if let attempts = attempts { - encoder.encodeObject(attempts, forKey: "att") - } else { - encoder.encodeNil(forKey: "att") - } - } - } - - public static func ==(lhs: PostboxAccessChallengeData, rhs: PostboxAccessChallengeData) -> Bool { - switch lhs { - case .none: - if case .none = rhs { - return true - } else { - return false - } - case let .numericalPassword(lhsText, lhsTimeout, lhsAttempts): - if case let .numericalPassword(rhsText, rhsTimeout, rhsAttempts) = rhs, lhsText == rhsText, lhsTimeout == rhsTimeout, lhsAttempts == rhsAttempts { - return true - } else { - return false - } - case let .plaintextPassword(lhsText, lhsTimeout, lhsAttempts): - if case let .plaintextPassword(rhsText, rhsTimeout, rhsAttempts) = rhs, lhsText == rhsText, lhsTimeout == rhsTimeout, lhsAttempts == rhsAttempts { - return true - } else { - return false - } - } - } - - public var isLockable: Bool { - if case .none = self { - return false - } else { - return true - } - } - - public var autolockDeadline: Int32? { - switch self { - case .none: - return nil - case let .numericalPassword(_, timeout, _): - return timeout - case let .plaintextPassword(_, timeout, _): - return timeout - } - } - - public var attempts: AccessChallengeAttempts? { - switch self { - case .none: - return nil - case let .numericalPassword(_, _, attempts): - return attempts - case let .plaintextPassword(_, _, attempts): - return attempts - } - } - - public func withUpdatedAutolockDeadline(_ autolockDeadline: Int32?) -> PostboxAccessChallengeData { - switch self { - case .none: - return self - case let .numericalPassword(value, _, attempts): - return .numericalPassword(value: value, timeout: autolockDeadline, attempts: attempts) - case let .plaintextPassword(value, _, attempts): - return .plaintextPassword(value: value, timeout: autolockDeadline, attempts: attempts) - } - } -} - final class MetadataTable: Table { static func tableSpec(_ id: Int32) -> ValueBoxTable { return ValueBoxTable(id: id, keyType: .int64) @@ -248,22 +106,6 @@ final class MetadataTable: Table { self.valueBox.set(self.table, key: self.key(.MasterClientId), value: buffer) } - func accessChallengeData() -> PostboxAccessChallengeData { - if let value = self.valueBox.get(self.table, key: self.key(.AccessChallenge)) { - return PostboxAccessChallengeData(decoder: PostboxDecoder(buffer: value)) - } else { - return .none - } - } - - func setAccessChallengeData(_ data: PostboxAccessChallengeData) { - let encoder = PostboxEncoder() - data.encode(encoder) - withExtendedLifetime(encoder, { - self.valueBox.set(self.table, key: self.key(.AccessChallenge), value: encoder.readBufferNoCopy()) - }) - } - func setRemoteContactCount(_ count: Int32) { self.cachedRemoteContactCount = count var mutableCount: Int32 = count diff --git a/Postbox/NoticeEntryView.swift b/Postbox/NoticeEntryView.swift index 3de5e37885..fcee321560 100644 --- a/Postbox/NoticeEntryView.swift +++ b/Postbox/NoticeEntryView.swift @@ -1,28 +1,28 @@ import Foundation -final class MutableNoticeEntryView: MutablePostboxView { +final class MutableNoticeEntryView { private let key: NoticeEntryKey fileprivate var value: NoticeEntry? - init(postbox: Postbox, key: NoticeEntryKey) { + init(accountManagerImpl: AccountManagerImpl, key: NoticeEntryKey) { self.key = key - self.value = postbox.noticeTable.get(key: key) + self.value = accountManagerImpl.noticeTable.get(key: key) } - func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { - if transaction.updatedNoticeEntryKeys.contains(self.key) { - self.value = postbox.noticeTable.get(key: key) + func replay(accountManagerImpl: AccountManagerImpl, updatedKeys: Set) -> Bool { + if updatedKeys.contains(self.key) { + self.value = accountManagerImpl.noticeTable.get(key: self.key) return true } return false } - func immutableView() -> PostboxView { + func immutableView() -> NoticeEntryView { return NoticeEntryView(self) } } -public final class NoticeEntryView: PostboxView { +public final class NoticeEntryView { public let value: NoticeEntry? init(_ view: MutableNoticeEntryView) { diff --git a/Postbox/NoticeTable.swift b/Postbox/NoticeTable.swift index 1e1a4e7f90..24922e6f1b 100644 --- a/Postbox/NoticeTable.swift +++ b/Postbox/NoticeTable.swift @@ -41,6 +41,17 @@ final class NoticeTable: Table { return ValueBoxTable(id: id, keyType: .binary) } + func getAll() -> [ValueBoxKey: NoticeEntry] { + var result: [ValueBoxKey: NoticeEntry] = [:] + self.valueBox.scan(self.table, values: { key, value in + if let object = PostboxDecoder(buffer: value).decodeRootObject() as? NoticeEntry { + result[key] = object + } + return true + }) + return result + } + func get(key: NoticeEntryKey) -> NoticeEntry? { if let cached = self.cachedEntries[key] { return cached.entry diff --git a/Postbox/Postbox.swift b/Postbox/Postbox.swift index 07058ec309..796cc83e53 100644 --- a/Postbox/Postbox.swift +++ b/Postbox/Postbox.swift @@ -769,20 +769,6 @@ public final class Transaction { } } - public func getAccessChallengeData() -> PostboxAccessChallengeData { - assert(!self.disposed) - if let postbox = self.postbox { - return postbox.metadataTable.accessChallengeData() - } else { - return .none - } - } - - public func setAccessChallengeData(_ data: PostboxAccessChallengeData) { - assert(!self.disposed) - self.postbox?.setAccessChallengeData(data) - } - public func enumerateMedia(lowerBound: MessageIndex?, limit: Int) -> ([PeerId: Set], [MediaId: Media], MessageIndex?) { assert(!self.disposed) if let postbox = self.postbox { @@ -830,6 +816,15 @@ public final class Transaction { } } + public func getAllNoticeEntries() -> [ValueBoxKey: NoticeEntry] { + assert(!self.disposed) + if let postbox = self.postbox { + return postbox.noticeTable.getAll() + } else { + return [:] + } + } + public func getNoticeEntry(key: NoticeEntryKey) -> PostboxCoding? { assert(!self.disposed) if let postbox = self.postbox { @@ -1078,7 +1073,6 @@ public final class Postbox { private var currentItemCollectionInfosOperations: [ItemCollectionInfosOperation] = [] private var currentUpdatedPeerChatStates = Set() private var currentUpdatedPeerGroupStates = Set() - private var currentUpdatedAccessChallengeData: PostboxAccessChallengeData? private var currentPendingMessageActionsOperations: [PendingMessageActionsOperation] = [] private var currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32] = [:] private var currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey : MessageHistoryTagNamespaceSummary] = [:] @@ -1937,7 +1931,7 @@ public final class Postbox { }*/ #endif - let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentOperationsByPeerId: self.currentOperationsByPeerId, currentGroupFeedOperations: self.currentGroupFeedOperations, peerIdsWithFilledHoles: self.currentFilledHolesByPeerId, removedHolesByPeerId: self.currentRemovedHolesByPeerId, groupFeedIdsWithFilledHoles: self.currentGroupFeedIdsWithFilledHoles, removedHolesByPeerGroupId: self.currentRemovedHolesByPeerGroupId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadState: self.currentUpdatedTotalUnreadState, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentUpdatedPeerGroupStates: self.currentUpdatedPeerGroupStates, updatedAccessChallengeData: self.currentUpdatedAccessChallengeData, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, currentGroupFeedReadStateContext: self.currentGroupFeedReadStateContext, currentInitialPeerGroupIdsBeforeUpdate: self.currentInitialPeerGroupIdsBeforeUpdate, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId) + let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentOperationsByPeerId: self.currentOperationsByPeerId, currentGroupFeedOperations: self.currentGroupFeedOperations, peerIdsWithFilledHoles: self.currentFilledHolesByPeerId, removedHolesByPeerId: self.currentRemovedHolesByPeerId, groupFeedIdsWithFilledHoles: self.currentGroupFeedIdsWithFilledHoles, removedHolesByPeerGroupId: self.currentRemovedHolesByPeerGroupId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadState: self.currentUpdatedTotalUnreadState, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentUpdatedPeerGroupStates: self.currentUpdatedPeerGroupStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, currentGroupFeedReadStateContext: self.currentGroupFeedReadStateContext, currentInitialPeerGroupIdsBeforeUpdate: self.currentInitialPeerGroupIdsBeforeUpdate, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId) var updatedTransactionState: Int64? var updatedMasterClientId: Int64? if !transaction.isEmpty { @@ -1986,7 +1980,6 @@ public final class Postbox { self.currentItemCollectionInfosOperations.removeAll() self.currentUpdatedPeerChatStates.removeAll() self.currentUpdatedPeerGroupStates.removeAll() - self.currentUpdatedAccessChallengeData = nil self.currentPendingMessageActionsOperations.removeAll() self.currentUpdatedMessageActionsSummaries.removeAll() self.currentUpdatedMessageTagSummaries.removeAll() @@ -2680,7 +2673,7 @@ public final class Postbox { return 0 } }) - mutableView.render(self.renderIntermediateMessage) + mutableView.render(self.renderIntermediateMessage, postbox: self) let initialUpdateType: ViewUpdateType if let unreadIndex = unreadIndex { @@ -2889,7 +2882,10 @@ public final class Postbox { var additionalChatPeerIds: [PeerId] = [] for peerId in chatPeerIds { for associatedId in self.reverseAssociatedPeerTable.get(peerId: peerId) { - additionalChatPeerIds.append(associatedId) + let inclusionIndex = self.chatListIndexTable.get(peerId: associatedId) + if inclusionIndex.includedIndex(peerId: associatedId) != nil { + additionalChatPeerIds.append(associatedId) + } } } chatPeerIds.append(contentsOf: additionalChatPeerIds) @@ -3327,11 +3323,6 @@ public final class Postbox { self.orderedItemListTable.updateItem(collectionId: collectionId, itemId: itemId, item: item, operations: &self.currentOrderedItemListOperations) } - fileprivate func setAccessChallengeData(_ data: PostboxAccessChallengeData) { - self.currentUpdatedAccessChallengeData = data - self.metadataTable.setAccessChallengeData(data) - } - public func installStoreMessageAction(peerId: PeerId, _ f: @escaping ([StoreMessage], Transaction) -> Void) -> Disposable { let disposable = MetaDisposable() self.queue.async { diff --git a/Postbox/PostboxAccess.swift b/Postbox/PostboxAccess.swift index 4e2d344e5e..8b13789179 100644 --- a/Postbox/PostboxAccess.swift +++ b/Postbox/PostboxAccess.swift @@ -1,63 +1 @@ -import Foundation -#if os(macOS) - import SwiftSignalKitMac -#else - import SwiftSignalKit -#endif -public enum PostboxAuthorizationChallenge { - case numericPassword(length: Int32) - case arbitraryPassword -} - -public enum PostboxAccess { - case unlocked - case locked(PostboxAuthorizationChallenge) -} - -private final class PostboxAccessHelper { - let queue: Queue - let valueBox: ValueBox - let metadataTable: MetadataTable - - init(queue: Queue, basePath: String) { - self.queue = queue - self.valueBox = SqliteValueBox(basePath: basePath + "/db", queue: self.queue) - self.metadataTable = MetadataTable(valueBox: self.valueBox, table: MetadataTable.tableSpec(0)) - } -} - -public func accessPostbox(basePath: String, password: String?) -> Signal { - return Signal { subscriber in - let queue = Queue() - - queue.async { - let postbox = PostboxAccessHelper(queue: queue, basePath: basePath) - let challengeData = postbox.metadataTable.accessChallengeData() - switch challengeData { - case .none: - subscriber.putNext(.unlocked) - subscriber.putCompletion() - case let .numericalPassword(text, _, _): - if text == password { - subscriber.putNext(.unlocked) - subscriber.putCompletion() - } else { - subscriber.putNext(.locked(.numericPassword(length: Int32(text.characters.count)))) - subscriber.putCompletion() - } - case let .plaintextPassword(text, _, _): - if text == password { - subscriber.putNext(.unlocked) - subscriber.putCompletion() - } else { - subscriber.putNext(.locked(.arbitraryPassword)) - subscriber.putCompletion() - } - } - } - - return ActionDisposable { - } - } -} diff --git a/Postbox/PostboxTransaction.swift b/Postbox/PostboxTransaction.swift index 1fdc5736f0..b639b445ac 100644 --- a/Postbox/PostboxTransaction.swift +++ b/Postbox/PostboxTransaction.swift @@ -25,7 +25,6 @@ final class PostboxTransaction { let currentItemCollectionInfosOperations: [ItemCollectionInfosOperation] let currentUpdatedPeerChatStates: Set let currentUpdatedPeerGroupStates: Set - let updatedAccessChallengeData: PostboxAccessChallengeData? let currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation] let currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation] let currentPendingMessageActionsOperations: [PendingMessageActionsOperation] @@ -140,9 +139,6 @@ final class PostboxTransaction { if !currentUpdatedPeerGroupStates.isEmpty { return false } - if self.updatedAccessChallengeData != nil { - return false - } if !self.currentGlobalTagsOperations.isEmpty { return false } @@ -182,7 +178,7 @@ final class PostboxTransaction { return true } - init(currentUpdatedState: PostboxCoding?, currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], currentGroupFeedOperations: [PeerGroupId : [GroupFeedIndexOperation]], peerIdsWithFilledHoles: [PeerId: [MessageIndex: HoleFillDirection]], removedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]], groupFeedIdsWithFilledHoles: [PeerGroupId: [MessageIndex: HoleFillDirection]], removedHolesByPeerGroupId: [PeerGroupId: [MessageIndex: HoleFillDirection]], chatListOperations: [WrappedPeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadState: ChatListTotalUnreadState?, alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentUpdatedPeerGroupStates: Set, updatedAccessChallengeData: PostboxAccessChallengeData?, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, currentGroupFeedReadStateContext: GroupFeedReadStateUpdateContext, currentInitialPeerGroupIdsBeforeUpdate: [PeerId: WrappedPeerGroupId], replacedAdditionalChatListItems: [PeerId]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?) { + init(currentUpdatedState: PostboxCoding?, currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], currentGroupFeedOperations: [PeerGroupId : [GroupFeedIndexOperation]], peerIdsWithFilledHoles: [PeerId: [MessageIndex: HoleFillDirection]], removedHolesByPeerId: [PeerId: [MessageIndex: HoleFillDirection]], groupFeedIdsWithFilledHoles: [PeerGroupId: [MessageIndex: HoleFillDirection]], removedHolesByPeerGroupId: [PeerGroupId: [MessageIndex: HoleFillDirection]], chatListOperations: [WrappedPeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadState: ChatListTotalUnreadState?, alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentUpdatedPeerGroupStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, currentGroupFeedReadStateContext: GroupFeedReadStateUpdateContext, currentInitialPeerGroupIdsBeforeUpdate: [PeerId: WrappedPeerGroupId], replacedAdditionalChatListItems: [PeerId]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?) { self.currentUpdatedState = currentUpdatedState self.currentOperationsByPeerId = currentOperationsByPeerId self.currentGroupFeedOperations = currentGroupFeedOperations @@ -209,7 +205,6 @@ final class PostboxTransaction { self.currentItemCollectionInfosOperations = currentItemCollectionInfosOperations self.currentUpdatedPeerChatStates = currentUpdatedPeerChatStates self.currentUpdatedPeerGroupStates = currentUpdatedPeerGroupStates - self.updatedAccessChallengeData = updatedAccessChallengeData self.currentGlobalTagsOperations = currentGlobalTagsOperations self.currentLocalTagsOperations = currentLocalTagsOperations self.updatedMedia = updatedMedia diff --git a/Postbox/SharedAccountMediaManager.swift b/Postbox/SharedAccountMediaManager.swift new file mode 100644 index 0000000000..d96aa0232b --- /dev/null +++ b/Postbox/SharedAccountMediaManager.swift @@ -0,0 +1,25 @@ +import Foundation + +final class SharedAccountMediaManager { + private let basePath: String + + init(basePath: String) { + self.basePath = basePath + } + + private func fileNameForId(_ id: MediaResourceId) -> String { + return "\(id.uniqueId)" + } + + private func pathForId(_ id: MediaResourceId) -> String { + return "\(self.basePath)/\(fileNameForId(id))" + } + + func resourceData(resourceId: MediaResourceId) -> Data? { + return try? Data(contentsOf: URL(fileURLWithPath: self.pathForId(resourceId))) + } + + func storeResourceData(resourceId: MediaResourceId, data: Data) { + let _ = try? data.write(to: URL(fileURLWithPath: self.pathForId(resourceId))) + } +} diff --git a/Postbox/ViewTracker.swift b/Postbox/ViewTracker.swift index 7d61a858e6..3c01040e71 100644 --- a/Postbox/ViewTracker.swift +++ b/Postbox/ViewTracker.swift @@ -157,7 +157,7 @@ final class ViewTracker { } if updated { - mutableView.render(self.renderMessage) + mutableView.render(self.renderMessage, postbox: postbox) pipe.putNext((MessageHistoryView(mutableView), updateType)) self.updateTrackedHoles() @@ -274,7 +274,7 @@ final class ViewTracker { if mutableView.refreshDueToExternalTransaction(postbox: postbox) { mutableView.incrementVersion() - mutableView.render(self.renderMessage) + mutableView.render(self.renderMessage, postbox: postbox) pipe.putNext((MessageHistoryView(mutableView), .Generic)) updateTrackedHoles = true @@ -405,7 +405,7 @@ final class ViewTracker { if updated { updateTrackedHoles = true - mutableView.render(self.renderMessage) + mutableView.render(self.renderMessage, postbox: postbox) pipe.putNext((MessageHistoryView(mutableView), updateType)) } diff --git a/Postbox/Views.swift b/Postbox/Views.swift index 7486db77c3..4583d5d9de 100644 --- a/Postbox/Views.swift +++ b/Postbox/Views.swift @@ -7,7 +7,6 @@ public enum PostboxViewKey: Hashable { case peerChatState(peerId: PeerId) case peerGroupState(groupId: PeerGroupId) case orderedItemList(id: Int32) - case accessChallengeData case preferences(keys: Set) case globalMessageTags(globalTag: GlobalMessageTags, position: MessageIndex, count: Int, groupingPredicate: ((Message, Message) -> Bool)?) case peer(peerId: PeerId, components: PeerViewComponents) @@ -25,7 +24,6 @@ public enum PostboxViewKey: Hashable { case localMessageTag(LocalMessageTags) case messages(Set) case additionalChatListItems - case noticeEntry(NoticeEntryKey) case cachedItem(ItemCacheEntryId) case orderedContacts case peerPresences(peerIds: Set) @@ -44,8 +42,6 @@ public enum PostboxViewKey: Hashable { return id.hashValue case let .orderedItemList(id): return id.hashValue - case .accessChallengeData: - return 2 case .preferences: return 3 case .globalMessageTags: @@ -80,8 +76,6 @@ public enum PostboxViewKey: Hashable { return 10 case .additionalChatListItems: return 11 - case let .noticeEntry(key): - return key.hashValue case let .cachedItem(id): return id.hashValue case .orderedContacts: @@ -129,12 +123,6 @@ public enum PostboxViewKey: Hashable { } else { return false } - case .accessChallengeData: - if case .accessChallengeData = rhs { - return true - } else { - return false - } case let .preferences(lhsKeys): if case let .preferences(rhsKeys) = rhs, lhsKeys == rhsKeys { return true @@ -237,12 +225,6 @@ public enum PostboxViewKey: Hashable { } else { return false } - case let .noticeEntry(key): - if case .noticeEntry(key) = rhs { - return true - } else { - return false - } case let .cachedItem(id): if case .cachedItem(id) = rhs { return true @@ -279,8 +261,6 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV return MutablePeerGroupStateView(postbox: postbox, groupId: groupId) case let .orderedItemList(id): return MutableOrderedItemListView(postbox: postbox, collectionId: id) - case .accessChallengeData: - return MutableAccessChallengeDataView(postbox: postbox) case let .preferences(keys): return MutablePreferencesView(postbox: postbox, keys: keys) case let .globalMessageTags(globalTag, position, count, groupingPredicate): @@ -315,8 +295,6 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV return MutableMessagesView(postbox: postbox, ids: ids) case .additionalChatListItems: return MutableAdditionalChatListItemsView(postbox: postbox) - case let .noticeEntry(key): - return MutableNoticeEntryView(postbox: postbox, key: key) case let .cachedItem(id): return MutableCachedItemView(postbox: postbox, id: id) case .orderedContacts: