Merge branch 'database-refactor' into add-documents-section
This commit is contained in:
commit
456c9ac874
|
@ -6838,7 +6838,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 356;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -6910,7 +6910,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 356;
|
||||
CURRENT_PROJECT_VERSION = 357;
|
||||
DEVELOPMENT_TEAM = SUQ8J2PCT7;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
|
@ -54,14 +54,25 @@ class MediaDetailViewController: OWSViewController, UIScrollViewDelegate, OWSVid
|
|||
galleryItem.attachment.thumbnail(
|
||||
size: .large,
|
||||
success: { [weak self] image, _ in
|
||||
self?.image = image
|
||||
|
||||
// Only reload the content if the view has already loaded (if it
|
||||
// hasn't then it'll load with the image immediately)
|
||||
if self?.isViewLoaded == true {
|
||||
self?.updateContents()
|
||||
self?.updateMinZoomScale()
|
||||
let updateUICallback = {
|
||||
self?.image = image
|
||||
|
||||
if self?.isViewLoaded == true {
|
||||
self?.updateContents()
|
||||
self?.updateMinZoomScale()
|
||||
}
|
||||
}
|
||||
|
||||
guard Thread.isMainThread else {
|
||||
DispatchQueue.main.async {
|
||||
updateUICallback()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
updateUICallback()
|
||||
},
|
||||
failure: {
|
||||
SNLog("Could not load media.")
|
||||
|
|
|
@ -7,13 +7,9 @@ import SessionSnodeKit
|
|||
import SessionMessagingKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@objc(LKBackgroundPoller)
|
||||
public final class BackgroundPoller: NSObject {
|
||||
public final class BackgroundPoller {
|
||||
private static var promises: [Promise<Void>] = []
|
||||
|
||||
private override init() { }
|
||||
|
||||
@objc(pollWithCompletionHandler:)
|
||||
public static func poll(completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
promises = []
|
||||
.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)
|
||||
.done { _ in
|
||||
cancelTimer.invalidate()
|
||||
completionHandler(.newData)
|
||||
}
|
||||
.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)")
|
||||
cancelTimer.invalidate()
|
||||
completionHandler(.failed)
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +87,7 @@ public final class BackgroundPoller: NSObject {
|
|||
ClosedGroupPoller.poll(
|
||||
groupPublicKey,
|
||||
on: DispatchQueue.main,
|
||||
maxRetryCount: 4,
|
||||
maxRetryCount: 0,
|
||||
isBackgroundPoll: true
|
||||
)
|
||||
}
|
||||
|
@ -85,78 +98,76 @@ public final class BackgroundPoller: NSObject {
|
|||
.then(on: DispatchQueue.main) { swarm -> Promise<Void> in
|
||||
guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic }
|
||||
|
||||
return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) {
|
||||
return SnodeAPI.getMessages(from: snode, associatedWith: publicKey)
|
||||
.then(on: DispatchQueue.main) { messages -> Promise<Void> in
|
||||
guard !messages.isEmpty else { return Promise.value(()) }
|
||||
return SnodeAPI.getMessages(from: snode, associatedWith: publicKey)
|
||||
.then(on: DispatchQueue.main) { messages -> Promise<Void> in
|
||||
guard !messages.isEmpty else { return Promise.value(()) }
|
||||
|
||||
var jobsToRun: [Job] = []
|
||||
|
||||
Storage.shared.write { db in
|
||||
var threadMessages: [String: [MessageReceiveJob.Details.MessageInfo]] = [:]
|
||||
|
||||
var jobsToRun: [Job] = []
|
||||
|
||||
Storage.shared.write { db in
|
||||
var threadMessages: [String: [MessageReceiveJob.Details.MessageInfo]] = [:]
|
||||
|
||||
messages.forEach { message in
|
||||
do {
|
||||
let processedMessage: ProcessedMessage? = try Message.processRawReceivedMessage(db, rawMessage: message)
|
||||
let key: String = (processedMessage?.threadId ?? Message.nonThreadMessageId)
|
||||
messages.forEach { message in
|
||||
do {
|
||||
let processedMessage: ProcessedMessage? = try Message.processRawReceivedMessage(db, rawMessage: message)
|
||||
let key: String = (processedMessage?.threadId ?? Message.nonThreadMessageId)
|
||||
|
||||
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
|
||||
|
||||
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).")
|
||||
}
|
||||
default: SNLog("Failed to deserialize envelope due to error: \(error).")
|
||||
}
|
||||
}
|
||||
|
||||
threadMessages
|
||||
.forEach { threadId, threadMessages in
|
||||
let maybeJob: Job? = Job(
|
||||
variant: .messageReceive,
|
||||
behaviour: .runOnce,
|
||||
threadId: threadId,
|
||||
details: MessageReceiveJob.Details(
|
||||
messages: threadMessages,
|
||||
isBackgroundPoll: true
|
||||
)
|
||||
)
|
||||
|
||||
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 promise
|
||||
}
|
||||
|
||||
return when(fulfilled: promises)
|
||||
threadMessages
|
||||
.forEach { threadId, threadMessages in
|
||||
let maybeJob: Job? = Job(
|
||||
variant: .messageReceive,
|
||||
behaviour: .runOnce,
|
||||
threadId: threadId,
|
||||
details: MessageReceiveJob.Details(
|
||||
messages: threadMessages,
|
||||
isBackgroundPoll: true
|
||||
)
|
||||
)
|
||||
|
||||
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 promise
|
||||
}
|
||||
|
||||
return when(fulfilled: promises)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2328,7 +2328,7 @@ class OpenGroupAPISpec: QuickSpec {
|
|||
OpenGroupAPI
|
||||
.downloadFile(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
from: "testRoom",
|
||||
on: "testserver",
|
||||
using: dependencies
|
||||
|
|
|
@ -1574,7 +1574,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: 10,
|
||||
imageId: "10",
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
|
@ -1732,7 +1732,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: 10,
|
||||
imageId: "10",
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
|
@ -1843,7 +1843,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: 10,
|
||||
imageId: "10",
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
|
@ -1912,7 +1912,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: 10,
|
||||
imageId: "10",
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
|
@ -2988,7 +2988,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: 12,
|
||||
imageId: "12",
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
|
@ -3104,7 +3104,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: 12,
|
||||
imageId: "12",
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
|
@ -3188,7 +3188,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
created: 0,
|
||||
activeUsers: 0,
|
||||
activeUsersCutoff: 0,
|
||||
imageId: 12,
|
||||
imageId: "12",
|
||||
pinnedMessages: nil,
|
||||
admin: false,
|
||||
globalAdmin: false,
|
||||
|
@ -3279,7 +3279,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: "testServer",
|
||||
using: dependencies
|
||||
|
@ -3293,7 +3293,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: "testServer",
|
||||
using: dependencies
|
||||
|
@ -3321,7 +3321,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: "testServer",
|
||||
using: dependencies
|
||||
|
@ -3348,7 +3348,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
@ -3357,7 +3357,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
let promise = mockStorage.read { db in
|
||||
OpenGroupManager.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: "testServer",
|
||||
using: dependencies
|
||||
|
@ -3382,7 +3382,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: OpenGroupAPI.defaultServer,
|
||||
using: dependencies
|
||||
|
@ -3400,7 +3400,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: OpenGroupAPI.defaultServer,
|
||||
using: dependencies
|
||||
|
@ -3428,7 +3428,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: OpenGroupAPI.defaultServer,
|
||||
using: dependencies
|
||||
|
@ -3471,7 +3471,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: OpenGroupAPI.defaultServer,
|
||||
using: dependencies
|
||||
|
@ -3500,7 +3500,7 @@ class OpenGroupManagerSpec: QuickSpec {
|
|||
OpenGroupManager
|
||||
.roomImage(
|
||||
db,
|
||||
fileId: 1,
|
||||
fileId: "1",
|
||||
for: "testRoom",
|
||||
on: OpenGroupAPI.defaultServer,
|
||||
using: dependencies
|
||||
|
|
|
@ -46,7 +46,7 @@ class SOGSEndpointSpec: QuickSpec {
|
|||
// Files
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class TestOnionRequestAPI: OnionRequestAPIType {
|
|||
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!)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -212,13 +212,13 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
}
|
||||
|
||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||
if paths.count >= targetPathCount {
|
||||
if let snode: Snode = snode {
|
||||
return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) }
|
||||
}
|
||||
else {
|
||||
return Promise { $0.fulfill(paths.randomElement()!) }
|
||||
}
|
||||
if
|
||||
paths.count >= targetPathCount,
|
||||
let targetPath: [Snode] = paths
|
||||
.filter({ snode == nil || !$0.contains(snode!) })
|
||||
.randomElement()
|
||||
{
|
||||
return Promise { $0.fulfill(targetPath) }
|
||||
}
|
||||
else if !paths.isEmpty {
|
||||
if let snode = snode {
|
||||
|
@ -228,13 +228,22 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
}
|
||||
else {
|
||||
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 {
|
||||
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 {
|
||||
|
@ -247,7 +256,11 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
throw OnionRequestAPIError.insufficientSnodes
|
||||
}
|
||||
|
||||
return paths.randomElement()!
|
||||
guard let path: [Snode] = paths.randomElement() else {
|
||||
throw OnionRequestAPIError.insufficientSnodes
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue