Merge branch 'database-refactor' into add-documents-section

pull/646/head
ryanzhao 2 years ago
commit 456c9ac874

@ -6838,7 +6838,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 356; CURRENT_PROJECT_VERSION = 357;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -6910,7 +6910,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 356; CURRENT_PROJECT_VERSION = 357;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",

@ -54,14 +54,25 @@ class MediaDetailViewController: OWSViewController, UIScrollViewDelegate, OWSVid
galleryItem.attachment.thumbnail( galleryItem.attachment.thumbnail(
size: .large, size: .large,
success: { [weak self] image, _ in success: { [weak self] image, _ in
self?.image = image
// Only reload the content if the view has already loaded (if it // Only reload the content if the view has already loaded (if it
// hasn't then it'll load with the image immediately) // hasn't then it'll load with the image immediately)
if self?.isViewLoaded == true { let updateUICallback = {
self?.updateContents() self?.image = image
self?.updateMinZoomScale()
if self?.isViewLoaded == true {
self?.updateContents()
self?.updateMinZoomScale()
}
}
guard Thread.isMainThread else {
DispatchQueue.main.async {
updateUICallback()
}
return
} }
updateUICallback()
}, },
failure: { failure: {
SNLog("Could not load media.") SNLog("Could not load media.")

@ -7,13 +7,9 @@ import SessionSnodeKit
import SessionMessagingKit import SessionMessagingKit
import SessionUtilitiesKit import SessionUtilitiesKit
@objc(LKBackgroundPoller) public final class BackgroundPoller {
public final class BackgroundPoller: NSObject {
private static var promises: [Promise<Void>] = [] private static var promises: [Promise<Void>] = []
private override init() { }
@objc(pollWithCompletionHandler:)
public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
promises = [] promises = []
.appending(pollForMessages()) .appending(pollForMessages())
@ -40,12 +36,29 @@ public final class BackgroundPoller: NSObject {
} }
) )
// Background tasks will automatically be terminated after 30 seconds (which results in a crash
// and a prompt to appear for the user) we want to avoid this so we start a timer which expires
// after 25 seconds allowing us to cancel all pending promises
let cancelTimer: Timer = Timer.scheduledTimerOnMainThread(withTimeInterval: 25, repeats: false) { timer in
timer.invalidate()
guard promises.contains(where: { !$0.isResolved }) else { return }
SNLog("Background poll failed due to manual timeout")
completionHandler(.failed)
}
when(resolved: promises) when(resolved: promises)
.done { _ in .done { _ in
cancelTimer.invalidate()
completionHandler(.newData) completionHandler(.newData)
} }
.catch { error in .catch { error in
// If we have already invalidated the timer then do nothing (we essentially timed out)
guard cancelTimer.isValid else { return }
SNLog("Background poll failed due to error: \(error)") SNLog("Background poll failed due to error: \(error)")
cancelTimer.invalidate()
completionHandler(.failed) completionHandler(.failed)
} }
} }
@ -74,7 +87,7 @@ public final class BackgroundPoller: NSObject {
ClosedGroupPoller.poll( ClosedGroupPoller.poll(
groupPublicKey, groupPublicKey,
on: DispatchQueue.main, on: DispatchQueue.main,
maxRetryCount: 4, maxRetryCount: 0,
isBackgroundPoll: true isBackgroundPoll: true
) )
} }
@ -85,78 +98,76 @@ public final class BackgroundPoller: NSObject {
.then(on: DispatchQueue.main) { swarm -> Promise<Void> in .then(on: DispatchQueue.main) { swarm -> Promise<Void> in
guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic }
return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { return SnodeAPI.getMessages(from: snode, associatedWith: publicKey)
return SnodeAPI.getMessages(from: snode, associatedWith: publicKey) .then(on: DispatchQueue.main) { messages -> Promise<Void> in
.then(on: DispatchQueue.main) { messages -> Promise<Void> in guard !messages.isEmpty else { return Promise.value(()) }
guard !messages.isEmpty else { return Promise.value(()) }
var jobsToRun: [Job] = []
var jobsToRun: [Job] = []
Storage.shared.write { db in
var threadMessages: [String: [MessageReceiveJob.Details.MessageInfo]] = [:]
Storage.shared.write { db in messages.forEach { message in
var threadMessages: [String: [MessageReceiveJob.Details.MessageInfo]] = [:] do {
let processedMessage: ProcessedMessage? = try Message.processRawReceivedMessage(db, rawMessage: message)
messages.forEach { message in let key: String = (processedMessage?.threadId ?? Message.nonThreadMessageId)
do {
let processedMessage: ProcessedMessage? = try Message.processRawReceivedMessage(db, rawMessage: message) threadMessages[key] = (threadMessages[key] ?? [])
let key: String = (processedMessage?.threadId ?? Message.nonThreadMessageId) .appending(processedMessage?.messageInfo)
threadMessages[key] = (threadMessages[key] ?? [])
.appending(processedMessage?.messageInfo)
}
catch {
switch error {
// Ignore duplicate & selfSend message errors (and don't bother logging
// them as there will be a lot since we each service node duplicates messages)
case DatabaseError.SQLITE_CONSTRAINT_UNIQUE,
MessageReceiverError.duplicateMessage,
MessageReceiverError.duplicateControlMessage,
MessageReceiverError.selfSend:
break
default: SNLog("Failed to deserialize envelope due to error: \(error).")
}
}
} }
catch {
threadMessages switch error {
.forEach { threadId, threadMessages in // Ignore duplicate & selfSend message errors (and don't bother logging
let maybeJob: Job? = Job( // them as there will be a lot since we each service node duplicates messages)
variant: .messageReceive, case DatabaseError.SQLITE_CONSTRAINT_UNIQUE,
behaviour: .runOnce, MessageReceiverError.duplicateMessage,
threadId: threadId, MessageReceiverError.duplicateControlMessage,
details: MessageReceiveJob.Details( MessageReceiverError.selfSend:
messages: threadMessages, break
isBackgroundPoll: true
)
)
guard let job: Job = maybeJob else { return }
// Add to the JobRunner so they are persistent and will retry on default: SNLog("Failed to deserialize envelope due to error: \(error).")
// the next app run if they fail
JobRunner.add(db, job: job, canStartJob: false)
jobsToRun.append(job)
} }
}
} }
let promises: [Promise<Void>] = jobsToRun.map { job -> Promise<Void> in threadMessages
let (promise, seal) = Promise<Void>.pending() .forEach { threadId, threadMessages in
let maybeJob: Job? = Job(
// Note: In the background we just want jobs to fail silently variant: .messageReceive,
MessageReceiveJob.run( behaviour: .runOnce,
job, threadId: threadId,
queue: DispatchQueue.main, details: MessageReceiveJob.Details(
success: { _, _ in seal.fulfill(()) }, messages: threadMessages,
failure: { _, _, _ in seal.fulfill(()) }, isBackgroundPoll: true
deferred: { _ in seal.fulfill(()) } )
) )
return promise guard let job: Job = maybeJob else { return }
}
// Add to the JobRunner so they are persistent and will retry on
// the next app run if they fail
JobRunner.add(db, job: job, canStartJob: false)
jobsToRun.append(job)
}
}
let promises: [Promise<Void>] = jobsToRun.map { job -> Promise<Void> in
let (promise, seal) = Promise<Void>.pending()
// Note: In the background we just want jobs to fail silently
MessageReceiveJob.run(
job,
queue: DispatchQueue.main,
success: { _, _ in seal.fulfill(()) },
failure: { _, _, _ in seal.fulfill(()) },
deferred: { _ in seal.fulfill(()) }
)
return when(fulfilled: promises) return promise
} }
}
return when(fulfilled: promises)
}
} }
} }
} }

@ -2328,7 +2328,7 @@ class OpenGroupAPISpec: QuickSpec {
OpenGroupAPI OpenGroupAPI
.downloadFile( .downloadFile(
db, db,
fileId: 1, fileId: "1",
from: "testRoom", from: "testRoom",
on: "testserver", on: "testserver",
using: dependencies using: dependencies

@ -1574,7 +1574,7 @@ class OpenGroupManagerSpec: QuickSpec {
created: 0, created: 0,
activeUsers: 0, activeUsers: 0,
activeUsersCutoff: 0, activeUsersCutoff: 0,
imageId: 10, imageId: "10",
pinnedMessages: nil, pinnedMessages: nil,
admin: false, admin: false,
globalAdmin: false, globalAdmin: false,
@ -1732,7 +1732,7 @@ class OpenGroupManagerSpec: QuickSpec {
created: 0, created: 0,
activeUsers: 0, activeUsers: 0,
activeUsersCutoff: 0, activeUsersCutoff: 0,
imageId: 10, imageId: "10",
pinnedMessages: nil, pinnedMessages: nil,
admin: false, admin: false,
globalAdmin: false, globalAdmin: false,
@ -1843,7 +1843,7 @@ class OpenGroupManagerSpec: QuickSpec {
created: 0, created: 0,
activeUsers: 0, activeUsers: 0,
activeUsersCutoff: 0, activeUsersCutoff: 0,
imageId: 10, imageId: "10",
pinnedMessages: nil, pinnedMessages: nil,
admin: false, admin: false,
globalAdmin: false, globalAdmin: false,
@ -1912,7 +1912,7 @@ class OpenGroupManagerSpec: QuickSpec {
created: 0, created: 0,
activeUsers: 0, activeUsers: 0,
activeUsersCutoff: 0, activeUsersCutoff: 0,
imageId: 10, imageId: "10",
pinnedMessages: nil, pinnedMessages: nil,
admin: false, admin: false,
globalAdmin: false, globalAdmin: false,
@ -2988,7 +2988,7 @@ class OpenGroupManagerSpec: QuickSpec {
created: 0, created: 0,
activeUsers: 0, activeUsers: 0,
activeUsersCutoff: 0, activeUsersCutoff: 0,
imageId: 12, imageId: "12",
pinnedMessages: nil, pinnedMessages: nil,
admin: false, admin: false,
globalAdmin: false, globalAdmin: false,
@ -3104,7 +3104,7 @@ class OpenGroupManagerSpec: QuickSpec {
created: 0, created: 0,
activeUsers: 0, activeUsers: 0,
activeUsersCutoff: 0, activeUsersCutoff: 0,
imageId: 12, imageId: "12",
pinnedMessages: nil, pinnedMessages: nil,
admin: false, admin: false,
globalAdmin: false, globalAdmin: false,
@ -3188,7 +3188,7 @@ class OpenGroupManagerSpec: QuickSpec {
created: 0, created: 0,
activeUsers: 0, activeUsers: 0,
activeUsersCutoff: 0, activeUsersCutoff: 0,
imageId: 12, imageId: "12",
pinnedMessages: nil, pinnedMessages: nil,
admin: false, admin: false,
globalAdmin: false, globalAdmin: false,
@ -3279,7 +3279,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: "testServer", on: "testServer",
using: dependencies using: dependencies
@ -3293,7 +3293,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: "testServer", on: "testServer",
using: dependencies using: dependencies
@ -3321,7 +3321,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: "testServer", on: "testServer",
using: dependencies using: dependencies
@ -3348,7 +3348,7 @@ class OpenGroupManagerSpec: QuickSpec {
return Promise<(OnionRequestResponseInfoType, Data?)>.pending().promise return Promise<(OnionRequestResponseInfoType, Data?)>.pending().promise
} }
static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, using version: OnionRequestAPIVersion, associatedWith publicKey: String?) -> Promise<Data> { static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise<Data> {
return Promise.value(Data()) return Promise.value(Data())
} }
} }
@ -3357,7 +3357,7 @@ class OpenGroupManagerSpec: QuickSpec {
let promise = mockStorage.read { db in let promise = mockStorage.read { db in
OpenGroupManager.roomImage( OpenGroupManager.roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: "testServer", on: "testServer",
using: dependencies using: dependencies
@ -3382,7 +3382,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: OpenGroupAPI.defaultServer, on: OpenGroupAPI.defaultServer,
using: dependencies using: dependencies
@ -3400,7 +3400,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: OpenGroupAPI.defaultServer, on: OpenGroupAPI.defaultServer,
using: dependencies using: dependencies
@ -3428,7 +3428,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: OpenGroupAPI.defaultServer, on: OpenGroupAPI.defaultServer,
using: dependencies using: dependencies
@ -3471,7 +3471,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: OpenGroupAPI.defaultServer, on: OpenGroupAPI.defaultServer,
using: dependencies using: dependencies
@ -3500,7 +3500,7 @@ class OpenGroupManagerSpec: QuickSpec {
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
fileId: 1, fileId: "1",
for: "testRoom", for: "testRoom",
on: OpenGroupAPI.defaultServer, on: OpenGroupAPI.defaultServer,
using: dependencies using: dependencies

@ -46,7 +46,7 @@ class SOGSEndpointSpec: QuickSpec {
// Files // Files
expect(OpenGroupAPI.Endpoint.roomFile("test").path).to(equal("room/test/file")) expect(OpenGroupAPI.Endpoint.roomFile("test").path).to(equal("room/test/file"))
expect(OpenGroupAPI.Endpoint.roomFileIndividual("test", 123).path).to(equal("room/test/file/123")) expect(OpenGroupAPI.Endpoint.roomFileIndividual("test", "123").path).to(equal("room/test/file/123"))
// Inbox/Outbox (Message Requests) // Inbox/Outbox (Message Requests)

@ -54,7 +54,7 @@ class TestOnionRequestAPI: OnionRequestAPIType {
return Promise.value((responseInfo, mockResponse)) return Promise.value((responseInfo, mockResponse))
} }
static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, using version: OnionRequestAPIVersion, associatedWith publicKey: String?) -> Promise<Data> { static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise<Data> {
return Promise.value(mockResponse!) return Promise.value(mockResponse!)
} }
} }

@ -212,13 +212,13 @@ public enum OnionRequestAPI: OnionRequestAPIType {
} }
// randomElement() uses the system's default random generator, which is cryptographically secure // randomElement() uses the system's default random generator, which is cryptographically secure
if paths.count >= targetPathCount { if
if let snode: Snode = snode { paths.count >= targetPathCount,
return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) } let targetPath: [Snode] = paths
} .filter({ snode == nil || !$0.contains(snode!) })
else { .randomElement()
return Promise { $0.fulfill(paths.randomElement()!) } {
} return Promise { $0.fulfill(targetPath) }
} }
else if !paths.isEmpty { else if !paths.isEmpty {
if let snode = snode { if let snode = snode {
@ -228,13 +228,22 @@ public enum OnionRequestAPI: OnionRequestAPIType {
} }
else { else {
return buildPaths(reusing: paths).map2 { paths in return buildPaths(reusing: paths).map2 { paths in
return paths.filter { !$0.contains(snode) }.randomElement()! guard let path: [Snode] = paths.filter({ !$0.contains(snode) }).randomElement() else {
throw OnionRequestAPIError.insufficientSnodes
}
return path
} }
} }
} }
else { else {
buildPaths(reusing: paths) // Re-build paths in the background buildPaths(reusing: paths) // Re-build paths in the background
return Promise { $0.fulfill(paths.randomElement()!) }
guard let path: [Snode] = paths.randomElement() else {
return Promise(error: OnionRequestAPIError.insufficientSnodes)
}
return Promise { $0.fulfill(path) }
} }
} }
else { else {
@ -247,7 +256,11 @@ public enum OnionRequestAPI: OnionRequestAPIType {
throw OnionRequestAPIError.insufficientSnodes throw OnionRequestAPIError.insufficientSnodes
} }
return paths.randomElement()! guard let path: [Snode] = paths.randomElement() else {
throw OnionRequestAPIError.insufficientSnodes
}
return path
} }
} }
} }

Loading…
Cancel
Save