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

This commit is contained in:
ryanzhao 2022-07-19 16:16:42 +10:00
commit 456c9ac874
8 changed files with 143 additions and 108 deletions

View File

@ -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)",

View File

@ -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.")

View File

@ -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)
}
}
}
}

View File

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

View File

@ -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

View File

@ -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)

View File

@ -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!)
}
}

View File

@ -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
}
}
}