mirror of https://github.com/oxen-io/session-ios
Updated the JobRunner to have multiple job queues (needs more testing)
Added a backoff to the Poller retry Updated the "blocking" behaviour of the JobRunner Tweaked the Job dependency handling to better handle orphaned dependencies Fixed an issue where the Conversation screen wasn't observing database changespull/612/head
parent
45d0faee6a
commit
3514ed4f50
@ -0,0 +1,121 @@
|
||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension UnicodeScalar {
|
||||
class EmojiRange {
|
||||
// rangeStart and rangeEnd are inclusive.
|
||||
let rangeStart: UInt32
|
||||
let rangeEnd: UInt32
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
init(rangeStart: UInt32, rangeEnd: UInt32) {
|
||||
self.rangeStart = rangeStart
|
||||
self.rangeEnd = rangeEnd
|
||||
}
|
||||
}
|
||||
|
||||
// From:
|
||||
// https://www.unicode.org/Public/emoji/
|
||||
// Current Version:
|
||||
// https://www.unicode.org/Public/emoji/6.0/emoji-data.txt
|
||||
//
|
||||
// These ranges can be code-generated using:
|
||||
//
|
||||
// * Scripts/emoji-data.txt
|
||||
// * Scripts/emoji_ranges.py
|
||||
static let kEmojiRanges = [
|
||||
// NOTE: Don't treat Pound Sign # as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0x23, rangeEnd:0x23),
|
||||
// NOTE: Don't treat Asterisk * as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0x2A, rangeEnd:0x2A),
|
||||
// NOTE: Don't treat Digits 0..9 as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0x30, rangeEnd:0x39),
|
||||
// NOTE: Don't treat Copyright Symbol © as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0xA9, rangeEnd:0xA9),
|
||||
// NOTE: Don't treat Trademark Sign ® as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0xAE, rangeEnd:0xAE),
|
||||
EmojiRange(rangeStart: 0x200D, rangeEnd: 0x200D),
|
||||
EmojiRange(rangeStart: 0x203C, rangeEnd: 0x203C),
|
||||
EmojiRange(rangeStart: 0x2049, rangeEnd: 0x2049),
|
||||
EmojiRange(rangeStart: 0x20D0, rangeEnd: 0x20FF),
|
||||
EmojiRange(rangeStart: 0x2122, rangeEnd: 0x2122),
|
||||
EmojiRange(rangeStart: 0x2139, rangeEnd: 0x2139),
|
||||
EmojiRange(rangeStart: 0x2194, rangeEnd: 0x2199),
|
||||
EmojiRange(rangeStart: 0x21A9, rangeEnd: 0x21AA),
|
||||
EmojiRange(rangeStart: 0x231A, rangeEnd: 0x231B),
|
||||
EmojiRange(rangeStart: 0x2328, rangeEnd: 0x2328),
|
||||
EmojiRange(rangeStart: 0x2388, rangeEnd: 0x2388),
|
||||
EmojiRange(rangeStart: 0x23CF, rangeEnd: 0x23CF),
|
||||
EmojiRange(rangeStart: 0x23E9, rangeEnd: 0x23F3),
|
||||
EmojiRange(rangeStart: 0x23F8, rangeEnd: 0x23FA),
|
||||
EmojiRange(rangeStart: 0x24C2, rangeEnd: 0x24C2),
|
||||
EmojiRange(rangeStart: 0x25AA, rangeEnd: 0x25AB),
|
||||
EmojiRange(rangeStart: 0x25B6, rangeEnd: 0x25B6),
|
||||
EmojiRange(rangeStart: 0x25C0, rangeEnd: 0x25C0),
|
||||
EmojiRange(rangeStart: 0x25FB, rangeEnd: 0x25FE),
|
||||
EmojiRange(rangeStart: 0x2600, rangeEnd: 0x27BF),
|
||||
EmojiRange(rangeStart: 0x2934, rangeEnd: 0x2935),
|
||||
EmojiRange(rangeStart: 0x2B05, rangeEnd: 0x2B07),
|
||||
EmojiRange(rangeStart: 0x2B1B, rangeEnd: 0x2B1C),
|
||||
EmojiRange(rangeStart: 0x2B50, rangeEnd: 0x2B50),
|
||||
EmojiRange(rangeStart: 0x2B55, rangeEnd: 0x2B55),
|
||||
EmojiRange(rangeStart: 0x3030, rangeEnd: 0x3030),
|
||||
EmojiRange(rangeStart: 0x303D, rangeEnd: 0x303D),
|
||||
EmojiRange(rangeStart: 0x3297, rangeEnd: 0x3297),
|
||||
EmojiRange(rangeStart: 0x3299, rangeEnd: 0x3299),
|
||||
EmojiRange(rangeStart: 0xFE00, rangeEnd: 0xFE0F),
|
||||
EmojiRange(rangeStart: 0x1F000, rangeEnd: 0x1F0FF),
|
||||
EmojiRange(rangeStart: 0x1F10D, rangeEnd: 0x1F10F),
|
||||
EmojiRange(rangeStart: 0x1F12F, rangeEnd: 0x1F12F),
|
||||
EmojiRange(rangeStart: 0x1F16C, rangeEnd: 0x1F171),
|
||||
EmojiRange(rangeStart: 0x1F17E, rangeEnd: 0x1F17F),
|
||||
EmojiRange(rangeStart: 0x1F18E, rangeEnd: 0x1F18E),
|
||||
EmojiRange(rangeStart: 0x1F191, rangeEnd: 0x1F19A),
|
||||
EmojiRange(rangeStart: 0x1F1AD, rangeEnd: 0x1F1FF),
|
||||
EmojiRange(rangeStart: 0x1F201, rangeEnd: 0x1F20F),
|
||||
EmojiRange(rangeStart: 0x1F21A, rangeEnd: 0x1F21A),
|
||||
EmojiRange(rangeStart: 0x1F22F, rangeEnd: 0x1F22F),
|
||||
EmojiRange(rangeStart: 0x1F232, rangeEnd: 0x1F23A),
|
||||
EmojiRange(rangeStart: 0x1F23C, rangeEnd: 0x1F23F),
|
||||
EmojiRange(rangeStart: 0x1F249, rangeEnd: 0x1F64F),
|
||||
EmojiRange(rangeStart: 0x1F680, rangeEnd: 0x1F6FF),
|
||||
EmojiRange(rangeStart: 0x1F774, rangeEnd: 0x1F77F),
|
||||
EmojiRange(rangeStart: 0x1F7D5, rangeEnd: 0x1F7FF),
|
||||
EmojiRange(rangeStart: 0x1F80C, rangeEnd: 0x1F80F),
|
||||
EmojiRange(rangeStart: 0x1F848, rangeEnd: 0x1F84F),
|
||||
EmojiRange(rangeStart: 0x1F85A, rangeEnd: 0x1F85F),
|
||||
EmojiRange(rangeStart: 0x1F888, rangeEnd: 0x1F88F),
|
||||
EmojiRange(rangeStart: 0x1F8AE, rangeEnd: 0x1FFFD),
|
||||
EmojiRange(rangeStart: 0xE0020, rangeEnd: 0xE007F)
|
||||
]
|
||||
|
||||
var isEmoji: Bool {
|
||||
// Binary search
|
||||
var left: Int = 0
|
||||
var right = Int(UnicodeScalar.kEmojiRanges.count - 1)
|
||||
|
||||
while true {
|
||||
let mid = (left + right) / 2
|
||||
let midRange = UnicodeScalar.kEmojiRanges[mid]
|
||||
if value < midRange.rangeStart {
|
||||
if mid == left {
|
||||
return false
|
||||
}
|
||||
right = mid - 1
|
||||
} else if value > midRange.rangeEnd {
|
||||
if mid == right {
|
||||
return false
|
||||
}
|
||||
left = mid + 1
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isZeroWidthJoiner: Bool {
|
||||
return value == 8205
|
||||
}
|
||||
}
|
@ -1,298 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension UnicodeScalar {
|
||||
class EmojiRange {
|
||||
// rangeStart and rangeEnd are inclusive.
|
||||
let rangeStart: UInt32
|
||||
let rangeEnd: UInt32
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
init(rangeStart: UInt32, rangeEnd: UInt32) {
|
||||
self.rangeStart = rangeStart
|
||||
self.rangeEnd = rangeEnd
|
||||
}
|
||||
}
|
||||
|
||||
// From:
|
||||
// https://www.unicode.org/Public/emoji/
|
||||
// Current Version:
|
||||
// https://www.unicode.org/Public/emoji/6.0/emoji-data.txt
|
||||
//
|
||||
// These ranges can be code-generated using:
|
||||
//
|
||||
// * Scripts/emoji-data.txt
|
||||
// * Scripts/emoji_ranges.py
|
||||
static let kEmojiRanges = [
|
||||
// NOTE: Don't treat Pound Sign # as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0x23, rangeEnd:0x23),
|
||||
// NOTE: Don't treat Asterisk * as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0x2A, rangeEnd:0x2A),
|
||||
// NOTE: Don't treat Digits 0..9 as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0x30, rangeEnd:0x39),
|
||||
// NOTE: Don't treat Copyright Symbol © as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0xA9, rangeEnd:0xA9),
|
||||
// NOTE: Don't treat Trademark Sign ® as Jumbomoji.
|
||||
// EmojiRange(rangeStart:0xAE, rangeEnd:0xAE),
|
||||
EmojiRange(rangeStart: 0x200D, rangeEnd: 0x200D),
|
||||
EmojiRange(rangeStart: 0x203C, rangeEnd: 0x203C),
|
||||
EmojiRange(rangeStart: 0x2049, rangeEnd: 0x2049),
|
||||
EmojiRange(rangeStart: 0x20D0, rangeEnd: 0x20FF),
|
||||
EmojiRange(rangeStart: 0x2122, rangeEnd: 0x2122),
|
||||
EmojiRange(rangeStart: 0x2139, rangeEnd: 0x2139),
|
||||
EmojiRange(rangeStart: 0x2194, rangeEnd: 0x2199),
|
||||
EmojiRange(rangeStart: 0x21A9, rangeEnd: 0x21AA),
|
||||
EmojiRange(rangeStart: 0x231A, rangeEnd: 0x231B),
|
||||
EmojiRange(rangeStart: 0x2328, rangeEnd: 0x2328),
|
||||
EmojiRange(rangeStart: 0x2388, rangeEnd: 0x2388),
|
||||
EmojiRange(rangeStart: 0x23CF, rangeEnd: 0x23CF),
|
||||
EmojiRange(rangeStart: 0x23E9, rangeEnd: 0x23F3),
|
||||
EmojiRange(rangeStart: 0x23F8, rangeEnd: 0x23FA),
|
||||
EmojiRange(rangeStart: 0x24C2, rangeEnd: 0x24C2),
|
||||
EmojiRange(rangeStart: 0x25AA, rangeEnd: 0x25AB),
|
||||
EmojiRange(rangeStart: 0x25B6, rangeEnd: 0x25B6),
|
||||
EmojiRange(rangeStart: 0x25C0, rangeEnd: 0x25C0),
|
||||
EmojiRange(rangeStart: 0x25FB, rangeEnd: 0x25FE),
|
||||
EmojiRange(rangeStart: 0x2600, rangeEnd: 0x27BF),
|
||||
EmojiRange(rangeStart: 0x2934, rangeEnd: 0x2935),
|
||||
EmojiRange(rangeStart: 0x2B05, rangeEnd: 0x2B07),
|
||||
EmojiRange(rangeStart: 0x2B1B, rangeEnd: 0x2B1C),
|
||||
EmojiRange(rangeStart: 0x2B50, rangeEnd: 0x2B50),
|
||||
EmojiRange(rangeStart: 0x2B55, rangeEnd: 0x2B55),
|
||||
EmojiRange(rangeStart: 0x3030, rangeEnd: 0x3030),
|
||||
EmojiRange(rangeStart: 0x303D, rangeEnd: 0x303D),
|
||||
EmojiRange(rangeStart: 0x3297, rangeEnd: 0x3297),
|
||||
EmojiRange(rangeStart: 0x3299, rangeEnd: 0x3299),
|
||||
EmojiRange(rangeStart: 0xFE00, rangeEnd: 0xFE0F),
|
||||
EmojiRange(rangeStart: 0x1F000, rangeEnd: 0x1F0FF),
|
||||
EmojiRange(rangeStart: 0x1F10D, rangeEnd: 0x1F10F),
|
||||
EmojiRange(rangeStart: 0x1F12F, rangeEnd: 0x1F12F),
|
||||
EmojiRange(rangeStart: 0x1F16C, rangeEnd: 0x1F171),
|
||||
EmojiRange(rangeStart: 0x1F17E, rangeEnd: 0x1F17F),
|
||||
EmojiRange(rangeStart: 0x1F18E, rangeEnd: 0x1F18E),
|
||||
EmojiRange(rangeStart: 0x1F191, rangeEnd: 0x1F19A),
|
||||
EmojiRange(rangeStart: 0x1F1AD, rangeEnd: 0x1F1FF),
|
||||
EmojiRange(rangeStart: 0x1F201, rangeEnd: 0x1F20F),
|
||||
EmojiRange(rangeStart: 0x1F21A, rangeEnd: 0x1F21A),
|
||||
EmojiRange(rangeStart: 0x1F22F, rangeEnd: 0x1F22F),
|
||||
EmojiRange(rangeStart: 0x1F232, rangeEnd: 0x1F23A),
|
||||
EmojiRange(rangeStart: 0x1F23C, rangeEnd: 0x1F23F),
|
||||
EmojiRange(rangeStart: 0x1F249, rangeEnd: 0x1F64F),
|
||||
EmojiRange(rangeStart: 0x1F680, rangeEnd: 0x1F6FF),
|
||||
EmojiRange(rangeStart: 0x1F774, rangeEnd: 0x1F77F),
|
||||
EmojiRange(rangeStart: 0x1F7D5, rangeEnd: 0x1F7FF),
|
||||
EmojiRange(rangeStart: 0x1F80C, rangeEnd: 0x1F80F),
|
||||
EmojiRange(rangeStart: 0x1F848, rangeEnd: 0x1F84F),
|
||||
EmojiRange(rangeStart: 0x1F85A, rangeEnd: 0x1F85F),
|
||||
EmojiRange(rangeStart: 0x1F888, rangeEnd: 0x1F88F),
|
||||
EmojiRange(rangeStart: 0x1F8AE, rangeEnd: 0x1FFFD),
|
||||
EmojiRange(rangeStart: 0xE0020, rangeEnd: 0xE007F)
|
||||
]
|
||||
|
||||
var isEmoji: Bool {
|
||||
|
||||
// Binary search.
|
||||
var left: Int = 0
|
||||
var right = Int(UnicodeScalar.kEmojiRanges.count - 1)
|
||||
while true {
|
||||
let mid = (left + right) / 2
|
||||
let midRange = UnicodeScalar.kEmojiRanges[mid]
|
||||
if value < midRange.rangeStart {
|
||||
if mid == left {
|
||||
return false
|
||||
}
|
||||
right = mid - 1
|
||||
} else if value > midRange.rangeEnd {
|
||||
if mid == right {
|
||||
return false
|
||||
}
|
||||
left = mid + 1
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isZeroWidthJoiner: Bool {
|
||||
|
||||
return value == 8205
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
var glyphCount: Int {
|
||||
let richText = NSAttributedString(string: self)
|
||||
let line = CTLineCreateWithAttributedString(richText)
|
||||
return CTLineGetGlyphCount(line)
|
||||
}
|
||||
|
||||
var isSingleEmoji: Bool {
|
||||
return glyphCount == 1 && containsEmoji
|
||||
}
|
||||
|
||||
var containsEmoji: Bool {
|
||||
return unicodeScalars.contains { $0.isEmoji }
|
||||
}
|
||||
|
||||
var containsOnlyEmoji: Bool {
|
||||
return !isEmpty
|
||||
&& !unicodeScalars.contains(where: {
|
||||
!$0.isEmoji
|
||||
&& !$0.isZeroWidthJoiner
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc public class DisplayableText: NSObject {
|
||||
|
||||
@objc public let fullText: String
|
||||
@objc public let displayText: String
|
||||
@objc public let isTextTruncated: Bool
|
||||
@objc public let jumbomojiCount: UInt
|
||||
|
||||
@objc
|
||||
public static let kMaxJumbomojiCount: UInt = 5
|
||||
// This value is a bit arbitrary since we don't need to be 100% correct about
|
||||
// rendering "Jumbomoji". It allows us to place an upper bound on worst-case
|
||||
// performacne.
|
||||
@objc
|
||||
public static let kMaxCharactersPerEmojiCount: UInt = 10
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
@objc
|
||||
public init(fullText: String, displayText: String, isTextTruncated: Bool) {
|
||||
self.fullText = fullText
|
||||
self.displayText = displayText
|
||||
self.isTextTruncated = isTextTruncated
|
||||
self.jumbomojiCount = DisplayableText.jumbomojiCount(in: fullText)
|
||||
}
|
||||
|
||||
// MARK: Emoji
|
||||
|
||||
// If the string is...
|
||||
//
|
||||
// * Non-empty
|
||||
// * Only contains emoji
|
||||
// * Contains <= kMaxJumbomojiCount emoji
|
||||
//
|
||||
// ...return the number of emoji (to be treated as "Jumbomoji") in the string.
|
||||
private class func jumbomojiCount(in string: String) -> UInt {
|
||||
if string == "" {
|
||||
return 0
|
||||
}
|
||||
if string.count > Int(kMaxJumbomojiCount * kMaxCharactersPerEmojiCount) {
|
||||
return 0
|
||||
}
|
||||
guard string.containsOnlyEmoji else {
|
||||
return 0
|
||||
}
|
||||
let emojiCount = string.glyphCount
|
||||
if UInt(emojiCount) > kMaxJumbomojiCount {
|
||||
return 0
|
||||
}
|
||||
return UInt(emojiCount)
|
||||
}
|
||||
|
||||
// For perf we use a static linkDetector. It doesn't change and building DataDetectors is
|
||||
// surprisingly expensive. This should be fine, since NSDataDetector is an NSRegularExpression
|
||||
// and NSRegularExpressions are thread safe.
|
||||
private static let linkDetector: NSDataDetector? = {
|
||||
return try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
||||
}()
|
||||
|
||||
private static let hostRegex: NSRegularExpression? = {
|
||||
let pattern = "^(?:https?:\\/\\/)?([^:\\/\\s]+)(.*)?$"
|
||||
return try? NSRegularExpression(pattern: pattern)
|
||||
}()
|
||||
|
||||
@objc
|
||||
public lazy var shouldAllowLinkification: Bool = {
|
||||
guard let linkDetector: NSDataDetector = DisplayableText.linkDetector else {
|
||||
owsFailDebug("linkDetector was unexpectedly nil")
|
||||
return false
|
||||
}
|
||||
|
||||
func isValidLink(linkText: String) -> Bool {
|
||||
guard let hostRegex = DisplayableText.hostRegex else {
|
||||
owsFailDebug("hostRegex was unexpectedly nil")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let hostText = hostRegex.parseFirstMatch(inText: linkText) else {
|
||||
owsFailDebug("hostText was unexpectedly nil")
|
||||
return false
|
||||
}
|
||||
|
||||
let strippedHost = hostText.replacingOccurrences(of: ".", with: "") as NSString
|
||||
|
||||
if strippedHost.isOnlyASCII {
|
||||
return true
|
||||
} else if strippedHost.hasAnyASCII {
|
||||
// mix of ascii and non-ascii is invalid
|
||||
return false
|
||||
} else {
|
||||
// IDN
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for match in linkDetector.matches(in: fullText, options: [], range: NSRange(location: 0, length: fullText.utf16.count)) {
|
||||
guard let matchURL: URL = match.url else {
|
||||
continue
|
||||
}
|
||||
|
||||
// We extract the exact text from the `fullText` rather than use match.url.host
|
||||
// because match.url.host actually escapes non-ascii domains into puny-code.
|
||||
//
|
||||
// But what we really want is to check the text which will ultimately be presented to
|
||||
// the user.
|
||||
let rawTextOfMatch = (fullText as NSString).substring(with: match.range)
|
||||
guard isValidLink(linkText: rawTextOfMatch) else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}()
|
||||
|
||||
// MARK: Filter Methods
|
||||
|
||||
@objc
|
||||
public class func filterNotificationText(_ text: String?) -> String? {
|
||||
guard let text = text?.filterStringForDisplay() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// iOS strips anything that looks like a printf formatting character from
|
||||
// the notification body, so if we want to dispay a literal "%" in a notification
|
||||
// it must be escaped.
|
||||
// see https://developer.apple.com/documentation/uikit/uilocalnotification/1616646-alertbody
|
||||
// for more details.
|
||||
return text.replacingOccurrences(of: "%", with: "%%")
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func displayableText(_ rawText: String) -> DisplayableText {
|
||||
// Only show up to N characters of text.
|
||||
let kMaxTextDisplayLength = 512
|
||||
let fullText = rawText.filterStringForDisplay()
|
||||
var isTextTruncated = false
|
||||
var displayText = fullText
|
||||
if displayText.count > kMaxTextDisplayLength {
|
||||
// Trim whitespace before _AND_ after slicing the snipper from the string.
|
||||
let snippet = String(displayText.prefix(kMaxTextDisplayLength)).ows_stripped()
|
||||
displayText = String(format: NSLocalizedString("OVERSIZE_TEXT_DISPLAY_FORMAT", comment:
|
||||
"A display format for oversize text messages."),
|
||||
snippet)
|
||||
isTextTruncated = true
|
||||
}
|
||||
|
||||
let displayableText = DisplayableText(fullText: fullText, displayText: displayText, isTextTruncated: isTextTruncated)
|
||||
return displayableText
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue