no message

This commit is contained in:
Peter Iakovlev 2018-02-06 22:31:32 +04:00
parent 6f5513f01a
commit 59e1f10908
16 changed files with 1330 additions and 199 deletions

View File

@ -38,6 +38,8 @@
D021E0D61DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */; };
D021E0D81DB4FD1300C6B04F /* ItemCollectionItemTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */; };
D021E0DC1DB5237C00C6B04F /* ItemCollectionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */; };
D021FC262024B83700C34AB7 /* FileSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021FC252024B83700C34AB7 /* FileSize.swift */; };
D021FC272024B83700C34AB7 /* FileSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021FC252024B83700C34AB7 /* FileSize.swift */; };
D02EB8071D2B07F300D07ED3 /* OrderStatisticTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */; };
D03120F81DA53FF4006A2A60 /* PeerPresenceTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */; };
D03120FA1DA540F0006A2A60 /* CachedPeerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03120F91DA540F0006A2A60 /* CachedPeerData.swift */; };
@ -343,6 +345,14 @@
D0FA0ACB1E780A26005BB9B7 /* PostboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */; };
D0FA0ACD1E781067005BB9B7 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ACC1E781067005BB9B7 /* Views.swift */; };
D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ACC1E781067005BB9B7 /* Views.swift */; };
D0FC194A201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */; };
D0FC194B201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */; };
D0FC195020208E8800FEDBB2 /* Crc32.h in Headers */ = {isa = PBXBuildFile; fileRef = D0FC194E20208E8800FEDBB2 /* Crc32.h */; };
D0FC195120208E8800FEDBB2 /* Crc32.h in Headers */ = {isa = PBXBuildFile; fileRef = D0FC194E20208E8800FEDBB2 /* Crc32.h */; };
D0FC195220208E8800FEDBB2 /* Crc32.m in Sources */ = {isa = PBXBuildFile; fileRef = D0FC194F20208E8800FEDBB2 /* Crc32.m */; };
D0FC195320208E8800FEDBB2 /* Crc32.m in Sources */ = {isa = PBXBuildFile; fileRef = D0FC194F20208E8800FEDBB2 /* Crc32.m */; };
D0FC19552020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */; };
D0FC19562020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -375,6 +385,7 @@
D021E0D51DB4FCFC00C6B04F /* ItemCollectionInfoTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfoTable.swift; sourceTree = "<group>"; };
D021E0D71DB4FD1300C6B04F /* ItemCollectionItemTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionItemTable.swift; sourceTree = "<group>"; };
D021E0DB1DB5237C00C6B04F /* ItemCollectionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionsView.swift; sourceTree = "<group>"; };
D021FC252024B83700C34AB7 /* FileSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSize.swift; sourceTree = "<group>"; };
D02EB8061D2B07F300D07ED3 /* OrderStatisticTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderStatisticTreeTests.swift; sourceTree = "<group>"; };
D03120F71DA53FF4006A2A60 /* PeerPresenceTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresenceTable.swift; sourceTree = "<group>"; };
D03120F91DA540F0006A2A60 /* CachedPeerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedPeerData.swift; sourceTree = "<group>"; };
@ -538,6 +549,10 @@
D0FA0AC61E77F0A2005BB9B7 /* ItemCollectionInfosView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCollectionInfosView.swift; sourceTree = "<group>"; };
D0FA0AC91E780A26005BB9B7 /* PostboxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostboxView.swift; sourceTree = "<group>"; };
D0FA0ACC1E781067005BB9B7 /* Views.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Views.swift; sourceTree = "<group>"; };
D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaBoxFile.swift; sourceTree = "<group>"; };
D0FC194E20208E8800FEDBB2 /* Crc32.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Crc32.h; sourceTree = "<group>"; };
D0FC194F20208E8800FEDBB2 /* Crc32.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Crc32.m; sourceTree = "<group>"; };
D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerGroupStateView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -575,6 +590,7 @@
D05F09A51C9E9F9300BB6F96 /* MediaResourceStatus.swift */,
D0CE63F51CA1CCB2002BC462 /* MediaResource.swift */,
D09ADF091D2E89F300C8208D /* RandomAccessMediaResourceContext.swift */,
D0FC1949201E8EAF00FEDBB2 /* MediaBoxFile.swift */,
);
name = "Media Box";
sourceTree = "<group>";
@ -754,6 +770,7 @@
D0B1671F1F9EAAA900976B40 /* OrderedList.swift */,
D0AA55121FB4C6AB00C2AB58 /* BinarySearch.swift */,
D0ECCB8E1FE9EB5500609802 /* PostboxLogging.swift */,
D021FC252024B83700C34AB7 /* FileSize.swift */,
);
name = Utils;
sourceTree = "<group>";
@ -812,6 +829,7 @@
D0C26D771FE31CA4004ABF18 /* GroupFeedReadStateSyncOperationsView.swift */,
D0C26D801FE41323004ABF18 /* ChatListGroupReferenceUnreadCounters.swift */,
D04614322004F2CC00EC0EF2 /* LocalMessageTagsView.swift */,
D0FC19542020CB7700FEDBB2 /* PeerGroupStateView.swift */,
);
name = Views;
sourceTree = "<group>";
@ -865,6 +883,8 @@
D0E3A74D1B28A7E300A402D9 /* Supporting Files */ = {
isa = PBXGroup;
children = (
D0FC194E20208E8800FEDBB2 /* Crc32.h */,
D0FC194F20208E8800FEDBB2 /* Crc32.m */,
D044E1611B2AD667001EE087 /* MurMurHash32.h */,
D044E1621B2AD677001EE087 /* MurMurHash32.m */,
D0E3A74F1B28A7E300A402D9 /* Postbox.h */,
@ -909,6 +929,7 @@
files = (
D0B418201D7DFDFD004562A4 /* sqlite3ext.h in Headers */,
D0B4185D1D7DFE35004562A4 /* IpcNotifier.h in Headers */,
D0FC195120208E8800FEDBB2 /* Crc32.h in Headers */,
D050F2651E4A5B4800988324 /* fts3_tokenizer.h in Headers */,
D0B4185B1D7DFE2C004562A4 /* MurMurHash32.h in Headers */,
D0B4181E1D7DFDF8004562A4 /* SQLite-Bridging.h in Headers */,
@ -923,6 +944,7 @@
files = (
D07516771B2EC90400AE42E0 /* fts3_tokenizer.h in Headers */,
D07516451B2D9CEF00AE42E0 /* sqlite3.h in Headers */,
D0FC195020208E8800FEDBB2 /* Crc32.h in Headers */,
D0D511041D64D91C00A97B8A /* IpcNotifier.h in Headers */,
D07516781B2EC90400AE42E0 /* SQLite-Bridging.h in Headers */,
D0E3A7501B28A7E300A402D9 /* Postbox.h in Headers */,
@ -1064,6 +1086,7 @@
C20EB2A31F7179DC00DD3A57 /* PeerNotificationSettingsView.swift in Sources */,
C25B56FE1F431C3300581D02 /* MessageHistoryTagsSummaryTable.swift in Sources */,
D050F2661E4A5B5A00988324 /* MessageGloballyUniqueIdTable.swift in Sources */,
D0FC195320208E8800FEDBB2 /* Crc32.m in Sources */,
D03229EF1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */,
D0AA55141FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */,
D01C7F081EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */,
@ -1106,6 +1129,7 @@
D0B418591D7DFE29004562A4 /* PostboxTransaction.swift in Sources */,
D0F7B1D21E045C6A007EB8A5 /* PeerNotificationSettingsTable.swift in Sources */,
D0C26D701FE2E737004ABF18 /* GroupFeedReadState.swift in Sources */,
D0FC19562020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */,
D0E23DE31E808A9400B9B6D2 /* ItemCollectionIdsView.swift in Sources */,
D0B4181D1D7DFDF4004562A4 /* sqlite3.c in Sources */,
D0F7B1C91E045C6A007EB8A5 /* MessageHistoryTagsTable.swift in Sources */,
@ -1135,9 +1159,11 @@
D0FA0ACE1E781067005BB9B7 /* Views.swift in Sources */,
D0B418501D7DFE20004562A4 /* UnsentMessageIndicesView.swift in Sources */,
D0BEAF641E54B2FA00BD963D /* AccountManager.swift in Sources */,
D021FC272024B83700C34AB7 /* FileSize.swift in Sources */,
D073CE7F1DCBF3B4007511FD /* ItemCollection.swift in Sources */,
D07047B21F3DE40400F6A8D4 /* PendingMessageActionsSummaryView.swift in Sources */,
D0F7B1D91E045C6A007EB8A5 /* OrderStatisticTable.swift in Sources */,
D0FC194B201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */,
D073CEA01DCBF3C1007511FD /* ItemCollectionsView.swift in Sources */,
D0BE383A1E7C1FD4000079AF /* ItemCollectionInfoView.swift in Sources */,
D0B418491D7DFE20004562A4 /* ChatListView.swift in Sources */,
@ -1226,6 +1252,7 @@
D0CE8CF61F703B1E00AA2DB0 /* PendingPeerNotificationSettingsIndexTable.swift in Sources */,
D03229EE1E6B33FD0000AF9C /* SqliteInterface.swift in Sources */,
D01C7F071EFC1ED3008305F1 /* UnorderedItemListTable.swift in Sources */,
D021FC262024B83700C34AB7 /* FileSize.swift in Sources */,
D0AA55131FB4C6AB00C2AB58 /* BinarySearch.swift in Sources */,
D0F9E8631C579F0200037222 /* MediaCleanupTable.swift in Sources */,
D0943AF81FDAC53F001522CC /* ChatLocation.swift in Sources */,
@ -1335,10 +1362,12 @@
D07827C11E0079CB00071108 /* StringIndexTokens.swift in Sources */,
D0F3CC721DDE1CDC008148FA /* ItemCacheTable.swift in Sources */,
D08C713C1C51283C00779C0F /* MessageHistoryIndexTable.swift in Sources */,
D0FC195220208E8800FEDBB2 /* Crc32.m in Sources */,
D019B1CF1E2E770700F80DB3 /* MessageGloballyUniqueIdTable.swift in Sources */,
D0F9E86D1C5A0E5D00037222 /* MetadataTable.swift in Sources */,
D0DA44481E4C7D1E005FDCA7 /* PostboxAccess.swift in Sources */,
D0B167201F9EAAA900976B40 /* OrderedList.swift in Sources */,
D0FC19552020CB7700FEDBB2 /* PeerGroupStateView.swift in Sources */,
D0C26D721FE2E7A8004ABF18 /* GroupFeedReadStateTable.swift in Sources */,
D0943AF11FD99DCD001522CC /* GroupChatListInclusion.swift in Sources */,
D0E3A7841B28AE0900A402D9 /* Peer.swift in Sources */,
@ -1359,6 +1388,7 @@
D04614302004E24600EC0EF2 /* LocalMessageHistoryTagsTable.swift in Sources */,
D00EED1E1C81F28D00341DFF /* MessageHistoryTagsTable.swift in Sources */,
D044CA2C1C617E2D002160FF /* MessageHistoryMetadataTable.swift in Sources */,
D0FC194A201E8EAF00FEDBB2 /* MediaBoxFile.swift in Sources */,
D03120FC1DA55427006A2A60 /* PeerNotificationSettings.swift in Sources */,
D0943B021FDB01D8001522CC /* PostboxUpgrade_14to15.swift in Sources */,
D0F7AB321DCFAB18009AD9A1 /* PeerChatTopIndexableMessageIds.swift in Sources */,

8
Postbox/Crc32.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef Postbox_Crc32_h
#define Postbox_Crc32_h
#import <Foundation/Foundation.h>
uint32_t Crc32(const void *bytes, int length);
#endif

7
Postbox/Crc32.m Normal file
View File

@ -0,0 +1,7 @@
#import "Crc32.h"
#import <zlib.h>
uint32_t Crc32(const void *bytes, int length) {
return (uint32_t)crc32(0, bytes, (uInt)length);
}

10
Postbox/FileSize.swift Normal file
View File

@ -0,0 +1,10 @@
import Foundation
func fileSize(_ path: String) -> Int? {
var value = stat()
if stat(path, &value) == 0 {
return Int(value.st_size)
} else {
return nil
}
}

View File

@ -50,6 +50,9 @@ private func writeEntry(_ entry: GroupFeedIndexEntry, to buffer: WriteBuffer) {
var stableIdValue: UInt32 = stableId
var timestampValue: Int32 = hole.lowerIndex.timestamp
if timestampValue == 0 {
//print("writing 0 hole")
}
var idPeerIdValue: Int64 = hole.lowerIndex.id.peerId.toInt64()
var idNamespaceValue: Int32 = hole.lowerIndex.id.namespace
var idIdValue: Int32 = hole.lowerIndex.id.id
@ -293,6 +296,8 @@ final class GroupFeedIndexTable: Table {
var filledUpperBound: MessageIndex?
var filledLowerBound: MessageIndex?
//self.debugPrintEntries(groupId: groupId)
var adjustedMainHoleIndex: MessageIndex?
do {
var upperItem: GroupFeedIndexEntry?
@ -454,9 +459,13 @@ final class GroupFeedIndexTable: Table {
self.fillHole(insertMessage: insertMessage, groupId: groupId, index: holeIndex, fillType: currentFillType, messages: holeMessages, addOperation: addOperation)
}
//self.debugPrintEntries(groupId: groupId)
for message in remainingMessages {
insertMessage(message)
}
//self.debugPrintEntries(groupId: groupId)
}
private func fillHole(insertMessage: (InternalStoreMessage) -> Void, groupId: PeerGroupId, index: MessageIndex, fillType: HoleFill, messages: [InternalStoreMessage], addOperation: (PeerGroupId, GroupFeedIndexOperation) -> Void) {
@ -714,4 +723,19 @@ final class GroupFeedIndexTable: Table {
}, limit: 1)
return result
}
private func debugPrintEntries(groupId: PeerGroupId) {
print("-----------------------------")
self.valueBox.range(self.table, start: self.lowerBound(groupId: groupId), end: self.upperBound(groupId: groupId), values: { key, value in
let entry = readEntry(groupId: groupId, key: key, value: value)
switch entry {
case let .message(index):
print("message timestamp: \(index.timestamp), peerId: \(index.id.peerId.id), id: \(index.id.id)")
case let .hole(_, hole):
print("hole upper timestamp: \(hole.upperIndex.timestamp), \(hole.upperIndex.id.peerId.id), \(hole.upperIndex.id.id), lower \(hole.lowerIndex.timestamp), \(hole.lowerIndex.id.peerId.id), \(hole.lowerIndex.id.id)")
}
return true
}, limit: 0)
print("-----------------------------")
}
}

View File

@ -1,23 +1,23 @@
import Foundation
public final class GroupFeedState {
public protocol PeerGroupState: PostboxCoding {
func equals(_ other: PeerGroupState) -> Bool
}
private struct GroupFeedStateEntry {
let state: GroupFeedState?
private struct PeerGroupStateEntry {
let state: PeerGroupState?
init(_ state: GroupFeedState?) {
init(_ state: PeerGroupState?) {
self.state = state
}
}
final class GroupFeedStateTable: Table {
final class PeerGroupStateTable: Table {
static func tableSpec(_ id: Int32) -> ValueBoxTable {
return ValueBoxTable(id: id, keyType: .int64)
}
private var cachedStates: [PeerGroupId: GroupFeedStateEntry] = [:]
private var cachedStates: [PeerGroupId: PeerGroupStateEntry] = [:]
private var updatedGroupIds = Set<PeerGroupId>()
private let sharedKey = ValueBoxKey(length: 8)
@ -27,23 +27,22 @@ final class GroupFeedStateTable: Table {
return self.sharedKey
}
func get(_ id: PeerGroupId) -> GroupFeedState? {
func get(_ id: PeerGroupId) -> PeerGroupState? {
if let state = self.cachedStates[id] {
return state.state
} else {
/*if let value = self.valueBox.get(self.table, key: self.key(id)), let state = PostboxDecoder(buffer: value).decodeRootObject() {
self.cachedPeerChatStates[id] = state
if let value = self.valueBox.get(self.table, key: self.key(id)), let state = PostboxDecoder(buffer: value).decodeRootObject() as? PeerGroupState {
self.cachedStates[id] = PeerGroupStateEntry(state)
return state
} else {
self.cachedPeerChatStates[id] = nil
return nil
}*/
self.cachedStates[id] = PeerGroupStateEntry(nil)
return nil
}
}
}
func set(_ id: PeerGroupId, state: GroupFeedState?) {
self.cachedStates[id] = GroupFeedStateEntry(state)
func set(_ id: PeerGroupId, state: PeerGroupState?) {
self.cachedStates[id] = PeerGroupStateEntry(state)
self.updatedGroupIds.insert(id)
}
@ -55,10 +54,11 @@ final class GroupFeedStateTable: Table {
override func beforeCommit() {
if !self.updatedGroupIds.isEmpty {
for id in self.updatedGroupIds {
let sharedEncoder = PostboxEncoder()
if let entry = self.cachedStates[id], let state = entry.state {
/*sharedEncoder.reset()
sharedEncoder.reset()
sharedEncoder.encodeRootObject(state)
self.valueBox.set(self.table, key: self.key(id), value: sharedEncoder.readBufferNoCopy())*/
self.valueBox.set(self.table, key: self.key(id), value: sharedEncoder.readBufferNoCopy())
} else {
self.valueBox.remove(self.table, key: self.key(id))
}
@ -68,3 +68,4 @@ final class GroupFeedStateTable: Table {
}
}

View File

@ -70,4 +70,17 @@ public final class ManagedFile {
public func truncate(count: Int64) {
ftruncate(self.fd, count)
}
public func getSize() -> Int? {
var value = stat()
if fstat(self.fd, &value) == 0 {
return Int(value.st_size)
} else {
return nil
}
}
public func sync() {
fsync(self.fd)
}
}

View File

@ -8,6 +8,11 @@ import Foundation
private final class ResourceStatusContext {
var status: MediaResourceStatus?
let subscribers = Bag<(MediaResourceStatus) -> Void>()
let disposable: Disposable
init(disposable: Disposable) {
self.disposable = disposable
}
}
private final class ResourceDataContext {
@ -24,15 +29,6 @@ private final class ResourceDataContext {
}
}
private func fileSize(_ path: String) -> Int? {
var value = stat()
if stat(path, &value) == 0 {
return Int(value.st_size)
} else {
return nil
}
}
public enum ResourceDataRangeMode {
case complete
case incremental
@ -51,33 +47,31 @@ private struct ResourceStorePaths {
public struct MediaResourceData {
public let path: String
public let offset: Int
public let size: Int
public let complete: Bool
public init(path: String, size: Int, complete: Bool) {
public init(path: String, offset: Int, size: Int, complete: Bool) {
self.path = path
self.offset = offset
self.size = size
self.complete = complete
}
}
public protocol MediaResourceDataFetchCopyLocalItem {
func copyTo(url: URL) -> Bool
}
public enum MediaResourceDataFetchResult {
case dataPart(data: Data, range: Range<Int>, complete: Bool)
case dataPart(resourceOffset: Int, data: Data, range: Range<Int>, complete: Bool)
case resourceSizeUpdated(Int)
case replaceHeader(data: Data, range: Range<Int>)
case moveLocalFile(path: String)
case copyLocalItem(MediaResourceDataFetchCopyLocalItem)
case reset
}
/*public struct MediaResourceDataFetchResult {
public let data: Data
public let complete: Bool
public init(data: Data, complete: Bool) {
self.data = data
self.complete = complete
}
}*/
public struct CachedMediaResourceRepresentationResult {
public let temporaryPath: String
@ -119,12 +113,12 @@ public final class MediaBox {
private let cacheQueue = Queue()
private var statusContexts: [WrappedMediaResourceId: ResourceStatusContext] = [:]
private var dataContexts: [WrappedMediaResourceId: ResourceDataContext] = [:]
private var randomAccessContexts: [WrappedMediaResourceId: RandomAccessMediaResourceContext] = [:]
private var cachedRepresentationContexts: [CachedMediaResourceRepresentationKey: CachedMediaResourceRepresentationContext] = [:]
private var wrappedFetchResource = Promise<(MediaResource, Range<Int>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>>()
public var fetchResource: ((MediaResource, Range<Int>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>)? {
private var fileContexts: [WrappedMediaResourceId: MediaBoxFileContext] = [:]
private var wrappedFetchResource = Promise<(MediaResource, Signal<IndexSet, NoError>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>>()
public var fetchResource: ((MediaResource, Signal<IndexSet, NoError>, MediaResourceFetchTag?) -> Signal<MediaResourceDataFetchResult, NoError>)? {
didSet {
if let fetchResource = self.fetchResource {
wrappedFetchResource.set(.single(fetchResource))
@ -199,12 +193,16 @@ public final class MediaBox {
subscriber.putCompletion()
} else {
self.statusQueue.async {
let resourceId = WrappedMediaResourceId(resource.id)
let statusContext: ResourceStatusContext
if let current = self.statusContexts[WrappedMediaResourceId(resource.id)] {
var statusUpdateDisposable: MetaDisposable?
if let current = self.statusContexts[resourceId] {
statusContext = current
} else {
statusContext = ResourceStatusContext()
self.statusContexts[WrappedMediaResourceId(resource.id)] = statusContext
let statusUpdateDisposableValue = MetaDisposable()
statusContext = ResourceStatusContext(disposable: statusUpdateDisposableValue)
self.statusContexts[resourceId] = statusContext
statusUpdateDisposable = statusUpdateDisposableValue
}
let index = statusContext.subscribers.add({ status in
@ -213,41 +211,33 @@ public final class MediaBox {
if let status = statusContext.status {
subscriber.putNext(status)
} else {
}
if let statusUpdateDisposable = statusUpdateDisposable {
let statusQueue = self.statusQueue
self.dataQueue.async {
let status: MediaResourceStatus
if let _ = fileSize(paths.complete) {
status = .Local
} else {
var fetchingData = false
if let dataContext = self.dataContexts[WrappedMediaResourceId(resource.id)] {
fetchingData = dataContext.fetchDisposable != nil
}
if fetchingData {
let currentSize = fileSize(paths.partial) ?? 0
if let resourceSize = resource.size {
status = .Fetching(isActive: true, progress: Float(currentSize) / Float(resourceSize))
} else {
status = .Fetching(isActive: true, progress: 0.0)
}
} else {
status = .Remote
}
}
self.statusQueue.async {
if let statusContext = self.statusContexts[WrappedMediaResourceId(resource.id)] , statusContext.status == nil {
statusContext.status = status
if let fileContext = self.fileContext(for: resource) {
statusUpdateDisposable.set(fileContext.status(next: { value in
statusQueue.async {
if let context = self.statusContexts[resourceId], context.status != value {
context.status = value
for subscriber in statusContext.subscribers.copyItems() {
subscriber(status)
subscriber(value)
}
}
}
}, completed: {
statusQueue.async {
if let context = self.statusContexts[resourceId] {
context.subscribers.remove(index)
if context.subscribers.isEmpty {
self.statusContexts.removeValue(forKey: resourceId)
context.disposable.dispose()
}
}
}
}, size: resource.size.flatMap(Int32.init)))
}
}
}
@ -257,6 +247,7 @@ public final class MediaBox {
current.subscribers.remove(index)
if current.subscribers.isEmpty {
self.statusContexts.removeValue(forKey: WrappedMediaResourceId(resource.id))
current.disposable.dispose()
}
}
}
@ -297,23 +288,53 @@ public final class MediaBox {
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, size: completeSize, complete: true))
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: completeSize, complete: true))
subscriber.putCompletion()
} else {
subscriber.putNext(MediaResourceData(path: paths.complete, size: completeSize, complete: true))
subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: completeSize, complete: true))
subscriber.putCompletion()
}
} else {
self.dataQueue.async {
let resourceId = WrappedMediaResourceId(resource.id)
let currentContext: ResourceDataContext? = self.dataContexts[resourceId]
if let fileContext = self.fileContext(for: resource) {
let waitUntilAfterInitialFetch: Bool
switch option {
case let .complete(waitUntilFetchStatus):
waitUntilAfterInitialFetch = waitUntilFetchStatus
case let .incremental(waitUntilFetchStatus):
waitUntilAfterInitialFetch = waitUntilFetchStatus
}
let dataDisposable = fileContext.data(range: 0 ..< Int32.max, waitUntilAfterInitialFetch: waitUntilAfterInitialFetch, next: { value in
self.dataQueue.async {
if value.complete {
if let pathExtension = pathExtension {
let symlinkPath = paths.complete + ".\(pathExtension)"
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: value.size, complete: true))
} else {
subscriber.putNext(value)
}
subscriber.putCompletion()
} else {
subscriber.putNext(value)
}
}
})
disposable.set(ActionDisposable {
dataDisposable.dispose()
})
}
/*let currentContext: ResourceDataContext? = self.dataContexts[resourceId]
if let currentContext = currentContext, currentContext.data.complete {
if let pathExtension = pathExtension {
let symlinkPath = paths.complete + ".\(pathExtension)"
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, size: currentContext.data.size, complete: currentContext.data.complete))
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: currentContext.data.size, complete: currentContext.data.complete))
subscriber.putCompletion()
} else {
subscriber.putNext(currentContext.data)
@ -325,10 +346,10 @@ public final class MediaBox {
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, size: completeSize, complete: true))
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: completeSize, complete: true))
subscriber.putCompletion()
} else {
subscriber.putNext(MediaResourceData(path: paths.complete, size: completeSize, complete: true))
subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: completeSize, complete: true))
subscriber.putCompletion()
}
} else {
@ -337,7 +358,7 @@ public final class MediaBox {
dataContext = currentContext
} else {
let partialSize = fileSize(paths.partial) ?? 0
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, size: partialSize, complete: false))
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, offset: 0, size: partialSize, complete: false))
self.dataContexts[resourceId] = dataContext
}
@ -350,7 +371,7 @@ public final class MediaBox {
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, size: data.size, complete: data.complete))
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: data.size, complete: data.complete))
if data.complete {
subscriber.putCompletion()
}
@ -360,7 +381,7 @@ public final class MediaBox {
}
}))
if !waitUntilFetchStatus || dataContext.processedFetch {
subscriber.putNext(MediaResourceData(path: dataContext.data.path, size: 0, complete: false))
subscriber.putNext(MediaResourceData(path: dataContext.data.path, offset: 0, size: 0, complete: false))
}
case let .incremental(waitUntilFetchStatus):
index = dataContext.progresiveDataSubscribers.add((waitUntilFetchStatus, { data in
@ -369,7 +390,7 @@ public final class MediaBox {
if fileSize(symlinkPath) == nil {
let _ = try? FileManager.default.createSymbolicLink(atPath: symlinkPath, withDestinationPath: URL(fileURLWithPath: paths.complete).lastPathComponent)
}
subscriber.putNext(MediaResourceData(path: symlinkPath, size: data.size, complete: data.complete))
subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: data.size, complete: data.complete))
subscriber.putCompletion()
} else {
subscriber.putNext(data)
@ -399,7 +420,7 @@ public final class MediaBox {
}
}
})
}
}*/
}
}
}
@ -408,72 +429,42 @@ public final class MediaBox {
}
}
private func randomAccessContext(for resource: MediaResource, size: Int, tag: MediaResourceFetchTag?) -> RandomAccessMediaResourceContext {
private func fileContext(for resource: MediaResource) -> MediaBoxFileContext? {
assert(self.dataQueue.isCurrent())
let resourceId = WrappedMediaResourceId(resource.id)
let dataContext: RandomAccessMediaResourceContext
if let current = self.randomAccessContexts[resourceId] {
dataContext = current
if let current = self.fileContexts[resourceId] {
return current
} else {
let path = self.pathForId(resource.id) + ".random"
dataContext = RandomAccessMediaResourceContext(path: path, size: size, fetchRange: { [weak self] range in
let disposable = MetaDisposable()
if let strongSelf = self {
strongSelf.dataQueue.async {
let fetch = strongSelf.wrappedFetchResource.get() |> take(1) |> mapToSignal { fetch -> Signal<MediaResourceDataFetchResult, NoError> in
return fetch(resource, range, tag)
let paths = self.storePathsForId(resource.id)
if let fileContext = MediaBoxFileContext(queue: self.dataQueue, path: paths.complete, partialPath: paths.partial) {
self.fileContexts[resourceId] = fileContext
return fileContext
} else {
return nil
}
var offset = 0
disposable.set(fetch.start(next: { [weak strongSelf] result in
if let strongSelf = strongSelf {
strongSelf.dataQueue.async {
if let dataContext = strongSelf.randomAccessContexts[resourceId] {
switch result {
case let .dataPart(data, dataRange, _):
let storeRange = RandomAccessResourceStoreRange(offset: range.lowerBound + offset, data: data.subdata(in: dataRange))
offset += data.count
dataContext.storeRanges([storeRange])
default:
assertionFailure()
}
}
}
}
}))
}
}
return disposable
})
self.randomAccessContexts[resourceId] = dataContext
}
return dataContext
}
public func fetchedResourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, tag: MediaResourceFetchTag?) -> Signal<Void, NoError> {
public func fetchedResourceData(_ resource: MediaResource, in range: Range<Int>, tag: MediaResourceFetchTag?) -> Signal<Void, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.dataQueue.async {
let resourceId = WrappedMediaResourceId(resource.id)
let dataContext = self.randomAccessContext(for: resource, size: size, tag: tag)
let fileContext = self.fileContext(for: resource)
let listener = dataContext.addListenerForFetchedData(in: range)
let fetchResource = self.wrappedFetchResource.get()
let fetchedDisposable = fileContext?.fetched(range: Int32(range.lowerBound) ..< Int32(range.upperBound), fetch: { ranges in
return fetchResource |> mapToSignal { fetch in
return fetch(resource, ranges, tag)
}
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable { [weak self] in
if let strongSelf = self {
strongSelf.dataQueue.async {
if let dataContext = strongSelf.randomAccessContexts[resourceId] {
dataContext.removeListenerForFetchedData(listener)
if !dataContext.hasDataListeners() {
//let _ = strongSelf.randomAccessContexts.removeValue(forKey: resourceId)
}
}
}
}
disposable.set(ActionDisposable {
fetchedDisposable?.dispose()
})
}
@ -481,55 +472,56 @@ public final class MediaBox {
}
}
public func resourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, tag: MediaResourceFetchTag?, mode: ResourceDataRangeMode = .complete) -> Signal<Data, NoError> {
public func resourceData(_ resource: MediaResource, size: Int, in range: Range<Int>, mode: ResourceDataRangeMode = .complete) -> Signal<Data, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.dataQueue.async {
let resourceId = WrappedMediaResourceId(resource.id)
let dataContext = self.randomAccessContext(for: resource, size: size, tag: tag)
let fileContext = self.fileContext(for: resource)
let listenerMode: RandomAccessResourceDataRangeMode
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) {
if result.complete {
let resultData = data.subdata(in: result.offset ..< (result.offset + result.size))
subscriber.putNext(resultData)
subscriber.putCompletion()
} else {
switch mode {
case .complete:
listenerMode = .Complete
break
case .incremental:
listenerMode = .Incremental
break
case .partial:
listenerMode = .Partial
}
var offset = 0
let listener = dataContext.addListenerForData(in: range, mode: listenerMode, updated: { [weak self] data in
if let strongSelf = self {
strongSelf.dataQueue.async {
subscriber.putNext(data)
switch mode {
case .complete, .partial:
offset = max(offset, data.count)
case .incremental:
offset += data.count
}
if offset == range.count {
subscriber.putCompletion()
break
}
}
}
})
disposable.set(ActionDisposable { [weak self] in
if let strongSelf = self {
strongSelf.dataQueue.async {
if let dataContext = strongSelf.randomAccessContexts[resourceId] {
dataContext.removeListenerForData(listener)
if !dataContext.hasDataListeners() {
//let _ = strongSelf.randomAccessContexts.removeValue(forKey: resourceId)
}
}
disposable.set(ActionDisposable {
dataDisposable?.dispose()
})
}
return disposable
}
}
public func resourceRangesStatus(_ resource: MediaResource) -> Signal<IndexSet, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.dataQueue.async {
let fileContext = self.fileContext(for: resource)
let statusDisposable = fileContext?.rangeStatus(next: { result in
subscriber.putNext(result)
}, completed: {
subscriber.putCompletion()
})
disposable.set(ActionDisposable {
statusDisposable?.dispose()
})
}
@ -542,7 +534,6 @@ public final class MediaBox {
let disposable = MetaDisposable()
self.dataQueue.async {
let resourceId = WrappedMediaResourceId(resource.id)
let paths = self.storePathsForId(resource.id)
if let _ = fileSize(paths.complete) {
@ -551,12 +542,27 @@ public final class MediaBox {
}
subscriber.putCompletion()
} else {
if let fileContext = self.fileContext(for: resource) {
let fetchResource = self.wrappedFetchResource.get()
let fetchedDisposable = fileContext.fetchedFullRange(fetch: { ranges in
return fetchResource |> mapToSignal { fetch in
return fetch(resource, ranges, tag)
}
}, completed: {
if implNext {
subscriber.putNext(.remote)
}
subscriber.putCompletion()
})
disposable.set(fetchedDisposable)
}
/*
let currentSize = fileSize(paths.partial) ?? 0
let dataContext: ResourceDataContext
if let current = self.dataContexts[resourceId] {
dataContext = current
} else {
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, size: currentSize, complete: false))
dataContext = ResourceDataContext(data: MediaResourceData(path: paths.partial, offset: 0, size: currentSize, complete: false))
self.dataContexts[resourceId] = dataContext
}
@ -582,7 +588,9 @@ public final class MediaBox {
let file = Atomic<ManagedFile?>(value: nil)
let dataQueue = self.dataQueue
dataContext.fetchDisposable = ((self.wrappedFetchResource.get() |> take(1) |> mapToSignal { fetch -> Signal<MediaResourceDataFetchResult, NoError> in
return fetch(resource, currentSize ..< Int.max, tag)
var ranges = IndexSet()
ranges.insert(integersIn: currentSize ..< Int.max)
return fetch(resource, .single(ranges), tag)
}) |> afterDisposed {
dataQueue.async {
let _ = file.modify { current in
@ -594,7 +602,9 @@ public final class MediaBox {
let _ = self.ensureDirectoryCreated
switch resultOption {
case let .dataPart(data, dataRange, complete):
case .resourceSizeUpdated:
break
case let .dataPart(_, data, dataRange, complete):
var currentFile: ManagedFile?
let _ = file.modify { current in
if let current = current {
@ -624,9 +634,9 @@ public final class MediaBox {
if complete {
let linkResult = link(paths.partial, paths.complete)
//assert(linkResult == 0)
updatedData = MediaResourceData(path: paths.complete, size: updatedSize, complete: true)
updatedData = MediaResourceData(path: paths.complete, offset: 0, size: updatedSize, complete: true)
} else {
updatedData = MediaResourceData(path: paths.partial, size: updatedSize, complete: false)
updatedData = MediaResourceData(path: paths.partial, offset: 0, size: updatedSize, complete: false)
}
dataContext.data = updatedData
@ -716,7 +726,7 @@ public final class MediaBox {
let updatedSize = offset
let updatedData: MediaResourceData
updatedData = MediaResourceData(path: paths.partial, size: updatedSize, complete: false)
updatedData = MediaResourceData(path: paths.partial, offset: 0, size: updatedSize, complete: false)
dataContext.data = updatedData
@ -779,7 +789,7 @@ public final class MediaBox {
let updatedData: MediaResourceData
let linkResult = link(paths.partial, paths.complete)
assert(linkResult == 0)
updatedData = MediaResourceData(path: paths.complete, size: updatedSize, complete: true)
updatedData = MediaResourceData(path: paths.complete, offset: 0, size: updatedSize, complete: true)
dataContext.data = updatedData
@ -857,7 +867,7 @@ public final class MediaBox {
}
}
}
})
})*/
}
}
@ -867,7 +877,10 @@ public final class MediaBox {
public func cancelInteractiveResourceFetch(_ resource: MediaResource) {
self.dataQueue.async {
let resourceId = WrappedMediaResourceId(resource.id)
if let fileContext = self.fileContext(for: resource) {
fileContext.cancelFullRangeFetches()
}
/*let resourceId = WrappedMediaResourceId(resource.id)
if let dataContext = self.dataContexts[resourceId], dataContext.fetchDisposable != nil {
dataContext.fetchDisposable?.dispose()
dataContext.fetchDisposable = nil
@ -887,7 +900,7 @@ public final class MediaBox {
}
}
}
}
}*/
}
}
@ -897,7 +910,7 @@ public final class MediaBox {
self.concurrentQueue.async {
let path = self.cachedRepresentationPathForId(resource.id, representation: representation)
if let size = fileSize(path) {
subscriber.putNext(MediaResourceData(path: path, size: size, complete: true))
subscriber.putNext(MediaResourceData(path: path, offset: 0, size: size, complete: true))
subscriber.putCompletion()
} else {
self.dataQueue.async {
@ -961,7 +974,7 @@ public final class MediaBox {
if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] {
strongSelf.cachedRepresentationContexts.removeValue(forKey: key)
if let size = fileSize(path) {
let data = MediaResourceData(path: path, size: size, complete: true)
let data = MediaResourceData(path: path, offset: 0, size: size, complete: true)
context.currentData = data
for subscriber in context.dataSubscribers.copyItems() {
subscriber(data)
@ -970,7 +983,7 @@ public final class MediaBox {
}
} else {
if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] {
let data = MediaResourceData(path: path, size: 0, complete: false)
let data = MediaResourceData(path: path, offset: 0, size: 0, complete: false)
context.currentData = data
for subscriber in context.dataSubscribers.copyItems() {
subscriber(data)
@ -1006,6 +1019,87 @@ public final class MediaBox {
}
}
public func collectOtherResourceUsage(excludeIds: Set<WrappedMediaResourceId>) -> Signal<(Int64, [String], Int64), NoError> {
return Signal { subscriber in
self.dataQueue.async {
var result: Int64 = 0
var excludeNames = Set<String>()
for id in excludeIds {
let partial = "\(self.fileNameForId(id.id))_partial"
let meta = "\(self.fileNameForId(id.id))_meta"
let complete = self.fileNameForId(id.id)
excludeNames.insert(meta)
excludeNames.insert(partial)
excludeNames.insert(complete)
}
var fileIds = Set<Data>()
var paths: [String] = []
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
loop: for url in enumerator {
if let url = url as? URL {
if excludeNames.contains(url.lastPathComponent) {
continue loop
}
if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data {
if fileIds.contains(fileId) {
paths.append(url.lastPathComponent)
continue loop
}
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
fileIds.insert(fileId)
paths.append(url.lastPathComponent)
result += Int64(value)
}
}
}
}
}
var cacheResult: Int64 = 0
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 value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
cacheResult += Int64(value)
}
}
}
}
subscriber.putNext((result, paths, cacheResult))
subscriber.putCompletion()
}
return EmptyDisposable
}
}
public func removeOtherCachedResources(paths: [String]) -> Signal<Void, NoError> {
return Signal { subscriber in
self.dataQueue.async {
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
}
}
public func removeCachedResources(_ ids: Set<WrappedMediaResourceId>) -> Signal<Void, NoError> {
return Signal { subscriber in
self.dataQueue.async {
@ -1014,6 +1108,7 @@ public final class MediaBox {
unlink(paths.complete)
unlink(paths.partial)
}
subscriber.putCompletion()
}
return EmptyDisposable
}

822
Postbox/MediaBoxFile.swift Normal file
View File

@ -0,0 +1,822 @@
import Foundation
#if os(iOS)
import SwiftSignalKit
#else
import SwiftSignalKitMac
#endif
import sqlcipher
private final class MediaBoxFileMap {
fileprivate(set) var sum: Int32
private(set) var ranges: IndexSet
private(set) var truncationSize: Int32?
init() {
self.sum = 0
self.ranges = IndexSet()
self.truncationSize = nil
}
init?(fd: ManagedFile) {
guard let length = fd.getSize() else {
return nil
}
var crc: UInt32 = 0
var count: Int32 = 0
var sum: Int32 = 0
var ranges: IndexSet = IndexSet()
guard fd.read(&crc, 4) == 4 else {
return nil
}
guard fd.read(&count, 4) == 4 else {
return nil
}
if count < 0 {
return nil
}
if count < 0 || length < 4 + 4 + count * 2 * 4 {
return nil
}
var truncationSizeValue: Int32 = 0
var data = Data(count: Int(4 + count * 2 * 4))
if !(data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Bool in
guard fd.read(bytes, data.count) == data.count else {
return false
}
memcpy(&truncationSizeValue, bytes, 4)
let calculatedCrc = Crc32(bytes, Int32(data.count))
if calculatedCrc != crc {
return false
}
var offset = 4
for _ in 0 ..< count {
var intervalOffset: Int32 = 0
var intervalLength: Int32 = 0
memcpy(&intervalOffset, bytes.advanced(by: offset), 4)
memcpy(&intervalLength, bytes.advanced(by: offset + 4), 4)
offset += 8
ranges.insert(integersIn: Int(intervalOffset) ..< Int(intervalOffset + intervalLength))
sum += intervalLength
}
return true
}) {
return nil
}
self.sum = sum
self.ranges = ranges
if truncationSizeValue == -1 {
self.truncationSize = nil
} else {
self.truncationSize = truncationSizeValue
}
}
func serialize(to file: ManagedFile) {
file.seek(position: 0)
let buffer = WriteBuffer()
var zero: Int32 = 0
buffer.write(&zero, offset: 0, length: 4)
let rangeView = self.ranges.rangeView
var count: Int32 = Int32(rangeView.count)
buffer.write(&count, offset: 0, length: 4)
var truncationSizeValue: Int32 = self.truncationSize ?? -1
buffer.write(&truncationSizeValue, offset: 0, length: 4)
for range in rangeView {
var intervalOffset = Int32(range.lowerBound)
var intervalLength = Int32(range.count)
buffer.write(&intervalOffset, offset: 0, length: 4)
buffer.write(&intervalLength, offset: 0, length: 4)
}
var crc: UInt32 = Crc32(buffer.memory.advanced(by: 4 * 2), Int32(buffer.length - 4 * 2))
memcpy(buffer.memory, &crc, 4)
let written = file.write(buffer.memory, count: buffer.length)
assert(written == buffer.length)
}
fileprivate func fill(_ range: Range<Int32>) {
let intRange = Range(Int(range.lowerBound) ..< Int(range.upperBound))
let previousCount = self.ranges.count(in: intRange)
self.ranges.insert(integersIn: intRange)
self.sum += Int32(range.count - previousCount)
}
fileprivate func truncate(_ size: Int32) {
self.truncationSize = size
}
fileprivate func reset() {
self.truncationSize = nil
self.ranges.removeAll()
self.sum = 0
}
fileprivate func contains(_ range: Range<Int32>) -> Bool {
let maxValue: Int
if let truncationSize = self.truncationSize {
maxValue = Int(truncationSize)
} else {
maxValue = Int.max
}
let intRange = Range(Int(range.lowerBound) ..< min(maxValue, Int(range.upperBound)))
return self.ranges.contains(integersIn: intRange)
}
}
private class MediaBoxPartialFileDataRequest {
let range: Range<Int32>
var waitingUntilAfterInitialFetch: Bool
let completion: (MediaResourceData) -> Void
init(range: Range<Int32>, waitingUntilAfterInitialFetch: Bool, completion: @escaping (MediaResourceData) -> Void) {
self.range = range
self.waitingUntilAfterInitialFetch = waitingUntilAfterInitialFetch
self.completion = completion
}
}
final class MediaBoxPartialFile {
private let queue: Queue
private let path: String
private let completePath: String
private let completed: (Int32) -> Void
private let metadataFd: ManagedFile
private let fd: ManagedFile
fileprivate let fileMap: MediaBoxFileMap
private var dataRequests = Bag<MediaBoxPartialFileDataRequest>()
private let missingRanges: MediaBoxFileMissingRanges
private let rangeStatusRequests = Bag<((IndexSet) -> Void, () -> Void)>()
private let statusRequests = Bag<((MediaResourceStatus) -> Void, Int32?)>()
private let fullRangeRequests = Bag<Disposable>()
private var currentFetch: (Promise<IndexSet>, Disposable)?
private var processedAtLeastOneFetch: Bool = false
init?(queue: Queue, path: String, completePath: String, completed: @escaping (Int32) -> Void) {
assert(queue.isCurrent())
if let metadataFd = ManagedFile(queue: queue, path: path + ".meta", mode: .readwrite), let fd = ManagedFile(queue: queue, path: path, mode: .readwrite) {
self.queue = queue
self.path = path
self.completePath = completePath
self.completed = completed
self.metadataFd = metadataFd
self.fd = fd
if let fileMap = MediaBoxFileMap(fd: self.metadataFd) {
self.fileMap = fileMap
} else {
self.fileMap = MediaBoxFileMap()
}
self.missingRanges = MediaBoxFileMissingRanges()
} else {
return nil
}
}
deinit {
self.currentFetch?.1.dispose()
}
var storedSize: Int32 {
assert(self.queue.isCurrent())
return self.fileMap.sum
}
func reset() {
assert(self.queue.isCurrent())
self.fileMap.reset()
self.fileMap.serialize(to: self.metadataFd)
for request in self.dataRequests.copyItems() {
request.completion(MediaResourceData(path: self.path, offset: Int(request.range.lowerBound), size: 0, complete: false))
}
if let updatedRanges = self.missingRanges.reset(fileMap: self.fileMap) {
self.updateRequestRanges(updatedRanges, fetch: nil)
}
if !self.rangeStatusRequests.isEmpty {
let ranges = self.fileMap.ranges
for (f, _) in self.rangeStatusRequests.copyItems() {
f(ranges)
}
}
self.updateStatuses()
}
func moveLocalFile(tempPath: String) {
assert(self.queue.isCurrent())
do {
try FileManager.default.moveItem(atPath: tempPath, toPath: self.completePath)
if let size = fileSize(self.completePath) {
unlink(self.path)
unlink(self.path + ".meta")
for completion in self.missingRanges.clear() {
completion()
}
if let (_, disposable) = self.currentFetch {
self.currentFetch = nil
disposable.dispose()
}
for request in self.dataRequests.copyItems() {
request.completion(MediaResourceData(path: self.completePath, offset: Int(request.range.lowerBound), size: max(0, size - Int(request.range.lowerBound)), complete: true))
}
self.dataRequests.removeAll()
for statusRequest in self.statusRequests.copyItems() {
statusRequest.0(.Local)
}
self.statusRequests.removeAll()
self.completed(self.fileMap.sum)
} else {
assertionFailure()
}
} catch {
assertionFailure()
}
}
func copyLocalItem(_ item: MediaResourceDataFetchCopyLocalItem) {
assert(self.queue.isCurrent())
do {
if item.copyTo(url: URL(fileURLWithPath: self.completePath)) {
} else {
return
}
if let size = fileSize(self.completePath) {
unlink(self.path)
unlink(self.path + ".meta")
for completion in self.missingRanges.clear() {
completion()
}
if let (_, disposable) = self.currentFetch {
self.currentFetch = nil
disposable.dispose()
}
for request in self.dataRequests.copyItems() {
request.completion(MediaResourceData(path: self.completePath, offset: Int(request.range.lowerBound), size: max(0, size - Int(request.range.lowerBound)), complete: true))
}
self.dataRequests.removeAll()
for statusRequest in self.statusRequests.copyItems() {
statusRequest.0(.Local)
}
self.statusRequests.removeAll()
self.completed(self.fileMap.sum)
} else {
assertionFailure()
}
} catch {
assertionFailure()
}
}
func truncate(_ size: Int32) {
assert(self.queue.isCurrent())
let range: Range<Int32> = size ..< Int32.max
self.fileMap.truncate(size)
self.fileMap.serialize(to: self.metadataFd)
self.checkDataRequestsAfterFill(range: range)
}
func write(offset: Int32, data: Data, dataRange: Range<Int>) {
assert(self.queue.isCurrent())
self.fd.seek(position: Int64(offset))
let written = data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Int in
return self.fd.write(bytes.advanced(by: dataRange.lowerBound), count: dataRange.count)
}
assert(written == dataRange.count)
let range: Range<Int32> = offset ..< (offset + Int32(dataRange.count))
self.fileMap.fill(range)
self.fileMap.serialize(to: self.metadataFd)
self.checkDataRequestsAfterFill(range: range)
}
func checkDataRequestsAfterFill(range: Range<Int32>) {
var removeIndices: [(Int, MediaBoxPartialFileDataRequest)] = []
for (index, request) in self.dataRequests.copyItemsWithIndices() {
if request.range.overlaps(range) {
var maxValue = request.range.upperBound
if let truncationSize = self.fileMap.truncationSize {
maxValue = truncationSize
}
if request.range.lowerBound > maxValue {
assertionFailure()
removeIndices.append((index, request))
} else {
let intRange = Range(Int(request.range.lowerBound) ..< Int(maxValue))
if self.fileMap.ranges.contains(integersIn: intRange) {
removeIndices.append((index, request))
}
}
}
}
if !removeIndices.isEmpty {
for (index, request) in removeIndices {
self.dataRequests.remove(index)
var maxValue = request.range.upperBound
if let truncationSize = self.fileMap.truncationSize {
maxValue = truncationSize
}
request.completion(MediaResourceData(path: self.path, offset: Int(request.range.lowerBound), size: Int(maxValue) - Int(request.range.lowerBound), complete: true))
}
}
var isCompleted = false
if let truncationSize = self.fileMap.truncationSize, self.fileMap.contains(0 ..< truncationSize) {
isCompleted = true
}
if isCompleted {
for completion in self.missingRanges.clear() {
completion()
}
} else {
if let (updatedRanges, completions) = self.missingRanges.fill(range) {
self.updateRequestRanges(updatedRanges, fetch: nil)
completions.forEach({ $0() })
}
}
if !self.rangeStatusRequests.isEmpty {
let ranges = self.fileMap.ranges
for (f, completed) in self.rangeStatusRequests.copyItems() {
f(ranges)
if isCompleted {
completed()
}
}
if isCompleted {
self.rangeStatusRequests.removeAll()
}
}
self.updateStatuses()
if isCompleted {
for statusRequest in self.statusRequests.copyItems() {
statusRequest.0(.Local)
}
self.statusRequests.removeAll()
self.fd.sync()
let linkResult = link(self.path, self.completePath)
assert(linkResult == 0)
self.completed(self.fileMap.sum)
}
}
func read(range: Range<Int32>) -> Data? {
assert(self.queue.isCurrent())
if self.fileMap.contains(range) {
self.fd.seek(position: Int64(range.lowerBound))
var data = Data(count: range.count)
let readBytes = data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Int in
return self.fd.read(bytes, data.count)
}
if readBytes == data.count {
return data
} else {
return nil
}
} else {
return nil
}
}
func data(range: Range<Int32>, waitUntilAfterInitialFetch: Bool, next: @escaping (MediaResourceData) -> Void) -> Disposable {
assert(self.queue.isCurrent())
if self.fileMap.contains(range) {
next(MediaResourceData(path: self.path, offset: Int(range.lowerBound), size: range.count, complete: true))
return EmptyDisposable
}
var waitingUntilAfterInitialFetch = false
if waitUntilAfterInitialFetch && !self.processedAtLeastOneFetch {
waitingUntilAfterInitialFetch = true
} else {
next(MediaResourceData(path: self.path, offset: Int(range.lowerBound), size: 0, complete: false))
}
let index = self.dataRequests.add(MediaBoxPartialFileDataRequest(range: range, waitingUntilAfterInitialFetch: waitingUntilAfterInitialFetch, completion: { data in
next(data)
}))
let queue = self.queue
return ActionDisposable { [weak self] in
queue.async {
if let strongSelf = self {
strongSelf.dataRequests.remove(index)
}
}
}
}
func fetched(range: Range<Int32>, fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
assert(self.queue.isCurrent())
if self.fileMap.contains(range) {
completed()
return EmptyDisposable
}
let (index, updatedRanges) = self.missingRanges.addRequest(fileMap: self.fileMap, range: range, completion: {
completed()
})
if let updatedRanges = updatedRanges {
self.updateRequestRanges(updatedRanges, fetch: fetch)
}
let queue = self.queue
return ActionDisposable { [weak self] in
queue.async {
if let strongSelf = self {
if let updatedRanges = strongSelf.missingRanges.removeRequest(fileMap: strongSelf.fileMap, index: index) {
strongSelf.updateRequestRanges(updatedRanges, fetch: nil)
}
}
}
}
}
func fetchedFullRange(fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
let queue = self.queue
let disposable = MetaDisposable()
let index = self.fullRangeRequests.add(disposable)
self.updateStatuses()
disposable.set(self.fetched(range: 0 ..< Int32.max, fetch: fetch, completed: { [weak self] in
queue.async {
if let strongSelf = self {
strongSelf.fullRangeRequests.remove(index)
if strongSelf.fullRangeRequests.isEmpty {
strongSelf.updateStatuses()
}
}
completed()
}
}))
return ActionDisposable { [weak self] in
queue.async {
if let strongSelf = self {
strongSelf.fullRangeRequests.remove(index)
disposable.dispose()
if strongSelf.fullRangeRequests.isEmpty {
strongSelf.updateStatuses()
}
}
}
}
}
func cancelFullRangeFetches() {
self.fullRangeRequests.copyItems().forEach({ $0.dispose() })
self.fullRangeRequests.removeAll()
self.updateStatuses()
}
private func updateStatuses() {
if !self.statusRequests.isEmpty {
for (f, size) in self.statusRequests.copyItems() {
let status = self.immediateStatus(size: size)
f(status)
}
}
}
func rangeStatus(next: @escaping (IndexSet) -> Void, completed: @escaping () -> Void) -> Disposable {
assert(self.queue.isCurrent())
next(self.fileMap.ranges)
if let truncationSize = self.fileMap.truncationSize, self.fileMap.contains(0 ..< truncationSize) {
completed()
return EmptyDisposable
}
let index = self.rangeStatusRequests.add((next, completed))
let queue = self.queue
return ActionDisposable { [weak self] in
queue.async {
if let strongSelf = self {
strongSelf.rangeStatusRequests.remove(index)
}
}
}
}
private func immediateStatus(size: Int32?) -> MediaResourceStatus {
let status: MediaResourceStatus
if self.fullRangeRequests.isEmpty {
status = .Remote
} else {
let progress: Float
if let truncationSize = self.fileMap.truncationSize, truncationSize != 0 {
progress = Float(self.fileMap.sum) / Float(truncationSize)
} else if let size = size {
progress = Float(self.fileMap.sum) / Float(size)
} else {
progress = 0.0
}
status = .Fetching(isActive: true, progress: progress)
}
return status
}
func status(next: @escaping (MediaResourceStatus) -> Void, completed: @escaping () -> Void, size: Int32?) -> Disposable {
let index = self.statusRequests.add((next, size))
let value = self.immediateStatus(size: size)
next(value)
if case .Local = value {
completed()
return EmptyDisposable
} else {
let queue = self.queue
return ActionDisposable { [weak self] in
queue.async {
if let strongSelf = self {
strongSelf.statusRequests.remove(index)
}
}
}
}
}
private func updateRequestRanges(_ ranges: IndexSet, fetch: ((Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>)?) {
assert(self.queue.isCurrent())
if ranges.isEmpty {
if let (_, disposable) = self.currentFetch {
self.currentFetch = nil
disposable.dispose()
}
} else {
if let (promise, _) = self.currentFetch {
promise.set(.single(ranges))
} else if let fetch = fetch {
let promise = Promise<IndexSet>()
let disposable = MetaDisposable()
self.currentFetch = (promise, disposable)
disposable.set((fetch(promise.get()) |> deliverOn(self.queue)).start(next: { [weak self] data in
if let strongSelf = self {
switch data {
case .reset:
if !strongSelf.fileMap.ranges.isEmpty {
strongSelf.reset()
}
case let .resourceSizeUpdated(size):
strongSelf.truncate(Int32(size))
case let .dataPart(resourceOffset, data, range, complete):
if !data.isEmpty {
strongSelf.write(offset: Int32(resourceOffset), data: data, dataRange: range)
}
if complete {
if let maxOffset = strongSelf.fileMap.ranges.max() {
let maxValue = max(resourceOffset + range.count, maxOffset)
strongSelf.truncate(Int32(maxValue))
}
}
case let .replaceHeader(data, range):
strongSelf.write(offset: 0, data: data, dataRange: range)
case let .moveLocalFile(path):
strongSelf.moveLocalFile(tempPath: path)
case let .copyLocalItem(item):
strongSelf.copyLocalItem(item)
}
if !strongSelf.processedAtLeastOneFetch {
strongSelf.processedAtLeastOneFetch = true
for request in strongSelf.dataRequests.copyItems() {
if request.waitingUntilAfterInitialFetch {
request.waitingUntilAfterInitialFetch = false
if strongSelf.fileMap.contains(request.range) {
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(request.range.lowerBound), size: request.range.count, complete: true))
} else {
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(request.range.lowerBound), size: 0, complete: false))
}
}
}
}
}
}))
promise.set(.single(ranges))
}
}
}
}
private final class MediaBoxFileMissingRange {
var range: Range<Int32>
var remainingRanges: IndexSet
let completion: () -> Void
init(range: Range<Int32>, completion: @escaping () -> Void) {
self.range = range
let intRange = Range(Int(range.lowerBound) ..< Int(range.upperBound))
self.remainingRanges = IndexSet(integersIn: intRange)
self.completion = completion
}
}
private final class MediaBoxFileMissingRanges {
private var requestedRanges = Bag<MediaBoxFileMissingRange>()
private var missingRanges = IndexSet()
func clear() -> [() -> Void] {
let completions = self.requestedRanges.copyItems().map({ $0.completion })
self.requestedRanges.removeAll()
return completions
}
func reset(fileMap: MediaBoxFileMap) -> IndexSet? {
return self.update(fileMap: fileMap)
}
func fill(_ range: Range<Int32>) -> (IndexSet, [() -> Void])? {
let intRange = Range(Int(range.lowerBound) ..< Int(range.upperBound))
if self.missingRanges.intersects(integersIn: intRange) {
self.missingRanges.remove(integersIn: intRange)
var completions: [() -> Void] = []
for (index, item) in self.requestedRanges.copyItemsWithIndices() {
if item.range.overlaps(range) {
item.remainingRanges.remove(integersIn: intRange)
if item.remainingRanges.isEmpty {
self.requestedRanges.remove(index)
completions.append(item.completion)
}
}
}
return (self.missingRanges, completions)
} else {
return nil
}
}
func addRequest(fileMap: MediaBoxFileMap, range: Range<Int32>, completion: @escaping () -> Void) -> (Int, IndexSet?) {
let index = self.requestedRanges.add(MediaBoxFileMissingRange(range: range, completion: completion))
return (index, self.update(fileMap: fileMap))
}
func removeRequest(fileMap: MediaBoxFileMap, index: Int) -> IndexSet? {
self.requestedRanges.remove(index)
return self.update(fileMap: fileMap)
}
private func update(fileMap: MediaBoxFileMap) -> IndexSet? {
var requested = IndexSet()
for (item) in self.requestedRanges.copyItems() {
let intRange = Range(Int(item.range.lowerBound) ..< Int(item.range.upperBound))
requested.insert(integersIn: intRange)
}
requested.subtract(fileMap.ranges)
if requested != self.missingRanges {
self.missingRanges = requested
return requested
}
return nil
}
}
private enum MediaBoxFileContent {
case complete(String, Int)
case partial(MediaBoxPartialFile)
}
final class MediaBoxFileContext {
private let queue: Queue
private let path: String
private let partialPath: String
private var content: MediaBoxFileContent
init?(queue: Queue, path: String, partialPath: String) {
assert(queue.isCurrent())
self.queue = queue
self.path = path
self.partialPath = partialPath
var completeImpl: ((Int32) -> Void)?
if let size = fileSize(path) {
self.content = .complete(path, size)
} else if let file = MediaBoxPartialFile(queue: queue, path: partialPath, completePath: path, completed: { size in
completeImpl?(size)
}) {
self.content = .partial(file)
completeImpl = { [weak self] size in
queue.async {
if let strongSelf = self {
strongSelf.content = .complete(path, Int(size))
}
}
}
} else {
return nil
}
}
deinit {
assert(self.queue.isCurrent())
}
func data(range: Range<Int32>, waitUntilAfterInitialFetch: Bool, next: @escaping (MediaResourceData) -> Void) -> Disposable {
switch self.content {
case let .complete(path, size):
next(MediaResourceData(path: path, offset: Int(range.lowerBound), size: min(Int(range.upperBound), size), complete: true))
return EmptyDisposable
case let .partial(file):
return file.data(range: range, waitUntilAfterInitialFetch: waitUntilAfterInitialFetch, next: next)
}
}
func fetched(range: Range<Int32>, fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
switch self.content {
case .complete:
return EmptyDisposable
case let .partial(file):
return file.fetched(range: range, fetch: fetch, completed: completed)
}
}
func fetchedFullRange(fetch: @escaping (Signal<IndexSet, NoError>) -> Signal<MediaResourceDataFetchResult, NoError>, completed: @escaping () -> Void) -> Disposable {
switch self.content {
case .complete:
return EmptyDisposable
case let .partial(file):
return file.fetchedFullRange(fetch: fetch, completed: completed)
}
}
func cancelFullRangeFetches() {
switch self.content {
case .complete:
break
case let .partial(file):
file.cancelFullRangeFetches()
}
}
func rangeStatus(next: @escaping (IndexSet) -> Void, completed: @escaping () -> Void) -> Disposable {
switch self.content {
case let .complete(_, size):
next(IndexSet(0 ..< size))
completed()
return EmptyDisposable
case let .partial(file):
return file.rangeStatus(next: next, completed: completed)
}
}
func status(next: @escaping (MediaResourceStatus) -> Void, completed: @escaping () -> Void, size: Int32?) -> Disposable {
switch self.content {
case .complete:
next(.Local)
return EmptyDisposable
case let .partial(file):
return file.status(next: next, completed: completed, size: size)
}
}
}

View File

@ -651,14 +651,26 @@ public final class StoreMessage {
}
}
public func withUpdatedFlags(_ flags: StoreMessageFlags) -> StoreMessage {
if flags == self.flags {
return self
} else {
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media)
}
}
public func withUpdatedAttributes(_ attributes: [MessageAttribute]) -> StoreMessage {
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: self.localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: attributes, media: self.media)
}
public func withUpdatedLocalTags(_ localTags: LocalMessageTags) -> StoreMessage {
if localTags == self.localTags {
return self
} else {
return StoreMessage(id: self.id, globallyUniqueId: self.globallyUniqueId, groupingKey: self.groupingKey, timestamp: self.timestamp, flags: self.flags, tags: self.tags, globalTags: self.globalTags, localTags: localTags, forwardInfo: self.forwardInfo, authorId: self.authorId, text: self.text, attributes: self.attributes, media: self.media)
}
}
}
final class InternalStoreMessage {
let id: MessageId

View File

@ -13,6 +13,7 @@ public enum AdditionalMessageHistoryViewData {
case cachedPeerData(PeerId)
case cachedPeerDataMessages(PeerId)
case peerChatState(PeerId)
case peerGroupState(PeerGroupId)
case totalUnreadCount
case peerNotificationSettings(PeerId)
}
@ -21,6 +22,7 @@ public enum AdditionalMessageHistoryViewDataEntry {
case cachedPeerData(PeerId, CachedPeerData?)
case cachedPeerDataMessages(PeerId, [MessageId: Message]?)
case peerChatState(PeerId, PeerChatState?)
case peerGroupState(PeerGroupId, PeerGroupState?)
case totalUnreadCount(Int32)
case peerNotificationSettings(PeerNotificationSettings?)
}
@ -1001,6 +1003,11 @@ final class MutableMessageHistoryView {
self.additionalDatas[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState)
hasChanges = true
}
case let .peerGroupState(groupId, _):
if transaction.currentUpdatedPeerGroupStates.contains(groupId) {
self.additionalDatas[i] = .peerGroupState(groupId, postbox.peerGroupStateTable.get(groupId))
hasChanges = true
}
case .totalUnreadCount:
break
case .peerNotificationSettings:

View File

@ -0,0 +1,35 @@
import Foundation
final class MutablePeerGroupStateView: MutablePostboxView {
let groupId: PeerGroupId
var state: PeerGroupState?
init(postbox: Postbox, groupId: PeerGroupId) {
self.groupId = groupId
self.state = postbox.peerGroupStateTable.get(groupId)
}
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
if transaction.currentUpdatedPeerGroupStates.contains(self.groupId) {
self.state = postbox.peerGroupStateTable.get(self.groupId)
return true
} else {
return false
}
}
func immutableView() -> PostboxView {
return PeerGroupStateView(self)
}
}
public final class PeerGroupStateView: PostboxView {
public let groupId: PeerGroupId
public let state: PeerGroupState?
init(_ view: MutablePeerGroupStateView) {
self.groupId = view.groupId
self.state = view.state
}
}

View File

@ -62,6 +62,16 @@ public final class Modifier {
self.postbox?.addFeedHoleFromLatestEntries(groupId: groupId)
}
public func addMessagesToGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
assert(!self.disposed)
self.postbox?.addMessagesToGroupFeedIndex(groupId: groupId, ids: ids)
}
public func removeMessagesFromGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
assert(!self.disposed)
self.postbox?.removeMessagesFromGroupFeedIndex(groupId: groupId, ids: ids)
}
public func replaceChatListHole(groupId: PeerGroupId?, index: MessageIndex, hole: ChatListHole?) {
assert(!self.disposed)
self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole)
@ -187,6 +197,16 @@ public final class Modifier {
self.postbox?.setPeerChatState(id, state: state)
}
public func getPeerGroupState(_ id: PeerGroupId) -> PeerGroupState? {
assert(!self.disposed)
return self.postbox?.peerGroupStateTable.get(id)
}
public func setPeerGroupState(_ id: PeerGroupId, state: PeerGroupState) {
assert(!self.disposed)
self.postbox?.setPeerGroupState(id, state: state)
}
public func getPeerChatInterfaceState(_ id: PeerId) -> PeerChatInterfaceState? {
assert(!self.disposed)
return self.postbox?.peerChatInterfaceStateTable.get(id)
@ -835,7 +855,7 @@ public func openPostbox(basePath: String, globalMessageIdsNamespace: MessageId.N
#if DEBUG
//debugSaveState(basePath: basePath, name: "previous")
debugRestoreState(basePath: basePath, name: "previous")
//debugRestoreState(basePath: basePath, name: "previous")
#endif
loop: while true {
@ -919,6 +939,7 @@ public final class Postbox {
private var currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]] = [:]
private var currentItemCollectionInfosOperations: [ItemCollectionInfosOperation] = []
private var currentUpdatedPeerChatStates = Set<PeerId>()
private var currentUpdatedPeerGroupStates = Set<PeerGroupId>()
private var currentUpdatedAccessChallengeData: PostboxAccessChallengeData?
private var currentPendingMessageActionsOperations: [PendingMessageActionsOperation] = []
private var currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32] = [:]
@ -967,6 +988,7 @@ public final class Postbox {
let globalMessageHistoryTagsTable: GlobalMessageHistoryTagsTable
let localMessageHistoryTagsTable: LocalMessageHistoryTagsTable
let peerChatStateTable: PeerChatStateTable
let peerGroupStateTable: PeerGroupStateTable
let readStateTable: MessageHistoryReadStateTable
let synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable
let contactsTable: ContactTable
@ -1057,6 +1079,7 @@ public final class Postbox {
self.groupAssociationTable = PeerGroupAssociationTable(valueBox: self.valueBox, table: PeerGroupAssociationTable.tableSpec(49))
self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), messageHistoryIndexTable: self.messageHistoryIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, tagsTable: self.messageHistoryTagsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable, groupAssociationTable: self.groupAssociationTable, groupFeedIndexTable: self.groupFeedIndexTable)
self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13))
self.peerGroupStateTable = PeerGroupStateTable(valueBox: self.valueBox, table: PeerGroupStateTable.tableSpec(53))
self.peerNameTokenIndexTable = ReverseIndexReferenceTable<PeerIdReverseIndexReference>(valueBox: self.valueBox, table: ReverseIndexReferenceTable<PeerIdReverseIndexReference>.tableSpec(26))
self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable)
self.contactsTable = ContactTable(valueBox: self.valueBox, table: ContactTable.tableSpec(16), peerNameIndexTable: self.peerNameIndexTable)
@ -1103,6 +1126,7 @@ public final class Postbox {
tables.append(self.chatListTable)
tables.append(self.groupAssociationTable)
tables.append(self.peerChatStateTable)
tables.append(self.peerGroupStateTable)
tables.append(self.contactsTable)
tables.append(self.peerRatingTable)
tables.append(self.peerNotificationSettingsTable)
@ -1388,6 +1412,24 @@ public final class Postbox {
self.groupFeedIndexTable.addHoleFromLatestEntries(groupId: groupId, messageHistoryTable: self.messageHistoryTable, operations: &self.currentGroupFeedOperations)
}
fileprivate func addMessagesToGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
for id in ids {
if let entry = self.messageHistoryIndexTable.get(id), case let .Message(index) = entry {
if let message = self.messageHistoryTable.getMessage(index) {
self.groupFeedIndexTable.add(groupId: groupId, message: message, operations: &self.currentGroupFeedOperations)
}
}
}
}
fileprivate func removeMessagesFromGroupFeedIndex(groupId: PeerGroupId, ids: [MessageId]) {
for id in ids {
if let entry = self.messageHistoryIndexTable.get(id), case let .Message(index) = entry {
self.groupFeedIndexTable.remove(groupId: groupId, messageIndex: index, operations: &self.currentGroupFeedOperations)
}
}
}
fileprivate func replaceChatListHole(groupId: PeerGroupId?, index: MessageIndex, hole: ChatListHole?) {
self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations)
}
@ -1670,7 +1712,7 @@ public final class Postbox {
return self.peerTable.get(peerId)
}, updatedTotalUnreadCount: &self.currentUpdatedTotalUnreadCount)
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, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadCount: self.currentUpdatedTotalUnreadCount, peerIdsWithUpdatedUnreadCounts: Set(transactionUnreadCountDeltas.keys), peerIdsWithUpdatedCombinedReadStates: peerIdsWithUpdatedCombinedReadStates, 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, 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, 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, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadCount: self.currentUpdatedTotalUnreadCount, peerIdsWithUpdatedUnreadCounts: Set(transactionUnreadCountDeltas.keys), peerIdsWithUpdatedCombinedReadStates: peerIdsWithUpdatedCombinedReadStates, 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, currentUpdatedMasterClientId: currentUpdatedMasterClientId)
var updatedTransactionState: Int64?
var updatedMasterClientId: Int64?
if !transaction.isEmpty {
@ -1714,6 +1756,7 @@ public final class Postbox {
self.currentItemCollectionItemsOperations.removeAll()
self.currentItemCollectionInfosOperations.removeAll()
self.currentUpdatedPeerChatStates.removeAll()
self.currentUpdatedPeerGroupStates.removeAll()
self.currentUpdatedAccessChallengeData = nil
self.currentPendingMessageActionsOperations.removeAll()
self.currentUpdatedMessageActionsSummaries.removeAll()
@ -1870,6 +1913,11 @@ public final class Postbox {
self.currentUpdatedPeerChatStates.insert(id)
}
fileprivate func setPeerGroupState(_ id: PeerGroupId, state: PeerGroupState) {
self.peerGroupStateTable.set(id, state: state)
self.currentUpdatedPeerGroupStates.insert(id)
}
fileprivate func updatePeerChatInterfaceState(_ id: PeerId, update: (PeerChatInterfaceState?) -> (PeerChatInterfaceState?)) {
let updatedState = update(self.peerChatInterfaceStateTable.get(id))
let (_, updatedEmbeddedState) = self.peerChatInterfaceStateTable.set(id, state: updatedState)
@ -2254,6 +2302,8 @@ public final class Postbox {
additionalDataEntries.append(.cachedPeerDataMessages(peerId, messages))
case let .peerChatState(peerId):
additionalDataEntries.append(.peerChatState(peerId, self.peerChatStateTable.get(peerId) as? PeerChatState))
case let .peerGroupState(groupId):
additionalDataEntries.append(.peerGroupState(groupId, self.peerGroupStateTable.get(groupId)))
case .totalUnreadCount:
additionalDataEntries.append(.totalUnreadCount(self.messageHistoryMetadataTable.getChatListTotalUnreadCount()))
case let .peerNotificationSettings(peerId):

View File

@ -3,5 +3,6 @@ module sqlcipher {
header "sqlcipher/sqlite3ext.h"
header "sqlcipher/SQLite-Bridging.h"
header "sqlcipher/fts3_tokenizer.h"
header "../Crc32.h"
export *
}

View File

@ -24,6 +24,7 @@ final class PostboxTransaction {
let currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]]
let currentItemCollectionInfosOperations: [ItemCollectionInfosOperation]
let currentUpdatedPeerChatStates: Set<PeerId>
let currentUpdatedPeerGroupStates: Set<PeerGroupId>
let updatedAccessChallengeData: PostboxAccessChallengeData?
let currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation]
let currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation]
@ -130,6 +131,9 @@ final class PostboxTransaction {
if !currentUpdatedPeerChatStates.isEmpty {
return false
}
if !currentUpdatedPeerGroupStates.isEmpty {
return false
}
if self.updatedAccessChallengeData != nil {
return false
}
@ -163,7 +167,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]], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadCount: Int32?, peerIdsWithUpdatedUnreadCounts: Set<PeerId>, peerIdsWithUpdatedCombinedReadStates: Set<PeerId>, currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set<PeerId>, updatedAccessChallengeData: PostboxAccessChallengeData?, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, currentGroupFeedReadStateContext: GroupFeedReadStateUpdateContext, currentInitialPeerGroupIdsBeforeUpdate: [PeerId: WrappedPeerGroupId], 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]], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadCount: Int32?, peerIdsWithUpdatedUnreadCounts: Set<PeerId>, peerIdsWithUpdatedCombinedReadStates: Set<PeerId>, currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set<PeerId>, currentUpdatedPeerGroupStates: Set<PeerGroupId>, updatedAccessChallengeData: PostboxAccessChallengeData?, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, currentGroupFeedReadStateContext: GroupFeedReadStateUpdateContext, currentInitialPeerGroupIdsBeforeUpdate: [PeerId: WrappedPeerGroupId], currentUpdatedMasterClientId: Int64?) {
self.currentUpdatedState = currentUpdatedState
self.currentOperationsByPeerId = currentOperationsByPeerId
self.currentGroupFeedOperations = currentGroupFeedOperations
@ -189,6 +193,7 @@ final class PostboxTransaction {
self.currentItemCollectionItemsOperations = currentItemCollectionItemsOperations
self.currentItemCollectionInfosOperations = currentItemCollectionInfosOperations
self.currentUpdatedPeerChatStates = currentUpdatedPeerChatStates
self.currentUpdatedPeerGroupStates = currentUpdatedPeerGroupStates
self.updatedAccessChallengeData = updatedAccessChallengeData
self.currentGlobalTagsOperations = currentGlobalTagsOperations
self.currentLocalTagsOperations = currentLocalTagsOperations

View File

@ -5,6 +5,7 @@ public enum PostboxViewKey: Hashable {
case itemCollectionIds(namespaces: [ItemCollectionId.Namespace])
case itemCollectionInfo(id: ItemCollectionId)
case peerChatState(peerId: PeerId)
case peerGroupState(groupId: PeerGroupId)
case orderedItemList(id: Int32)
case accessChallengeData
case preferences(keys: Set<ValueBoxKey>)
@ -31,6 +32,8 @@ public enum PostboxViewKey: Hashable {
return 1
case let .peerChatState(peerId):
return peerId.hashValue
case let .peerGroupState(groupId):
return groupId.hashValue
case let .itemCollectionInfo(id):
return id.hashValue
case let .orderedItemList(id):
@ -96,6 +99,12 @@ public enum PostboxViewKey: Hashable {
} else {
return false
}
case let .peerGroupState(groupId):
if case .peerGroupState(groupId) = rhs {
return true
} else {
return false
}
case let .orderedItemList(id):
if case .orderedItemList(id) = rhs {
return true
@ -212,6 +221,8 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV
return MutableItemCollectionInfoView(postbox: postbox, id: id)
case let .peerChatState(peerId):
return MutablePeerChatStateView(postbox: postbox, peerId: peerId)
case let .peerGroupState(groupId):
return MutablePeerGroupStateView(postbox: postbox, groupId: groupId)
case let .orderedItemList(id):
return MutableOrderedItemListView(postbox: postbox, collectionId: id)
case .accessChallengeData: