From 7f5890f45994d9a97ad1c42c48bd26fb6f7eabd2 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 29 Jul 2015 18:02:56 +0300 Subject: [PATCH] no message --- Postbox.xcodeproj/project.pbxproj | 15 +- Postbox/Coding.swift | 129 +++++++ Postbox/DeferredString.swift | 124 +++++++ Postbox/Message.swift | 25 +- Postbox/MessageView.swift | 148 +++++--- Postbox/Peer.swift | 26 ++ Postbox/PeerView.swift | 97 +++++- Postbox/Postbox.swift | 544 ++++++++++++++++++++++++------ submodules/SQLite.swift | 2 +- 9 files changed, 940 insertions(+), 170 deletions(-) create mode 100644 Postbox/DeferredString.swift diff --git a/Postbox.xcodeproj/project.pbxproj b/Postbox.xcodeproj/project.pbxproj index 4533d9cee2..5b3a1ed5a8 100644 --- a/Postbox.xcodeproj/project.pbxproj +++ b/Postbox.xcodeproj/project.pbxproj @@ -22,7 +22,8 @@ D07516771B2EC90400AE42E0 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516741B2EC90400AE42E0 /* fts3_tokenizer.h */; }; D07516781B2EC90400AE42E0 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = D07516751B2EC90400AE42E0 /* SQLite-Bridging.h */; }; D07516791B2EC90400AE42E0 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = D07516761B2EC90400AE42E0 /* SQLite-Bridging.m */; }; - D07FC7D31B4A3D6B0010B3F7 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D07FC7D21B4A3D6B0010B3F7 /* SwiftSignalKit.framework */; }; + D0B76BE71B66639F0095CF45 /* DeferredString.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B76BE61B66639F0095CF45 /* DeferredString.swift */; }; + D0C07F6A1B67DB4800966E43 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */; }; D0D224F21B4D6ABD0085E26D /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D224ED1B4D6ABD0085E26D /* Functions.swift */; }; D0D225261B4D84930085E26D /* PeerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D225251B4D84930085E26D /* PeerView.swift */; }; D0E3A7501B28A7E300A402D9 /* Postbox.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E3A74F1B28A7E300A402D9 /* Postbox.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -62,7 +63,8 @@ D07516741B2EC90400AE42E0 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; D07516751B2EC90400AE42E0 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; D07516761B2EC90400AE42E0 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - D07FC7D21B4A3D6B0010B3F7 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKit.framework; path = "../SSignalKit/build/Debug-iphoneos/SwiftSignalKit.framework"; sourceTree = ""; }; + D0B76BE61B66639F0095CF45 /* DeferredString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeferredString.swift; sourceTree = ""; }; + D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/SwiftSignalKit.framework"; sourceTree = ""; }; D0D224ED1B4D6ABD0085E26D /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Functions.swift; path = submodules/sqlite.swift/SQLite/Functions.swift; sourceTree = SOURCE_ROOT; }; D0D225251B4D84930085E26D /* PeerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerView.swift; sourceTree = ""; }; D0E3A74A1B28A7E300A402D9 /* Postbox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Postbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -83,6 +85,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D0C07F6A1B67DB4800966E43 /* SwiftSignalKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -90,7 +93,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D07FC7D31B4A3D6B0010B3F7 /* SwiftSignalKit.framework in Frameworks */, D0E3A7561B28A7E300A402D9 /* Postbox.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -142,7 +144,7 @@ D0E3A7401B28A7E300A402D9 = { isa = PBXGroup; children = ( - D07FC7D21B4A3D6B0010B3F7 /* SwiftSignalKit.framework */, + D0C07F691B67DB4800966E43 /* SwiftSignalKit.framework */, D07516491B2D9E2500AE42E0 /* Postbox.xcconfig */, D0E3A74C1B28A7E300A402D9 /* Postbox */, D0E3A7591B28A7E300A402D9 /* PostboxTests */, @@ -166,6 +168,7 @@ D075163D1B2D9CEF00AE42E0 /* PostboxPrivate */, D07515FC1B2C44A200AE42E0 /* thirdparty */, D0E3A7871B28AE9C00A402D9 /* Coding.swift */, + D0B76BE61B66639F0095CF45 /* DeferredString.swift */, D0E3A7831B28AE0900A402D9 /* Peer.swift */, D0E3A79D1B28B50400A402D9 /* Message.swift */, D0E3A7A11B28B7DC00A402D9 /* Media.swift */, @@ -334,6 +337,7 @@ D07516711B2EC7FE00AE42E0 /* Statement.swift in Sources */, D0D225261B4D84930085E26D /* PeerView.swift in Sources */, D0E3A7841B28AE0900A402D9 /* Peer.swift in Sources */, + D0B76BE71B66639F0095CF45 /* DeferredString.swift in Sources */, D075166A1B2EC7FE00AE42E0 /* Database.swift in Sources */, D07516441B2D9CEF00AE42E0 /* sqlite3.c in Sources */, ); @@ -459,6 +463,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "/Users/peter/Documents/PostBoxTest/submodules/SSignalKit/build/Debug-iphoneos", @@ -487,6 +492,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "/Users/peter/Documents/PostBoxTest/submodules/SSignalKit/build/Debug-iphoneos", @@ -503,6 +509,7 @@ ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; diff --git a/Postbox/Coding.swift b/Postbox/Coding.swift index 51f53915b0..864a6449fa 100644 --- a/Postbox/Coding.swift +++ b/Postbox/Coding.swift @@ -136,6 +136,13 @@ public final class Encoder { self.buffer.write(UnsafePointer(key), offset: 0, length: Int(length)) } + public func encodeKey(key: Int8) { + var length: Int8 = 1 + self.buffer.write(&length, offset: 0, length: 1) + var keyValue = key + self.buffer.write(&keyValue, offset: 0, length: Int(length)) + } + public func encodeInt32(value: Int32, forKey key: UnsafePointer) { self.encodeKey(key) var type: Int8 = ValueType.Int32.rawValue @@ -144,6 +151,14 @@ public final class Encoder { self.buffer.write(&v, offset: 0, length: 4) } + public func encodeInt32(value: Int32, forKey key: Int8) { + self.encodeKey(key) + var type: Int8 = ValueType.Int32.rawValue + self.buffer.write(&type, offset: 0, length: 1) + var v = value + self.buffer.write(&v, offset: 0, length: 4) + } + public func encodeInt64(value: Int64, forKey key: UnsafePointer) { self.encodeKey(key) var type: Int8 = ValueType.Int64.rawValue @@ -152,6 +167,14 @@ public final class Encoder { self.buffer.write(&v, offset: 0, length: 8) } + public func encodeInt64(value: Int64, forKey key: Int8) { + self.encodeKey(key) + var type: Int8 = ValueType.Int64.rawValue + self.buffer.write(&type, offset: 0, length: 1) + var v = value + self.buffer.write(&v, offset: 0, length: 8) + } + public func encodeBool(value: Bool, forKey key: UnsafePointer) { self.encodeKey(key) var type: Int8 = ValueType.Bool.rawValue @@ -160,6 +183,14 @@ public final class Encoder { self.buffer.write(&v, offset: 0, length: 1) } + public func encodeBool(value: Bool, forKey key: Int8) { + self.encodeKey(key) + var type: Int8 = ValueType.Bool.rawValue + self.buffer.write(&type, offset: 0, length: 1) + var v: Int8 = value ? 1 : 0 + self.buffer.write(&v, offset: 0, length: 1) + } + public func encodeDouble(value: Double, forKey key: UnsafePointer) { self.encodeKey(key) var type: Int8 = ValueType.Double.rawValue @@ -168,6 +199,14 @@ public final class Encoder { self.buffer.write(&v, offset: 0, length: 8) } + public func encodeDouble(value: Double, forKey key: Int8) { + self.encodeKey(key) + var type: Int8 = ValueType.Double.rawValue + self.buffer.write(&type, offset: 0, length: 1) + var v = value + self.buffer.write(&v, offset: 0, length: 8) + } + public func encodeString(value: String, forKey key: UnsafePointer) { self.encodeKey(key) var type: Int8 = ValueType.String.rawValue @@ -178,6 +217,26 @@ public final class Encoder { self.buffer.write(data.bytes, offset: 0, length: Int(length)) } + public func encodeString(value: DeferredString, forKey key: UnsafePointer) { + self.encodeKey(key) + var type: Int8 = ValueType.String.rawValue + self.buffer.write(&type, offset: 0, length: 1) + let data = value.data + var length: Int32 = Int32(data.length) + self.buffer.write(&length, offset: 0, length: 4) + self.buffer.write(data.bytes, offset: 0, length: Int(length)) + } + + public func encodeString(value: String, forKey key: Int8) { + self.encodeKey(key) + var type: Int8 = ValueType.String.rawValue + self.buffer.write(&type, offset: 0, length: 1) + let data = value.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)! + var length: Int32 = Int32(data.length) + self.buffer.write(&length, offset: 0, length: 4) + self.buffer.write(data.bytes, offset: 0, length: Int(length)) + } + public func encodeRootObject(value: Coding) { self.encodeObject(value, forKey: "_") } @@ -278,6 +337,8 @@ public final class Encoder { self.buffer.write(&bytesLength, offset: 0, length: 4) self.buffer.write(bytes.memory, offset: 0, length: bytes.offset) } + + public let sharedWriteBuffer = WriteBuffer() } public final class Decoder { @@ -376,6 +437,37 @@ public final class Decoder { return false } + private class func positionOnKey(bytes: UnsafePointer, inout offset: Int, maxOffset: Int, length: Int, key: Int16, valueType: ValueType) -> Bool + { + var keyValue = key + let startOffset = offset + + let keyLength: Int = 2 + while (offset < maxOffset) + { + let readKeyLength = bytes[offset] + offset += 1 + offset += Int(readKeyLength) + + let readValueType = bytes[offset] + offset += 1 + + if readValueType != valueType.rawValue || keyLength != Int(readKeyLength) || memcmp(bytes + (offset - Int(readKeyLength) - 1), &keyValue, keyLength) != 0 { + skipValue(bytes, offset: &offset, length: length, valueType: ValueType(rawValue: readValueType)!) + } else { + return true + } + } + + if (startOffset != 0) + { + offset = 0 + return positionOnKey(bytes, offset: &offset, maxOffset: startOffset, length: length, key: key, valueType: valueType) + } + + return false + } + public func decodeInt32ForKey(key: UnsafePointer) -> Int32 { if Decoder.positionOnKey(UnsafePointer(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .Int32) { var value: Int32 = 0 @@ -433,6 +525,43 @@ public final class Decoder { } } + public func decodeStringForKey(key: UnsafePointer) -> String? { + if Decoder.positionOnKey(UnsafePointer(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) { + var length: Int32 = 0 + memcpy(&length, self.buffer.memory + self.buffer.offset, 4) + let data = NSData(bytes: self.buffer.memory + (self.buffer.offset + 4), length: Int(length)) + self.buffer.offset += 4 + Int(length) + let value = NSString(data: data, encoding: NSUTF8StringEncoding) + return value as? String + } else { + return nil + } + } + + public func decodeStringForKey(key: UnsafePointer) -> DeferredString { + if Decoder.positionOnKey(UnsafePointer(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) { + var length: Int32 = 0 + memcpy(&length, self.buffer.memory + self.buffer.offset, 4) + let data = NSData(bytes: self.buffer.memory + (self.buffer.offset + 4), length: Int(length)) + self.buffer.offset += 4 + Int(length) + return DeferredStringValue(data) + } else { + return DeferredStringValue("") + } + } + + public func decodeStringForKey(key: UnsafePointer) -> DeferredString? { + if Decoder.positionOnKey(UnsafePointer(self.buffer.memory), offset: &self.buffer.offset, maxOffset: self.buffer.length, length: self.buffer.length, key: key, valueType: .String) { + var length: Int32 = 0 + memcpy(&length, self.buffer.memory + self.buffer.offset, 4) + let data = NSData(bytes: self.buffer.memory + (self.buffer.offset + 4), length: Int(length)) + self.buffer.offset += 4 + Int(length) + return DeferredStringValue(data) + } else { + return nil + } + } + public func decodeRootObject() -> Coding? { return self.decodeObjectForKey("_") } diff --git a/Postbox/DeferredString.swift b/Postbox/DeferredString.swift new file mode 100644 index 0000000000..adce77284f --- /dev/null +++ b/Postbox/DeferredString.swift @@ -0,0 +1,124 @@ +import Foundation + +public protocol DeferredString { + var data: NSData { get } + var string: String { get } +} + +private final class DeferredStringImpl { + var string: String! + var data: NSData! + var lock: OSSpinLock = 0 + + init(string: String!, data: NSData!) { + self.string = string + self.data = data + } +} + +public final class DeferredStringValue: DeferredString, CustomStringConvertible, StringLiteralConvertible { + private var impl: DeferredStringImpl + + public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType + public typealias UnicodeScalarLiteralType = StringLiteralType + + public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { + self.impl = DeferredStringImpl(string: "\(value)", data: nil) + } + + public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) { + self.impl = DeferredStringImpl(string: value, data: nil) + } + + public init(stringLiteral value: StringLiteralType) { + self.impl = DeferredStringImpl(string: value, data: nil) + } + + public init(_ string: String) { + self.impl = DeferredStringImpl(string: string, data: nil) + } + + public init(_ data: NSData) { + self.impl = DeferredStringImpl(string: nil, data: data) + } + + public var description: String { + return self.string + } + + public var data: NSData { + if isUniquelyReferencedNonObjC(&self.impl) { + let value: NSData + + if impl.data != nil { + value = impl.data + } else if impl.string != nil { + value = impl.string.dataUsingEncoding(NSUTF8StringEncoding) ?? NSData() + impl.data = value + } else { + value = NSData() + } + + return value + } else { + let value: NSData + + OSSpinLockLock(&impl.lock) + if impl.data != nil { + value = impl.data + } else if impl.string != nil { + value = impl.string.dataUsingEncoding(NSUTF8StringEncoding) ?? NSData() + impl.data = value + } else { + value = NSData() + } + OSSpinLockUnlock(&impl.lock) + + return value + } + } + + public var string: String { + if isUniquelyReferencedNonObjC(&self.impl) { + let value: String + + if impl.string != nil { + value = impl.string + } else if impl.data != nil { + let fromData = NSString(data: impl.data, encoding: NSUTF8StringEncoding) + if fromData == nil { + value = "" + impl.string = value + } else { + value = fromData as! String + impl.string = value + } + } else { + value = "" + } + + return value + } else { + let value: String + + OSSpinLockLock(&impl.lock) + if impl.string != nil { + value = impl.string + } else if impl.data != nil { + let fromData = NSString(data: impl.data, encoding: NSUTF8StringEncoding) + if fromData == nil { + value = "" + impl.string = value + } else { + value = fromData as! String + impl.string = value + } + } else { + value = "" + } + OSSpinLockUnlock(&impl.lock) + + return value + } + } +} diff --git a/Postbox/Message.swift b/Postbox/Message.swift index 6db0eda143..a2b25de31e 100644 --- a/Postbox/Message.swift +++ b/Postbox/Message.swift @@ -110,5 +110,28 @@ public protocol Message: Coding { var id: MessageId { get } var timestamp: Int32 { get } var text: String { get } - var referencedMediaIds: [MediaId] { get } + var mediaIds: [MediaId] { get } + var peerIds: [PeerId] { get } +} + +public struct RenderedMessage { + public let message: Message + + internal let incomplete: Bool + public let peers: [Peer] + public let media: [Media] + + internal init(message: Message) { + self.message = message + self.peers = [] + self.media = [] + self.incomplete = true + } + + internal init(message: Message, peers: [Peer], media: [Media]) { + self.message = message + self.peers = peers + self.media = media + self.incomplete = false + } } diff --git a/Postbox/MessageView.swift b/Postbox/MessageView.swift index a7b4eda4d9..3050f7cf2a 100644 --- a/Postbox/MessageView.swift +++ b/Postbox/MessageView.swift @@ -19,11 +19,11 @@ public final class MutableMessageView: CustomStringConvertible { let namespaces: [MessageId.Namespace] let count: Int - var earlier: [MessageId.Namespace : Message] = [:] - var later: [MessageId.Namespace : Message] = [:] - var messages: [Message] + var earlier: [MessageId.Namespace : RenderedMessage] = [:] + var later: [MessageId.Namespace : RenderedMessage] = [:] + var messages: [RenderedMessage] - public init(namespaces: [MessageId.Namespace], count: Int, earlier: [MessageId.Namespace : Message], messages: [Message], later: [MessageId.Namespace : Message]) { + public init(namespaces: [MessageId.Namespace], count: Int, earlier: [MessageId.Namespace : RenderedMessage], messages: [RenderedMessage], later: [MessageId.Namespace : RenderedMessage]) { self.namespaces = namespaces self.count = count self.earlier = earlier @@ -31,56 +31,60 @@ public final class MutableMessageView: CustomStringConvertible { self.messages = messages } - public func add(message: Message) { + public func add(message: RenderedMessage) -> Bool { if self.messages.count == 0 { self.messages.append(message) + return true } else { - let first = MessageIndex(self.messages[self.messages.count - 1]) - let last = MessageIndex(self.messages[0]) + let first = MessageIndex(self.messages[self.messages.count - 1].message) + let last = MessageIndex(self.messages[0].message) var next: MessageIndex? for namespace in self.namespaces { if let message = later[namespace] { - let messageIndex = MessageIndex(message) + let messageIndex = MessageIndex(message.message) if next == nil || messageIndex < next! { next = messageIndex } } } - let index = MessageIndex(message) + let index = MessageIndex(message.message) if index < last { - let earlierMessage = self.earlier[message.id.namespace] - if earlierMessage == nil || earlierMessage!.id.id < message.id.id { + let earlierMessage = self.earlier[message.message.id.namespace] + if earlierMessage == nil || earlierMessage!.message.id.id < message.message.id.id { if self.messages.count < self.count { self.messages.insert(message, atIndex: 0) } else { - self.earlier[message.id.namespace] = message + self.earlier[message.message.id.namespace] = message } } + + return true } else if index > first { if next != nil && index > next! { - let laterMessage = self.later[message.id.namespace] - if laterMessage == nil || laterMessage!.id.id > message.id.id { + let laterMessage = self.later[message.message.id.namespace] + if laterMessage == nil || laterMessage!.message.id.id > message.message.id.id { if self.messages.count < self.count { self.messages.append(message) } else { - self.later[message.id.namespace] = message + self.later[message.message.id.namespace] = message } } } else { self.messages.append(message) if self.messages.count > self.count { let earliest = self.messages[0] - self.earlier[earliest.id.namespace] = earliest + self.earlier[earliest.message.id.namespace] = earliest self.messages.removeAtIndex(0) } } + return true } else if index != last && index != first { var i = self.messages.count while i >= 1 { - if MessageIndex(self.messages[i - 1]) < index { + if MessageIndex(self.messages[i - 1].message) < index { break } i-- @@ -88,9 +92,12 @@ public final class MutableMessageView: CustomStringConvertible { self.messages.insert(message, atIndex: i) if self.messages.count > self.count { let earliest = self.messages[0] - self.earlier[earliest.id.namespace] = earliest + self.earlier[earliest.message.id.namespace] = earliest self.messages.removeAtIndex(0) } + return true + } else { + return false } } } @@ -102,21 +109,21 @@ public final class MutableMessageView: CustomStringConvertible { } for (_, message) in self.earlier { - if ids.contains(message.id) { - updatedContext.invalidEarlier.insert(message.id.namespace) + if ids.contains(message.message.id) { + updatedContext.invalidEarlier.insert(message.message.id.namespace) } } for (_, message) in self.later { - if ids.contains(message.id) { - updatedContext.invalidLater.insert(message.id.namespace) + if ids.contains(message.message.id) { + updatedContext.invalidLater.insert(message.message.id.namespace) } } if self.messages.count != 0 { var i = self.messages.count - 1 while i >= 0 { - if ids.contains(self.messages[i].id) { + if ids.contains(self.messages[i].message.id) { self.messages.removeAtIndex(i) updatedContext.removedMessages = true } @@ -127,19 +134,18 @@ public final class MutableMessageView: CustomStringConvertible { return updatedContext } - public func complete(context: RemoveContext, fetchEarlier: (MessageId.Namespace, MessageId.Id?, Int) -> [Message], fetchLater: (MessageId.Namespace, MessageId.Id?, Int) -> [Message]) { + public func complete(context: RemoveContext, fetchEarlier: (MessageId.Namespace, MessageId.Id?, Int) -> [RenderedMessage], fetchLater: (MessageId.Namespace, MessageId.Id?, Int) -> [RenderedMessage]) { if context.removedMessages { - var addedMessages: [Message] = [] + var addedMessages: [RenderedMessage] = [] var latestAnchor: MessageIndex? if let lastMessage = self.messages.last { - latestAnchor = MessageIndex(lastMessage) + latestAnchor = MessageIndex(lastMessage.message) } if latestAnchor == nil { - var laterMessages: [Message] = [] for (_, message) in self.later { - let messageIndex = MessageIndex(message) + let messageIndex = MessageIndex(message.message) if latestAnchor == nil || latestAnchor! > messageIndex { latestAnchor = messageIndex } @@ -148,18 +154,18 @@ public final class MutableMessageView: CustomStringConvertible { for namespace in self.namespaces { if let later = self.later[namespace] { - addedMessages += fetchLater(namespace, later.id.id - 1, self.count) + addedMessages += fetchLater(namespace, later.message.id.id - 1, self.count) } if let earlier = self.earlier[namespace] { - addedMessages += fetchEarlier(namespace, earlier.id.id + 1, self.count) + addedMessages += fetchEarlier(namespace, earlier.message.id.id + 1, self.count) } } addedMessages += self.messages - addedMessages.sortInPlace({ MessageIndex($0) < MessageIndex($1) }) + addedMessages.sortInPlace({ MessageIndex($0.message) < MessageIndex($1.message) }) var i = addedMessages.count - 1 while i >= 1 { - if addedMessages[i].id == addedMessages[i - 1].id { + if addedMessages[i].message.id == addedMessages[i - 1].message.id { addedMessages.removeAtIndex(i) } i-- @@ -170,7 +176,7 @@ public final class MutableMessageView: CustomStringConvertible { if let latestAnchor = latestAnchor { var i = addedMessages.count - 1 while i >= 0 { - if MessageIndex(addedMessages[i]) <= latestAnchor { + if MessageIndex(addedMessages[i].message) <= latestAnchor { anchorIndex = i break } @@ -183,7 +189,7 @@ public final class MutableMessageView: CustomStringConvertible { for namespace in self.namespaces { var i = anchorIndex + 1 while i < addedMessages.count { - if addedMessages[i].id.namespace == namespace { + if addedMessages[i].message.id.namespace == namespace { self.later[namespace] = addedMessages[i] break } @@ -203,7 +209,7 @@ public final class MutableMessageView: CustomStringConvertible { for namespace in self.namespaces { i = anchorIndex - self.count while i >= 0 { - if addedMessages[i].id.namespace == namespace { + if addedMessages[i].message.id.namespace == namespace { self.earlier[namespace] = addedMessages[i] break } @@ -217,8 +223,8 @@ public final class MutableMessageView: CustomStringConvertible { var earlyId: MessageId.Id? var i = 0 while i < self.messages.count { - if self.messages[i].id.namespace == namespace { - earlyId = self.messages[i].id.id + if self.messages[i].message.id.namespace == namespace { + earlyId = self.messages[i].message.id.id break } i++ @@ -236,8 +242,8 @@ public final class MutableMessageView: CustomStringConvertible { var lateId: MessageId.Id? var i = self.messages.count - 1 while i >= 0 { - if self.messages[i].id.namespace == namespace { - lateId = self.messages[i].id.id + if self.messages[i].message.id.namespace == namespace { + lateId = self.messages[i].message.id.id break } i-- @@ -253,6 +259,56 @@ public final class MutableMessageView: CustomStringConvertible { } } + + public func incompleteMessages() -> [Message] { + var result: [Message] = [] + + for (_, message) in self.earlier { + if message.incomplete { + result.append(message.message) + } + } + for (_, message) in self.later { + if message.incomplete { + result.append(message.message) + } + } + + for message in self.messages { + if message.incomplete { + result.append(message.message) + } + } + + return result + } + + public func completeMessages(messages: [MessageId : RenderedMessage]) { + var earlier = self.earlier + for (namespace, message) in self.earlier { + if let message = messages[message.message.id] { + earlier[namespace] = message + } + } + self.earlier = earlier + + var later = self.later + for (namespace, message) in self.later { + if let message = messages[message.message.id] { + later[namespace] = message + } + } + self.later = later + + var i = 0 + while i < self.messages.count { + if let message = messages[self.messages[i].message.id] { + self.messages[i] = message + } + i++ + } + } + public var description: String { var string = "" string += "...(" @@ -264,7 +320,7 @@ public final class MutableMessageView: CustomStringConvertible { } else { string += ", " } - string += "\(namespace): \(value.id.id)—\(value.timestamp)" + string += "\(namespace): \(value.message.id.id)—\(value.message.timestamp)" } } string += ") —— " @@ -277,7 +333,7 @@ public final class MutableMessageView: CustomStringConvertible { } else { string += ", " } - string += "\(message.id.namespace): \(message.id.id)—\(message.timestamp)" + string += "\(message.message.id.namespace): \(message.message.id.id)—\(message.message.timestamp)" } string += "]" @@ -290,7 +346,7 @@ public final class MutableMessageView: CustomStringConvertible { } else { string += ", " } - string += "\(namespace): \(value.id.id)—\(value.timestamp)" + string += "\(namespace): \(value.message.id.id)—\(value.message.timestamp)" } } string += ")..." @@ -304,7 +360,7 @@ public final class MessageView: CustomStringConvertible { private let earlierIds: [MessageIndex] public let hasLater: Bool private let laterIds: [MessageIndex] - public let messages: [Message] + public let messages: [RenderedMessage] init(_ mutableView: MutableMessageView) { self.hasEarlier = mutableView.earlier.count != 0 @@ -313,13 +369,13 @@ public final class MessageView: CustomStringConvertible { var earlierIds: [MessageIndex] = [] for (_, message) in mutableView.earlier { - earlierIds.append(MessageIndex(message)) + earlierIds.append(MessageIndex(message.message)) } self.earlierIds = earlierIds var laterIds: [MessageIndex] = [] for (_, message) in mutableView.later { - laterIds.append(MessageIndex(message)) + laterIds.append(MessageIndex(message.message)) } self.laterIds = laterIds } @@ -347,7 +403,7 @@ public final class MessageView: CustomStringConvertible { } else { string += ", " } - string += "\(message.id.namespace): \(message.id.id)—\(message.timestamp)" + string += "\(message.message.id.namespace): \(message.message.id.id)—\(message.message.timestamp)" } string += "]" if self.hasLater { diff --git a/Postbox/Peer.swift b/Postbox/Peer.swift index 190b7bf41a..03ca43228a 100644 --- a/Postbox/Peer.swift +++ b/Postbox/Peer.swift @@ -21,6 +21,30 @@ public struct PeerId: Hashable, CustomStringConvertible, Comparable { return (Int64(self.namespace) << 32) | Int64(self.id) } + public static func encodeArrayToBuffer(array: [PeerId], buffer: WriteBuffer) { + var length: Int32 = Int32(array.count) + buffer.write(&length, offset: 0, length: 4) + for id in array { + var value = id.toInt64() + buffer.write(&value, offset: 0, length: 8) + } + } + + public static func decodeArrayFromBuffer(buffer: ReadBuffer) -> [PeerId] { + var length: Int32 = 0 + memcpy(&length, buffer.memory, 4) + buffer.offset += 4 + var i = 0 + var array: [PeerId] = [] + while i < Int(length) { + var value: Int64 = 0 + buffer.read(&value, offset: 0, length: 8) + array.append(PeerId(value)) + i++ + } + return array + } + public var hashValue: Int { get { return Int(self.id) @@ -67,4 +91,6 @@ public func <(lhs: PeerId, rhs: PeerId) -> Bool { public protocol Peer: Coding { var id: PeerId { get } + + func equalsTo(other: Peer) -> Bool } diff --git a/Postbox/PeerView.swift b/Postbox/PeerView.swift index 5f77d90ded..82085302e8 100644 --- a/Postbox/PeerView.swift +++ b/Postbox/PeerView.swift @@ -1,21 +1,27 @@ import Foundation -public class PeerViewEntry { +public final class PeerViewEntry { public let peerId: PeerId public let peer: Peer? - public let message: Message + public let message: RenderedMessage - public init(peer: Peer, message: Message) { + public init(peer: Peer, message: RenderedMessage) { self.peerId = peer.id self.peer = peer self.message = message } - public init(peerId: PeerId, message: Message) { + public init(peerId: PeerId, message: RenderedMessage) { self.peerId = peerId self.peer = nil self.message = message } + + private init(peer: Peer?, peerId: PeerId, message: RenderedMessage) { + self.peer = peer + self.peerId = peerId + self.message = message + } } public struct PeerViewEntryIndex: Equatable, Comparable { @@ -24,7 +30,7 @@ public struct PeerViewEntryIndex: Equatable, Comparable { public init(_ entry: PeerViewEntry) { self.peerId = entry.peerId - self.messageIndex = MessageIndex(entry.message) + self.messageIndex = MessageIndex(entry.message.message) } public init(peerId: PeerId, messageIndex: MessageIndex) { @@ -252,12 +258,85 @@ public final class MutablePeerView: CustomStringConvertible { } } + public func updatePeers(peers: [PeerId : Peer]) -> Bool { + var updated = false + + if let earlier = self.earlier { + if let peer = peers[earlier.peerId] { + self.earlier = PeerViewEntry(peer: peer, message: earlier.message) + updated = true + } + } + + if let later = self.later { + if let peer = peers[later.peerId] { + self.later = PeerViewEntry(peer: peer, message: later.message) + updated = true + } + } + + var i = 0 + while i < self.entries.count { + if let peer = peers[self.entries[i].peerId] { + self.entries[i] = PeerViewEntry(peer: peer, message: self.entries[i].message) + updated = true + } + i++ + } + + return updated + } + + public func incompleteMessages() -> [Message] { + var result: [Message] = [] + + if let earlier = self.earlier { + if earlier.message.incomplete { + result.append(earlier.message.message) + } + } + if let later = self.later { + if later.message.incomplete { + result.append(later.message.message) + } + } + + for entry in self.entries { + if entry.message.incomplete { + result.append(entry.message.message) + } + } + + return result + } + + public func completeMessages(messages: [MessageId : RenderedMessage]) { + if let earlier = self.earlier { + if let message = messages[earlier.message.message.id] { + self.earlier = PeerViewEntry(peer: earlier.peer, peerId: earlier.peerId, message: message) + } + } + if let later = self.later { + if let message = messages[later.message.message.id] { + self.later = PeerViewEntry(peer: later.peer, peerId: later.peerId, message: message) + } + } + + var i = 0 + while i < self.entries.count { + if let message = messages[self.entries[i].message.message.id] { + self.entries[i] = PeerViewEntry(peer: self.entries[i].peer, peerId: self.entries[i].peerId, message: message) + } + i++ + } + } + public var description: String { var string = "" if let earlier = self.earlier { string += "more(" - string += "(p \(earlier.peerId.namespace):\(earlier.peerId.id), m \(earlier.message.id.namespace):\(earlier.message.id.id)—\(earlier.message.timestamp)" + string += "(p \(earlier.peerId.namespace):\(earlier.peerId.id), m \(earlier.message.message.id.namespace):\(earlier.message.message.id.id)—\(earlier.message.message.timestamp)" string += ") " } @@ -269,13 +348,13 @@ public final class MutablePeerView: CustomStringConvertible { } else { string += ", " } - string += "(p \(entry.peerId.namespace):\(entry.peerId.id), m \(entry.message.id.namespace):\(entry.message.id.id)—\(entry.message.timestamp))" + string += "(p \(entry.peerId.namespace):\(entry.peerId.id), m \(entry.message.message.id.namespace):\(entry.message.message.id.id)—\(entry.message.message.timestamp))" } string += "]" if let later = self.later { string += " more(" - string += "(p \(later.peerId.namespace):\(later.peerId), m \(later.message.id.namespace):\(later.message.id.id)—\(later.message.timestamp)" + string += "(p \(later.peerId.namespace):\(later.peerId), m \(later.message.message.id.namespace):\(later.message.message.id.id)—\(later.message.message.timestamp)" string += ")" } @@ -321,7 +400,7 @@ public final class PeerView: CustomStringConvertible { } else { string += ", " } - string += "(p \(entry.peerId.namespace):\(entry.peerId.id), m \(entry.message.id.namespace):\(entry.message.id.id)—\(entry.message.timestamp))" + string += "(p \(entry.peerId.namespace):\(entry.peerId.id), m \(entry.message.message.id.namespace):\(entry.message.message.id.id)—\(entry.message.message.timestamp))" } string += "]" diff --git a/Postbox/Postbox.swift b/Postbox/Postbox.swift index 5c8e08ec7f..5b0a1c8875 100644 --- a/Postbox/Postbox.swift +++ b/Postbox/Postbox.swift @@ -6,7 +6,7 @@ public protocol PostboxState: Coding { } -public class Modifier { +public final class Modifier { private weak var postbox: Postbox? private init(postbox: Postbox) { @@ -28,6 +28,10 @@ public class Modifier { public func setState(state: State) { self.postbox?.setState(state) } + + public func updatePeers(peers: [Peer], update: (Peer, Peer) -> Peer) { + self.postbox?.updatePeers(peers, update: update) + } } public final class Postbox { @@ -38,7 +42,10 @@ public final class Postbox { private var database: Database! private var peerMessageViews: [PeerId : Bag<(MutableMessageView, Pipe)>] = [:] + private var deferredMessageViewsToUpdate: [(MutableMessageView, Pipe)] = [] private var peerViews: Bag<(MutablePeerView, Pipe)> = Bag() + private var deferredPeerViewsToUpdate: [(MutablePeerView, Pipe)] = [] + private var statePipe: Pipe = Pipe() public init(basePath: String, messageNamespaces: [MessageId.Namespace]) { @@ -48,21 +55,45 @@ public final class Postbox { } private func openDatabase() { - do { - try NSFileManager.defaultManager().createDirectoryAtPath(basePath, withIntermediateDirectories: true, attributes: nil) - } catch _ { - } - self.database = Database(basePath.stringByAppendingPathComponent("db")) - self.queue.dispatch { + let startTime = CFAbsoluteTimeGetCurrent() + + do { + try NSFileManager.defaultManager().createDirectoryAtPath(self.basePath, withIntermediateDirectories: true, attributes: nil) + } catch _ { + } + self.database = Database(self.basePath.stringByAppendingPathComponent("db")) + let result = self.database.scalar("PRAGMA user_version") as! Int64 - if result == 1 { + let version: Int64 = 7 + if result == version { print("(Postbox schema version \(result))") } else { + if result != 0 { + print("(Postbox migrating to version \(version))") + do { + try NSFileManager.defaultManager().removeItemAtPath(self.basePath) + try NSFileManager.defaultManager().createDirectoryAtPath(self.basePath, withIntermediateDirectories: true, attributes: nil) + } catch (_) { + } + + self.database = Database(self.basePath.stringByAppendingPathComponent("db")) + } print("(Postbox creating schema)") self.createSchema() - self.database.execute("PRAGMA user_version = 1") + self.database.execute("PRAGMA user_version = \(version)") } + + self.database.adjustChunkSize() + self.database.execute("PRAGMA page_size=1024") + self.database.execute("PRAGMA cache_size=-2097152") + self.database.execute("PRAGMA synchronous=NORMAL") + self.database.execute("PRAGMA journal_mode=truncate") + self.database.execute("PRAGMA temp_store=MEMORY") + //self.database.execute("PRAGMA wal_autocheckpoint=32") + //self.database.execute("PRAGMA journal_size_limit=1536") + + print("(Postbox initialization took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") } } @@ -92,7 +123,7 @@ public final class Postbox { self.database.execute("CREATE INDEX peer_entries_entry on peer_entries (entry)") //peers - self.database.execute("CREATE TABLE peers (peerId INTEGER PRIMARY KEY, data BLOB)") + self.database.execute("CREATE TABLE peers (id INTEGER PRIMARY KEY, data BLOB)") } private class func peerViewEntryIndexForBlob(blob: Blob) -> PeerViewEntryIndex { @@ -197,14 +228,23 @@ public final class Postbox { return grouped } - private class func messagesGroupedByPeerId(messages: [Message]) -> [PeerId : [Message]] { - var grouped: [PeerId : [Message]] = [:] + private class func messagesGroupedByPeerId(messages: [Message]) -> [(PeerId, [Message])] { + var grouped: [(PeerId, [Message])] = [] for message in messages { - if grouped[message.id.peerId] != nil { - grouped[message.id.peerId]!.append(message) - } else { - grouped[message.id.peerId] = [message] + var i = 0 + let count = grouped.count + var found = false + while i < count { + if grouped[i].0 == message.id.peerId { + grouped[i].1.append(message) + found = true + break + } + i++ + } + if !found { + grouped.append((message.id.peerId, [message])) } } @@ -325,8 +365,12 @@ public final class Postbox { return ids } + private var cachedState: State? + private func setState(state: State) { self.queue.dispatch { + self.cachedState = state + let encoder = Encoder() encoder.encodeRootObject(state) let blob = Blob(data: encoder.makeData()) @@ -337,16 +381,21 @@ public final class Postbox { } private func getState() -> State? { - for row in self.database.prepareCached("SELECT data FROM state WHERE id = ?").run(Int64(0)) { - let data = (row[0] as! Blob).data - let buffer = ReadBuffer(memory: UnsafeMutablePointer(data.bytes), length: data.length, freeWhenDone: false) - let decoder = Decoder(buffer: buffer) - if let state = decoder.decodeRootObject() as? State { - return state + if let cachedState = self.cachedState { + return cachedState + } else { + for row in self.database.prepareCached("SELECT data FROM state WHERE id = ?").run(Int64(0)) { + let data = (row[0] as! Blob).data + let buffer = ReadBuffer(memory: UnsafeMutablePointer(data.bytes), length: data.length, freeWhenDone: false) + let decoder = Decoder(buffer: buffer) + if let state = decoder.decodeRootObject() as? State { + self.cachedState = state + return state + } + break } - break + return nil } - return nil } public func state() -> Signal { @@ -394,11 +443,11 @@ public final class Postbox { } private func addMessages(messages: [Message], medias: [Media]) { - let messageInsertStatement = self.database.prepare("INSERT INTO peer_messages (peerId, namespace, id, data, associatedMediaIds, timestamp) VALUES (?, ?, ?, ?, ?, ?)") - let peerMediaInsertStatement = self.database.prepare("INSERT INTO peer_media (peerId, mediaNamespace, messageNamespace, messageId) VALUES (?, ?, ?, ?)") - let mediaInsertStatement = self.database.prepare("INSERT INTO media (namespace, id, data, associatedMessageIds) VALUES (?, ?, ?, ?)") - let referencedMessageIdsStatement = self.database.prepare("SELECT associatedMessageIds FROM media WHERE namespace = ? AND id = ?") - let updateReferencedMessageIdsStatement = self.database.prepare("UPDATE media SET associatedMessageIds = ? WHERE namespace = ? AND id = ?") + let messageInsertStatement = self.database.prepareCached("INSERT INTO peer_messages (peerId, namespace, id, data, associatedMediaIds, timestamp) VALUES (?, ?, ?, ?, ?, ?)") + let peerMediaInsertStatement = self.database.prepareCached("INSERT INTO peer_media (peerId, mediaNamespace, messageNamespace, messageId) VALUES (?, ?, ?, ?)") + let mediaInsertStatement = self.database.prepareCached("INSERT INTO media (namespace, id, data, associatedMessageIds) VALUES (?, ?, ?, ?)") + let referencedMessageIdsStatement = self.database.prepareCached("SELECT associatedMessageIds FROM media WHERE namespace = ? AND id = ?") + let updateReferencedMessageIdsStatement = self.database.prepareCached("UPDATE media SET associatedMessageIds = ? WHERE namespace = ? AND id = ?") let encoder = Encoder() @@ -437,18 +486,12 @@ public final class Postbox { } } - let relatedViews = self.peerMessageViews[peerId] ?? Bag() - for message in peerMessages { if existingMessageIds.contains(message.id) { continue } existingMessageIds.insert(message.id) - for record in relatedViews.copyItems() { - record.0.add(message) - } - let index = MessageIndex(message) if maxMessage == nil || index > maxMessage!.0 { maxMessage = (index, message) @@ -458,8 +501,8 @@ public final class Postbox { encoder.encodeRootObject(message) let messageBlob = Blob(data: encoder.makeData()) - let referencedMediaIdsMediaIdsBlob = Postbox.blobForMediaIds(message.referencedMediaIds) - for id in message.referencedMediaIds { + let referencedMediaIdsMediaIdsBlob = Postbox.blobForMediaIds(message.mediaIds) + for id in message.mediaIds { if messageIdsByMediaId[id] != nil { messageIdsByMediaId[id]!.append(message.id) } else { @@ -469,17 +512,28 @@ public final class Postbox { messageInsertStatement.run(peerId.toInt64(), Int64(message.id.namespace), Int64(message.id.id), messageBlob, referencedMediaIdsMediaIdsBlob, Int64(message.timestamp)) - for id in message.referencedMediaIds { + for id in message.mediaIds { peerMediaInsertStatement.run(peerId.toInt64(), Int64(id.namespace), Int64(message.id.namespace), Int64(message.id.id)) } } - for record in relatedViews.copyItems() { - record.1.putNext(MessageView(record.0)) + if let relatedViews = self.peerMessageViews[peerId] { + for record in relatedViews.copyItems() { + var updated = false + for message in peerMessages { + if record.0.add(RenderedMessage(message: message)) { + updated = true + } + } + + if updated { + self.deferMessageViewUpdate(record.0, pipe: record.1) + } + } } if let maxMessage = maxMessage { - self.updatePeerEntry(peerId, message: maxMessage.1) + self.updatePeerEntry(peerId, message: RenderedMessage(message: maxMessage.1)) } } @@ -585,56 +639,197 @@ public final class Postbox { return grouped } - private func peerWithId(peerId: PeerId) -> Peer? { - for row in self.database.prepare("SELECT data FROM peers WHERE peerId = ?").run(peerId.toInt64()) { - let data = (row[0] as! Blob).data - let decoder = Decoder(buffer: ReadBuffer(memory: UnsafeMutablePointer(data.bytes), length: data.length, freeWhenDone: false)) - if let peer = decoder.decodeRootObject() as? Peer { - return peer - } else { - print("(PostBox: can't decode peer)") + private func mediaWithIds(ids: [MediaId]) -> [MediaId : Media] { + if ids.count == 0 { + return [:] + } else { + let select = self.database.prepareCached("SELECT data FROM media WHERE namespace = ? AND id = ?") + var result: [MediaId : Media] = [:] + + for id in ids { + for row in select.run(Int64(id.namespace), id.id) { + let blob = row[0] as! Blob + if let media = Decoder(buffer: ReadBuffer(memory: UnsafeMutablePointer(blob.data.bytes), length: blob.data.length, freeWhenDone: false)) as? Media { + result[media.id] = media + } + break + } } - break + return result } - - return nil } - + var cachedPeers: [PeerId : Peer] = [:] - private func updatePeerEntry(peerId: PeerId, message: Message?, replace: Bool = false) { + private func peerWithId(peerId: PeerId) -> Peer? { + if let cachedPeer = cachedPeers[peerId] { + return cachedPeer + } else { + for row in self.database.prepareCached("SELECT data FROM peers WHERE id = ?").run(peerId.toInt64()) { + let data = (row[0] as! Blob).data + let decoder = Decoder(buffer: ReadBuffer(memory: UnsafeMutablePointer(data.bytes), length: data.length, freeWhenDone: false)) + if let peer = decoder.decodeRootObject() as? Peer { + cachedPeers[peer.id] = peer + return peer + } else { + print("(PostBox: can't decode peer)") + } + + break + } + + return nil + } + } + + private func peersWithIds(ids: [PeerId]) -> [PeerId : Peer] { + if ids.count == 0 { + return [:] + } else { + var remainingIds: [PeerId] = [] + + var peers: [PeerId : Peer] = [:] + + for id in ids { + if let cachedPeer = cachedPeers[id] { + peers[id] = cachedPeer + } else { + remainingIds.append(id) + } + } + + if remainingIds.count != 0 { + let rows: Statement + if ids.count == 1 { + rows = self.database.prepareCached("SELECT data FROM peers WHERE id = ?").run(ids[0].toInt64()) + } else if ids.count == 2 { + rows = self.database.prepareCached("SELECT data FROM peers WHERE id IN (?, ?)").run(ids[0].toInt64(), ids[1].toInt64()) + } else { + var query = "SELECT data FROM peers WHERE id IN (" + var first = true + for id in ids { + if first { + first = false + query += "\(id.toInt64())" + } else { + query += ",\(id.toInt64())" + } + } + query += ")" + rows = self.database.prepare(query).run() + } + + for row in rows { + let blob = row[0] as! Blob + if let peer = Decoder(buffer: ReadBuffer(memory: UnsafeMutablePointer(blob.data.bytes), length: blob.data.length, freeWhenDone: false)).decodeRootObject() as? Peer { + self.cachedPeers[peer.id] = peer + peers[peer.id] = peer + } + } + } + + return peers + } + } + + private func deferPeerViewUpdate(view: MutablePeerView, pipe: Pipe) { + var i = 0 + var found = false + while i < self.deferredPeerViewsToUpdate.count { + if self.deferredPeerViewsToUpdate[i].1 === pipe { + self.deferredPeerViewsToUpdate[i] = (view, pipe) + found = true + break + } + i++ + } + if !found { + self.deferredPeerViewsToUpdate.append((view, pipe)) + } + } + + private func deferMessageViewUpdate(view: MutableMessageView, pipe: Pipe) { + var i = 0 + var found = false + while i < self.deferredPeerViewsToUpdate.count { + if self.deferredMessageViewsToUpdate[i].1 === pipe { + self.deferredMessageViewsToUpdate[i] = (view, pipe) + found = true + break + } + i++ + } + if !found { + self.deferredMessageViewsToUpdate.append((view, pipe)) + } + } + + private func performDeferredUpdates() { + let deferredPeerViewsToUpdate = self.deferredPeerViewsToUpdate + self.deferredPeerViewsToUpdate.removeAll() + + for entry in deferredPeerViewsToUpdate { + let viewRenderedMessages = self.renderedMessages(entry.0.incompleteMessages()) + if viewRenderedMessages.count != 0 { + var viewRenderedMessagesDict: [MessageId : RenderedMessage] = [:] + for message in viewRenderedMessages { + viewRenderedMessagesDict[message.message.id] = message + } + entry.0.completeMessages(viewRenderedMessagesDict) + } + + entry.1.putNext(PeerView(entry.0)) + } + + let deferredMessageViewsToUpdate = self.deferredMessageViewsToUpdate + self.deferredMessageViewsToUpdate.removeAll() + + for entry in deferredMessageViewsToUpdate { + let viewRenderedMessages = self.renderedMessages(entry.0.incompleteMessages()) + if viewRenderedMessages.count != 0 { + var viewRenderedMessagesDict: [MessageId : RenderedMessage] = [:] + for message in viewRenderedMessages { + viewRenderedMessagesDict[message.message.id] = message + } + entry.0.completeMessages(viewRenderedMessagesDict) + } + + entry.1.putNext(MessageView(entry.0)) + } + } + + private func updatePeerEntry(peerId: PeerId, message: RenderedMessage?, replace: Bool = false) { var currentIndex: PeerViewEntryIndex? - for row in self.database.prepare("SELECT entry FROM peer_entries WHERE peerId = ?").run(peerId.toInt64()) { + for row in self.database.prepareCached("SELECT entry FROM peer_entries WHERE peerId = ?").run(peerId.toInt64()) { currentIndex = Postbox.peerViewEntryIndexForBlob(row[0] as! Blob) break } - var updatedPeerMessage: Message? + var updatedPeerMessage: RenderedMessage? if let currentIndex = currentIndex { if let message = message { - let messageIndex = MessageIndex(message) + let messageIndex = MessageIndex(message.message) if replace || currentIndex.messageIndex < messageIndex { let updatedIndex = PeerViewEntryIndex(peerId: peerId, messageIndex: messageIndex) updatedPeerMessage = message let updatedBlob = Postbox.blobForPeerViewEntryIndex(updatedIndex) - self.database.prepare("UPDATE peer_entries SET entry = ? WHERE peerId = ?").run(updatedBlob, peerId.toInt64()) + self.database.prepareCached("UPDATE peer_entries SET entry = ? WHERE peerId = ?").run(updatedBlob, peerId.toInt64()) } } else if replace { //TODO: remove? } } else if let message = message { updatedPeerMessage = message - let updatedIndex = PeerViewEntryIndex(peerId: peerId, messageIndex: MessageIndex(message)) + let updatedIndex = PeerViewEntryIndex(peerId: peerId, messageIndex: MessageIndex(message.message)) let updatedBlob = Postbox.blobForPeerViewEntryIndex(updatedIndex) - self.database.prepare("INSERT INTO peer_entries (peerId, entry) VALUES (?, ?)").run(peerId.toInt64(), updatedBlob) + self.database.prepareCached("INSERT INTO peer_entries (peerId, entry) VALUES (?, ?)").run(peerId.toInt64(), updatedBlob) } if let updatedPeerMessage = updatedPeerMessage { var peer: Peer? - for (view, sink) in self.peerViews.copyItems() { - + for (view, pipe) in self.peerViews.copyItems() { if peer == nil { for entry in view.entries { if entry.peerId == peerId { @@ -659,7 +854,7 @@ public final class Postbox { view.addEntry(entry) view.complete(context, fetchEarlier: self.fetchPeerEntriesRelative(true), fetchLater: self.fetchPeerEntriesRelative(false)) - sink.putNext(PeerView(view)) + self.deferPeerViewUpdate(view, pipe: pipe) } } } @@ -671,11 +866,11 @@ public final class Postbox { for (peerId, messageIds) in Postbox.messageIdsGroupedByPeerId(ids) { if let relatedViews = self.peerMessageViews[peerId] { - for (view, sink) in relatedViews.copyItems() { + for (view, pipe) in relatedViews.copyItems() { let context = view.remove(Set(messageIds)) if !context.empty() { view.complete(context, fetchEarlier: self.fetchMessagesRelative(peerId, earlier: true), fetchLater: self.fetchMessagesRelative(peerId, earlier: false)) - sink.putNext(MessageView(view)) + self.deferMessageViewUpdate(view, pipe: pipe) } } } @@ -735,16 +930,16 @@ public final class Postbox { updatedMessageIdsByMediaId[mediaId] = messageIds if messageIds.count == 0 { - self.database.prepare("INSERT OR IGNORE INTO media_cleanup (namespace, id, data) VALUES (?, ?, ?)").run(Int64(namespace), mediaId.id, row[1] as! Blob) + self.database.prepareCached("INSERT OR IGNORE INTO media_cleanup (namespace, id, data) VALUES (?, ?, ?)").run(Int64(namespace), mediaId.id, row[1] as! Blob) } } } for (mediaId, messageIds) in updatedMessageIdsByMediaId { if messageIds.count == 0 { - self.database.prepare("DELETE FROM media WHERE namespace = ? AND id = ?").run(Int64(mediaId.namespace), mediaId.id) + self.database.prepareCached("DELETE FROM media WHERE namespace = ? AND id = ?").run(Int64(mediaId.namespace), mediaId.id) } else { - self.database.prepare("UPDATE media SET associatedMessageIds = ? WHERE namespace = ? AND id = ?").run(Postbox.blobForMessageIds(messageIds), Int64(mediaId.namespace), mediaId.id) + self.database.prepareCached("UPDATE media SET associatedMessageIds = ? WHERE namespace = ? AND id = ?").run(Postbox.blobForMessageIds(messageIds), Int64(mediaId.namespace), mediaId.id) } } } @@ -755,13 +950,79 @@ public final class Postbox { } } + private func updatePeers(peers: [Peer], update: (Peer, Peer) -> Peer) { + if peers.count == 0 { + return + } + + if peers.count == -1 { + + } else { + var peerIds: [PeerId] = [] + for peer in peers { + peerIds.append(peer.id) + } + + let currentPeers = self.peersWithIds(peerIds) + + let updatePeer = self.database.prepareCached("UPDATE peers SET data = ? WHERE id = ?") + let insertPeer = self.database.prepareCached("INSERT INTO peers (id, data) VALUES (?, ?)") + let encoder = Encoder() + + var updatedPeers: [PeerId : Peer] = [:] + + for updatedPeer in peers { + let currentPeer = currentPeers[updatedPeer.id] + + var finalPeer = updatedPeer + if let currentPeer = currentPeer { + finalPeer = update(currentPeer, updatedPeer) + } + + if currentPeer == nil || !finalPeer.equalsTo(currentPeer!) { + updatedPeers[finalPeer.id] = finalPeer + self.cachedPeers[finalPeer.id] = finalPeer + + encoder.reset() + encoder.encodeRootObject(finalPeer) + + if currentPeer != nil { + updatePeer.run(Blob(data: encoder.makeData()), finalPeer.id.toInt64()) + } else { + insertPeer.run(finalPeer.id.toInt64(), Blob(data: encoder.makeData())) + } + } + } + + for record in self.peerViews.copyItems() { + if record.0.updatePeers(updatedPeers) { + deferPeerViewUpdate(record.0, pipe: record.1) + } + } + } + } + public func modify(f: Modifier -> T) -> Signal { return Signal { subscriber in self.queue.dispatch { + //#if DEBUG + let startTime = CFAbsoluteTimeGetCurrent() + //#endif + self.database.transaction() let result = f(Modifier(postbox: self)) + //print("(Postbox modify took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms)") + //#if DEBUG + //startTime = CFAbsoluteTimeGetCurrent() + //#endif self.database.commit() + //#if DEBUG + print("(Postbox commit took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms)") + //#endif + + self.performDeferredUpdates() + subscriber.putNext(result) subscriber.putCompletion() } @@ -772,7 +1033,7 @@ public final class Postbox { private func findAdjacentMessageIds(peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex) -> (MessageId.Id?, MessageId.Id?) { var minId: MessageId.Id? var maxId: MessageId.Id? - for row in self.database.prepare("SELECT MIN(id), MAX(id) FROM peer_messages WHERE peerId = ? AND namespace = ?").run(peerId.toInt64(), Int64(namespace)) { + for row in self.database.prepareCached("SELECT MIN(id), MAX(id) FROM peer_messages WHERE peerId = ? AND namespace = ?").run(peerId.toInt64(), Int64(namespace)) { minId = MessageId.Id(row[0] as! Int64) maxId = MessageId.Id(row[1] as! Int64) } @@ -780,7 +1041,7 @@ public final class Postbox { if let minId = minId, maxId = maxId { var minTimestamp: Int32! var maxTimestamp: Int32! - for row in self.database.prepare("SELECT id, timestamp FROM peer_messages WHERE peerId = ? AND namespace = ? AND id IN (?, ?)").run(peerId.toInt64(), Int64(namespace), Int64(minId), Int64(maxId)) { + for row in self.database.prepareCached("SELECT id, timestamp FROM peer_messages WHERE peerId = ? AND namespace = ? AND id IN (?, ?)").run(peerId.toInt64(), Int64(namespace), Int64(minId), Int64(maxId)) { let id = Int32(row[0] as! Int64) let timestamp = Int32(row[1] as! Int64) if id == minId { @@ -790,8 +1051,8 @@ public final class Postbox { } } - let earlierMidStatement = self.database.prepare("SELECT id, timestamp FROM peer_messages WHERE peerId = ? AND namespace = ? AND id <= ? LIMIT 1") - let laterMidStatement = self.database.prepare("SELECT id, timestamp FROM peer_messages WHERE peerId = ? AND namespace = ? AND id >= ? LIMIT 1") + let earlierMidStatement = self.database.prepareCached("SELECT id, timestamp FROM peer_messages WHERE peerId = ? AND namespace = ? AND id <= ? LIMIT 1") + let laterMidStatement = self.database.prepareCached("SELECT id, timestamp FROM peer_messages WHERE peerId = ? AND namespace = ? AND id >= ? LIMIT 1") func lowerBound(timestamp: Int32) -> MessageId.Id? { var leftId = minId @@ -883,16 +1144,16 @@ public final class Postbox { } } - private func fetchMessagesAround(peerId: PeerId, anchorId: MessageId, count: Int) -> ([Message], [MessageId.Namespace : Message], [MessageId.Namespace : Message]) { - var messages: [Message] = [] + private func fetchMessagesAround(peerId: PeerId, anchorId: MessageId, count: Int) -> ([RenderedMessage], [MessageId.Namespace : RenderedMessage], [MessageId.Namespace : RenderedMessage]) { + var messages: [RenderedMessage] = [] messages += self.fetchMessagesRelative(peerId, earlier: true)(namespace: anchorId.namespace, id: anchorId.id, count: count + 1) messages += self.fetchMessagesRelative(peerId, earlier: false)(namespace: anchorId.namespace, id: anchorId.id - 1, count: count + 1) - messages.sortInPlace({ MessageIndex($0) < MessageIndex($1) }) + messages.sortInPlace({ MessageIndex($0.message) < MessageIndex($1.message) }) var i = messages.count - 1 while i >= 1 { - if messages[i].id == messages[i - 1].id { + if messages[i].message.id == messages[i - 1].message.id { messages.removeAtIndex(i) } i-- @@ -903,19 +1164,19 @@ public final class Postbox { } else { var index: MessageIndex! for message in messages { - if message.id == anchorId { - index = MessageIndex(message) + if message.message.id == anchorId { + index = MessageIndex(message.message) break } } if index == nil { - var closestId: MessageId.Id = messages[0].id.id + var closestId: MessageId.Id = messages[0].message.id.id var closestDistance = abs(closestId - anchorId.id) - let closestTimestamp: Int32 = messages[0].timestamp + let closestTimestamp: Int32 = messages[0].message.timestamp for message in messages { - if abs(message.id.id - anchorId.id) < closestDistance { - closestId = message.id.id - closestDistance = abs(message.id.id - anchorId.id) + if abs(message.message.id.id - anchorId.id) < closestDistance { + closestId = message.message.id.id + closestDistance = abs(message.message.id.id - anchorId.id) } } index = MessageIndex(id: MessageId(peerId: peerId, namespace: anchorId.namespace, id: closestId), timestamp: closestTimestamp) @@ -933,10 +1194,10 @@ public final class Postbox { } } - messages.sortInPlace({ MessageIndex($0) < MessageIndex($1) }) + messages.sortInPlace({ MessageIndex($0.message) < MessageIndex($1.message) }) var i = messages.count - 1 while i >= 1 { - if messages[i].id == messages[i - 1].id { + if messages[i].message.id == messages[i - 1].message.id { messages.removeAtIndex(i) } i-- @@ -945,16 +1206,16 @@ public final class Postbox { var anchorIndex = messages.count / 2 i = 0 while i < messages.count { - if messages[i].id == index.id { + if messages[i].message.id == index.id { anchorIndex = i break } i++ } - var filteredMessages: [Message] = [] - var earlier: [MessageId.Namespace : Message] = [:] - var later: [MessageId.Namespace : Message] = [:] + var filteredMessages: [RenderedMessage] = [] + var earlier: [MessageId.Namespace : RenderedMessage] = [:] + var later: [MessageId.Namespace : RenderedMessage] = [:] i = anchorIndex var j = anchorIndex - 1 @@ -977,32 +1238,32 @@ public final class Postbox { i = leftIndex - 1 while i >= 0 { - if earlier[messages[i].id.namespace] == nil { - earlier[messages[i].id.namespace] = messages[i] + if earlier[messages[i].message.id.namespace] == nil { + earlier[messages[i].message.id.namespace] = messages[i] } i-- } i = rightIndex + 1 while i < messages.count { - if later[messages[i].id.namespace] == nil { - later[messages[i].id.namespace] = messages[i] + if later[messages[i].message.id.namespace] == nil { + later[messages[i].message.id.namespace] = messages[i] } i++ } - filteredMessages.sortInPlace({ MessageIndex($0) < MessageIndex($1) }) + filteredMessages.sortInPlace({ MessageIndex($0.message) < MessageIndex($1.message) }) return (filteredMessages, earlier, later) } } - private func fetchMessagesRelative(peerId: PeerId, earlier: Bool)(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [Message] { + private func fetchMessagesRelative(peerId: PeerId, earlier: Bool)(namespace: MessageId.Namespace, id: MessageId.Id?, count: Int) -> [RenderedMessage] { var messages: [Message] = [] let sign = earlier ? "<" : ">" let order = earlier ? "DESC" : "ASC" - let statement = self.database.prepare("SELECT data, associatedMediaIds FROM peer_messages WHERE peerId = ? AND namespace = ? AND id \(sign) ? ORDER BY id \(order) LIMIT \(count)") + let statement = self.database.prepareCached("SELECT data, associatedMediaIds FROM peer_messages WHERE peerId = ? AND namespace = ? AND id \(sign) ? ORDER BY id \(order) LIMIT ?") let bound: Int64 if let id = id { bound = Int64(id) @@ -1012,7 +1273,7 @@ public final class Postbox { bound = Int64(Int32.min) } - for row in statement.run(Int64(peerId.toInt64()), Int64(namespace), bound) { + for row in statement.run(Int64(peerId.toInt64()), Int64(namespace), bound, Int64(count)) { let data = (row[0] as! Blob).data let decoder = Decoder(buffer: ReadBuffer(memory: UnsafeMutablePointer(data.bytes), length: data.length, freeWhenDone: false)) if let message = decoder.decodeRootObject() as? Message { @@ -1022,7 +1283,7 @@ public final class Postbox { } } - return messages + return self.renderedMessages(messages) } private func fetchPeerEntryIndicesRelative(earlier: Bool)(index: PeerViewEntryIndex?, count: Int) -> [PeerViewEntryIndex] { @@ -1049,12 +1310,12 @@ public final class Postbox { return entries } - private func messageForPeer(peerId: PeerId, id: MessageId) -> Message? { + private func messageForPeer(peerId: PeerId, id: MessageId) -> RenderedMessage? { for row in self.database.prepareCached("SELECT data, associatedMediaIds FROM peer_messages WHERE peerId = ? AND namespace = ? AND id = ?").run(peerId.toInt64(), Int64(id.namespace), Int64(id.id)) { let data = (row[0] as! Blob).data let decoder = Decoder(buffer: ReadBuffer(memory: UnsafeMutablePointer(data.bytes), length: data.length, freeWhenDone: false)) if let message = decoder.decodeRootObject() as? Message { - return message + return self.renderedMessages([message]).first } else { print("(PostBox: can't decode message)") } @@ -1098,38 +1359,98 @@ public final class Postbox { return entries } - private func fetchMessagesTail(peerId: PeerId, count: Int) -> [Message] { - var messages: [Message] = [] + private func renderedMessages(messages: [Message]) -> [RenderedMessage] { + if messages.count == 0 { + return [] + } + + var peerIds = Set() + var mediaIds = Set() + + for message in messages { + for peerId in message.peerIds { + peerIds.insert(peerId) + } + for mediaId in message.mediaIds { + mediaIds.insert(mediaId) + } + } + + var arrayPeerIds: [PeerId] = [] + for id in peerIds { + arrayPeerIds.append(id) + } + let peers = self.peersWithIds(arrayPeerIds) + + var arrayMediaIds: [MediaId] = [] + for id in mediaIds { + arrayMediaIds.append(id) + } + let medias = self.mediaWithIds(arrayMediaIds) + + var result: [RenderedMessage] = [] + + for message in messages { + if message.peerIds.count == 0 && message.mediaIds.count == 0 { + result.append(RenderedMessage(message: message, peers: [], media: [])) + } else { + var messagePeers: [Peer] = [] + for id in message.peerIds { + if let peer = peers[id] { + messagePeers.append(peer) + } + } + + var messageMedia: [Media] = [] + for id in message.mediaIds { + if let media = medias[id] { + messageMedia.append(media) + } + } + + result.append(RenderedMessage(message: message, peers: messagePeers, media: messageMedia)) + } + } + + return result + } + + private func fetchMessagesTail(peerId: PeerId, count: Int) -> [RenderedMessage] { + var messages: [RenderedMessage] = [] for namespace in self.messageNamespaces { messages += self.fetchMessagesRelative(peerId, earlier: true)(namespace: namespace, id: nil, count: count) } - messages.sortInPlace({ MessageIndex($0) < MessageIndex($1)}) + messages.sortInPlace({ MessageIndex($0.message) < MessageIndex($1.message)}) return messages } public func tailMessageViewForPeerId(peerId: PeerId, count: Int) -> Signal { return Signal { subscriber in + let startTime = CFAbsoluteTimeGetCurrent() + let disposable = MetaDisposable() self.queue.dispatch { let tail = self.fetchMessagesTail(peerId, count: count + 1) - var messages: [Message] = [] + print("tailMessageViewForPeerId fetch: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") + + var messages: [RenderedMessage] = [] var i = tail.count - 1 while i >= 0 && i >= tail.count - count { messages.insert(tail[i], atIndex: 0) i-- } - var earlier: [MessageId.Namespace : Message] = [:] + var earlier: [MessageId.Namespace : RenderedMessage] = [:] for namespace in self.messageNamespaces { var i = tail.count - count - 1 while i >= 0 { - if tail[i].id.namespace == namespace { + if tail[i].message.id.namespace == namespace { earlier[namespace] = tail[i] break } @@ -1184,19 +1505,19 @@ public final class Postbox { if around.0.count == 0 { let tail = self.fetchMessagesTail(peerId, count: count + 1) - var messages: [Message] = [] + var messages: [RenderedMessage] = [] var i = tail.count - 1 while i >= 0 && i >= tail.count - count { messages.insert(tail[i], atIndex: 0) i-- } - var earlier: [MessageId.Namespace : Message] = [:] + var earlier: [MessageId.Namespace : RenderedMessage] = [:] for namespace in self.messageNamespaces { var i = tail.count - count - 1 while i >= 0 { - if tail[i].id.namespace == namespace { + if tail[i].message.id.namespace == namespace { earlier[namespace] = tail[i] break } @@ -1249,7 +1570,12 @@ public final class Postbox { let disposable = MetaDisposable() self.queue.dispatch { + let startTime = CFAbsoluteTimeGetCurrent() + let tail = self.fetchPeerEntriesRelative(true)(index: nil, count: count + 1) + self.fetchPeerEntriesRelative(true)(index: nil, count: count + 1) + + print("(Postbox fetchPeerEntriesRelative took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms)") var entries: [PeerViewEntry] = [] var i = tail.count - 1 diff --git a/submodules/SQLite.swift b/submodules/SQLite.swift index e53267d739..b0b93692fd 160000 --- a/submodules/SQLite.swift +++ b/submodules/SQLite.swift @@ -1 +1 @@ -Subproject commit e53267d7394c6df648d588bebc4db10dc853c75d +Subproject commit b0b93692fdac68e8ffa71e11b23c95b3e0f8b256