mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Refactoring
This commit is contained in:
376
submodules/TelegramCore/Sources/Utils/JSON.swift
Normal file
376
submodules/TelegramCore/Sources/Utils/JSON.swift
Normal file
@@ -0,0 +1,376 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
extension JSON {
|
||||
private init?(_ object: Any) {
|
||||
if let object = object as? JSONValue {
|
||||
self = object.jsonValue
|
||||
} else if let dict = object as? [String: Any] {
|
||||
var values: [String: JSON] = [:]
|
||||
for (key, value) in dict {
|
||||
if let v = JSON(value) {
|
||||
values[key] = v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self = .dictionary(values)
|
||||
} else if let array = object as? [Any] {
|
||||
var values: [JSON] = []
|
||||
for value in array {
|
||||
if let v = JSON(value) {
|
||||
values.append(v)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self = .array(values)
|
||||
} else if let value = object as? String {
|
||||
self = .string(value)
|
||||
} else if let value = object as? Int {
|
||||
self = .number(Double(value))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public init?(data: Data) {
|
||||
if let object = try? JSONSerialization.jsonObject(with: data, options: []) {
|
||||
self.init(object)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public init?(string: String) {
|
||||
if let data = string.data(using: .utf8) {
|
||||
self.init(data: data)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON: Collection {
|
||||
public var startIndex: Index {
|
||||
switch self {
|
||||
case let .array(value):
|
||||
return .array(value.startIndex)
|
||||
case let .dictionary(value):
|
||||
return .dictionary(value.startIndex)
|
||||
default:
|
||||
return .null
|
||||
}
|
||||
}
|
||||
|
||||
public var endIndex: Index {
|
||||
switch self {
|
||||
case let .array(value):
|
||||
return .array(value.endIndex)
|
||||
case let .dictionary(value):
|
||||
return .dictionary(value.endIndex)
|
||||
default:
|
||||
return .null
|
||||
}
|
||||
}
|
||||
|
||||
public func index(after i: Index) -> Index {
|
||||
switch (i, self) {
|
||||
case let (.array(index), .array(value)):
|
||||
return .array(value.index(after: index))
|
||||
case let (.dictionary(index), .dictionary(value)):
|
||||
return .dictionary(value.index(after: index))
|
||||
default:
|
||||
return .null
|
||||
}
|
||||
}
|
||||
|
||||
public subscript (position: Index) -> (String, JSON) {
|
||||
switch (position, self) {
|
||||
case let (.array(index), .array(value)):
|
||||
return (String(index), value[index])
|
||||
case let (.dictionary(index), .dictionary(value)):
|
||||
let (key, value) = value[index]
|
||||
return (key, value)
|
||||
default:
|
||||
return ("", .null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum JSONKey {
|
||||
case index(Int)
|
||||
case key(String)
|
||||
}
|
||||
|
||||
public protocol JSONSubscriptType {
|
||||
var jsonKey: JSONKey { get }
|
||||
}
|
||||
|
||||
extension Int: JSONSubscriptType {
|
||||
public var jsonKey: JSONKey {
|
||||
return .index(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension String: JSONSubscriptType {
|
||||
public var jsonKey: JSONKey {
|
||||
return .key(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON {
|
||||
fileprivate var value: JSONElement {
|
||||
get {
|
||||
switch self {
|
||||
case .null:
|
||||
return 0
|
||||
case let .number(value):
|
||||
return value
|
||||
case let .string(value):
|
||||
return value
|
||||
case let .bool(value):
|
||||
return value
|
||||
case let .array(values):
|
||||
var array: [JSONElement] = []
|
||||
for value in values {
|
||||
array.append(value.value)
|
||||
}
|
||||
return array
|
||||
case let .dictionary(values):
|
||||
var dictionary: [String: JSONElement] = [:]
|
||||
for (key, value) in values {
|
||||
dictionary[key] = value.value
|
||||
}
|
||||
return dictionary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON {
|
||||
public subscript(key: JSONSubscriptType) -> JSONElement? {
|
||||
get {
|
||||
switch (key.jsonKey, self) {
|
||||
case let (.index(index), .array(value)):
|
||||
if value.indices.contains(index) {
|
||||
return value[index].value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let (.key(key), .dictionary(value)):
|
||||
if let value = value[key] {
|
||||
return value.value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON: ExpressibleByDictionaryLiteral {
|
||||
public init(dictionaryLiteral elements: (String, Any)...) {
|
||||
self = .dictionary(elements.reduce([String: JSON]()) { (dictionary, element) in
|
||||
var dictionary = dictionary
|
||||
if let value = JSON(element.1) {
|
||||
dictionary[element.0] = value
|
||||
}
|
||||
return dictionary
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension JSON: ExpressibleByArrayLiteral {
|
||||
public init(arrayLiteral elements: Any...) {
|
||||
self = .array(elements.compactMap { JSON($0) })
|
||||
}
|
||||
}
|
||||
|
||||
public protocol JSONElement {}
|
||||
private protocol JSONValue {
|
||||
var jsonValue: JSON { get }
|
||||
}
|
||||
|
||||
extension Int: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Int8: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Int16: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Int32: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Int64: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt8: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt16: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt32: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension UInt64: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Double: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .number(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension String: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .string(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Bool: JSONElement, JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .bool(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array: JSONElement where Element == JSONElement {
|
||||
}
|
||||
|
||||
extension Array: JSONValue where Element == JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .array(self.map { $0.jsonValue })
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary: JSONElement where Key == String, Value == JSONElement {
|
||||
}
|
||||
|
||||
extension Dictionary: JSONValue where Key == String, Value == JSONValue {
|
||||
var jsonValue: JSON {
|
||||
return .dictionary(self.mapValues { $0.jsonValue })
|
||||
}
|
||||
}
|
||||
|
||||
private extension Bool {
|
||||
init(apiBool: Api.Bool) {
|
||||
switch apiBool {
|
||||
case .boolTrue:
|
||||
self.init(true)
|
||||
case .boolFalse:
|
||||
self.init(false)
|
||||
}
|
||||
}
|
||||
|
||||
var apiBool: Api.Bool {
|
||||
if self {
|
||||
return .boolTrue
|
||||
} else {
|
||||
return .boolFalse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension JSON {
|
||||
private init?(apiJson: Api.JSONValue, root: Bool) {
|
||||
switch (apiJson, root) {
|
||||
case (.jsonNull, false):
|
||||
self = .null
|
||||
case let (.jsonNumber(value), false):
|
||||
self = .number(value)
|
||||
case let (.jsonString(value), false):
|
||||
self = .string(value)
|
||||
case let (.jsonBool(value), false):
|
||||
self = .bool(Bool(apiBool: value))
|
||||
case let (.jsonArray(value), _):
|
||||
self = .array(value.compactMap { JSON(apiJson: $0, root: false) })
|
||||
case let (.jsonObject(value), _):
|
||||
self = .dictionary(value.reduce([String: JSON]()) { dictionary, value in
|
||||
var dictionary = dictionary
|
||||
switch value {
|
||||
case let .jsonObjectValue(key, value):
|
||||
if let value = JSON(apiJson: value, root: false) {
|
||||
dictionary[key] = value
|
||||
}
|
||||
}
|
||||
return dictionary
|
||||
})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
init?(apiJson: Api.JSONValue) {
|
||||
self.init(apiJson: apiJson, root: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func apiJson(_ json: JSON, root: Bool) -> Api.JSONValue? {
|
||||
switch (json, root) {
|
||||
case (.null, false):
|
||||
return .jsonNull
|
||||
case let (.number(value), false):
|
||||
return .jsonNumber(value: value)
|
||||
case let (.string(value), false):
|
||||
return .jsonString(value: value)
|
||||
case let (.bool(value), false):
|
||||
return .jsonBool(value: value.apiBool)
|
||||
case let (.array(value), _):
|
||||
return .jsonArray(value: value.compactMap { apiJson($0, root: false) })
|
||||
case let (.dictionary(value), _):
|
||||
return .jsonObject(value: value.reduce([Api.JSONObjectValue]()) { objectValues, keyAndValue in
|
||||
var objectValues = objectValues
|
||||
if let value = apiJson(keyAndValue.value, root: false) {
|
||||
objectValues.append(.jsonObjectValue(key: keyAndValue.key, value: value))
|
||||
}
|
||||
return objectValues
|
||||
})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func apiJson(_ json: JSON) -> Api.JSONValue? {
|
||||
return apiJson(json, root: true)
|
||||
}
|
||||
462
submodules/TelegramCore/Sources/Utils/Log.swift
Normal file
462
submodules/TelegramCore/Sources/Utils/Log.swift
Normal file
@@ -0,0 +1,462 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
import NetworkLogging
|
||||
|
||||
private let queue = DispatchQueue(label: "org.telegram.Telegram.trace", qos: .utility)
|
||||
|
||||
public func trace2(_ what: @autoclosure() -> String) {
|
||||
let string = what()
|
||||
var rawTime = time_t()
|
||||
time(&rawTime)
|
||||
var timeinfo = tm()
|
||||
localtime_r(&rawTime, &timeinfo)
|
||||
|
||||
var curTime = timeval()
|
||||
gettimeofday(&curTime, nil)
|
||||
let milliseconds = curTime.tv_usec / 1000
|
||||
|
||||
//queue.async {
|
||||
let result = String(format: "%d-%d-%d %02d:%02d:%03d %@", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(milliseconds), string])
|
||||
print(result)
|
||||
//}
|
||||
}
|
||||
|
||||
public func trace1(_ domain: String, what: @autoclosure() -> String) {
|
||||
let string = what()
|
||||
var rawTime = time_t()
|
||||
time(&rawTime)
|
||||
var timeinfo = tm()
|
||||
localtime_r(&rawTime, &timeinfo)
|
||||
|
||||
var curTime = timeval()
|
||||
gettimeofday(&curTime, nil)
|
||||
let seconds = curTime.tv_sec
|
||||
let milliseconds = curTime.tv_usec / 1000
|
||||
|
||||
queue.async {
|
||||
let result = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [domain, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(seconds), Int(milliseconds), string])
|
||||
|
||||
print(result)
|
||||
}
|
||||
}
|
||||
|
||||
public func registerLoggingFunctions() {
|
||||
setBridgingTraceFunction({ domain, what in
|
||||
if let what = what {
|
||||
if let domain = domain {
|
||||
Logger.shared.log(domain, what as String)
|
||||
} else {
|
||||
Logger.shared.log("", what as String)
|
||||
}
|
||||
}
|
||||
})
|
||||
setBridgingShortTraceFunction({ domain, what in
|
||||
if let what = what {
|
||||
if let domain = domain {
|
||||
Logger.shared.shortLog(domain, what as String)
|
||||
} else {
|
||||
Logger.shared.shortLog("", what as String)
|
||||
}
|
||||
}
|
||||
})
|
||||
setTelegramApiLogger({ what in
|
||||
Logger.shared.log("Api", what as String)
|
||||
Logger.shared.shortLog("Api", what as String)
|
||||
})
|
||||
}
|
||||
|
||||
private var sharedLogger: Logger?
|
||||
|
||||
private let binaryEventMarker: UInt64 = 0xcadebabef00dcafe
|
||||
|
||||
public final class Logger {
|
||||
private let queue = Queue(name: "org.telegram.Telegram.log", qos: .utility)
|
||||
private let maxLength: Int = 2 * 1024 * 1024
|
||||
private let maxShortLength: Int = 1 * 1024 * 1024
|
||||
private let maxFiles: Int = 20
|
||||
|
||||
private let rootPath: String
|
||||
private let basePath: String
|
||||
private var file: (ManagedFile, Int)?
|
||||
private var shortFile: (ManagedFile, Int)?
|
||||
|
||||
public var logToFile: Bool = true {
|
||||
didSet {
|
||||
let oldEnabled = self.logToConsole || oldValue
|
||||
let newEnabled = self.logToConsole || self.logToFile
|
||||
if oldEnabled != newEnabled {
|
||||
NetworkSetLoggingEnabled(newEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
public var logToConsole: Bool = true {
|
||||
didSet {
|
||||
let oldEnabled = self.logToFile || oldValue
|
||||
let newEnabled = self.logToFile || self.logToConsole
|
||||
if oldEnabled != newEnabled {
|
||||
NetworkSetLoggingEnabled(newEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
public var redactSensitiveData: Bool = true
|
||||
|
||||
public static func setSharedLogger(_ logger: Logger) {
|
||||
sharedLogger = logger
|
||||
setPostboxLogger({ s in
|
||||
Logger.shared.log("Postbox", s)
|
||||
Logger.shared.shortLog("Postbox", s)
|
||||
})
|
||||
}
|
||||
|
||||
public static var shared: Logger {
|
||||
if let sharedLogger = sharedLogger {
|
||||
return sharedLogger
|
||||
} else {
|
||||
assertionFailure()
|
||||
let tempLogger = Logger(rootPath: "", basePath: "")
|
||||
tempLogger.logToFile = false
|
||||
tempLogger.logToConsole = false
|
||||
return tempLogger
|
||||
}
|
||||
}
|
||||
|
||||
public init(rootPath: String, basePath: String) {
|
||||
self.rootPath = rootPath
|
||||
self.basePath = basePath
|
||||
}
|
||||
|
||||
public func collectLogs(prefix: String? = nil) -> Signal<[(String, String)], NoError> {
|
||||
return Signal { subscriber in
|
||||
self.queue.async {
|
||||
let logsPath: String
|
||||
if let prefix = prefix {
|
||||
logsPath = self.rootPath + prefix
|
||||
} else {
|
||||
logsPath = self.basePath
|
||||
}
|
||||
|
||||
var result: [(Date, String, String)] = []
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logsPath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
|
||||
for url in files {
|
||||
if url.lastPathComponent.hasPrefix("log-") {
|
||||
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
|
||||
result.append((creationDate, url.lastPathComponent, url.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.sort(by: { $0.0 < $1.0 })
|
||||
subscriber.putNext(result.map { ($0.1, $0.2) })
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public func collectShortLogFiles() -> Signal<[(String, String)], NoError> {
|
||||
return Signal { subscriber in
|
||||
self.queue.async {
|
||||
var result: [(Date, String, String)] = []
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
|
||||
for url in files {
|
||||
if url.lastPathComponent.hasPrefix("critlog-") {
|
||||
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
|
||||
result.append((creationDate, url.lastPathComponent, url.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.sort(by: { $0.0 < $1.0 })
|
||||
subscriber.putNext(result.map { ($0.1, $0.2) })
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public func collectShortLog() -> Signal<[(Double, String)], NoError> {
|
||||
return Signal { subscriber in
|
||||
self.queue.async {
|
||||
var result: [(Date, String, String)] = []
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
|
||||
for url in files {
|
||||
if url.lastPathComponent.hasPrefix("critlog-") {
|
||||
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {
|
||||
result.append((creationDate, url.lastPathComponent, url.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.sort(by: { $0.0 < $1.0 })
|
||||
|
||||
var events: [(Double, String)] = []
|
||||
for (_, _, filePath) in result.reversed() {
|
||||
var fileEvents: [(Double, String)] = []
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: filePath), options: .mappedRead) {
|
||||
let dataLength = data.count
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<Int8>) -> Void in
|
||||
var offset = 0
|
||||
while offset < dataLength {
|
||||
let remainingLength = dataLength - offset
|
||||
if remainingLength < 8 + 4 + 8 {
|
||||
break
|
||||
}
|
||||
var maybeMarker: UInt64 = 0
|
||||
memcpy(&maybeMarker, bytes.advanced(by: offset), 8)
|
||||
if maybeMarker == binaryEventMarker {
|
||||
var length: Int32 = 0
|
||||
memcpy(&length, bytes.advanced(by: offset + 8), 4)
|
||||
if length < 0 || length > dataLength - offset {
|
||||
offset += 1
|
||||
} else {
|
||||
var timestamp: Double = 0.0
|
||||
memcpy(×tamp, bytes.advanced(by: offset + 8 + 4), 8)
|
||||
let eventStringData = Data(bytes: bytes.advanced(by: offset + 8 + 4 + 8), count: Int(length - 8))
|
||||
if let string = String(data: eventStringData, encoding: .utf8) {
|
||||
fileEvents.append((timestamp, string))
|
||||
}
|
||||
offset += 8 + 4 + Int(length)
|
||||
}
|
||||
} else {
|
||||
offset += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events.append(contentsOf: fileEvents.reversed())
|
||||
if events.count > 1000 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if events.count > 1000 {
|
||||
events.removeLast(events.count - 1000)
|
||||
}
|
||||
subscriber.putNext(events)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public func log(_ tag: String, _ what: @autoclosure () -> String) {
|
||||
if !self.logToFile && !self.logToConsole {
|
||||
return
|
||||
}
|
||||
|
||||
let string = what()
|
||||
|
||||
var rawTime = time_t()
|
||||
time(&rawTime)
|
||||
var timeinfo = tm()
|
||||
localtime_r(&rawTime, &timeinfo)
|
||||
|
||||
var curTime = timeval()
|
||||
gettimeofday(&curTime, nil)
|
||||
let milliseconds = curTime.tv_usec / 1000
|
||||
|
||||
var consoleContent: String?
|
||||
if self.logToConsole {
|
||||
let content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string])
|
||||
consoleContent = content
|
||||
print(content)
|
||||
}
|
||||
|
||||
if self.logToFile {
|
||||
self.queue.async {
|
||||
let content: String
|
||||
if let consoleContent = consoleContent {
|
||||
content = consoleContent
|
||||
} else {
|
||||
content = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%03d %@", arguments: [tag, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds), string])
|
||||
}
|
||||
|
||||
var currentFile: ManagedFile?
|
||||
var openNew = false
|
||||
if let (file, length) = self.file {
|
||||
if length >= self.maxLength {
|
||||
self.file = nil
|
||||
openNew = true
|
||||
} else {
|
||||
currentFile = file
|
||||
}
|
||||
} else {
|
||||
openNew = true
|
||||
}
|
||||
if openNew {
|
||||
let _ = try? FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
var createNew = false
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
|
||||
var minCreationDate: (Date, URL)?
|
||||
var maxCreationDate: (Date, URL)?
|
||||
var count = 0
|
||||
for url in files {
|
||||
if url.lastPathComponent.hasPrefix("log-") {
|
||||
if let values = try? url.resourceValues(forKeys: Set([URLResourceKey.creationDateKey])), let creationDate = values.creationDate {
|
||||
count += 1
|
||||
if minCreationDate == nil || minCreationDate!.0 > creationDate {
|
||||
minCreationDate = (creationDate, url)
|
||||
}
|
||||
if maxCreationDate == nil || maxCreationDate!.0 < creationDate {
|
||||
maxCreationDate = (creationDate, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (_, url) = minCreationDate, count >= self.maxFiles {
|
||||
let _ = try? FileManager.default.removeItem(at: url)
|
||||
}
|
||||
if let (_, url) = maxCreationDate {
|
||||
var value = stat()
|
||||
if stat(url.path, &value) == 0 && Int(value.st_size) < self.maxLength {
|
||||
if let file = ManagedFile(queue: self.queue, path: url.path, mode: .append) {
|
||||
self.file = (file, Int(value.st_size))
|
||||
currentFile = file
|
||||
}
|
||||
} else {
|
||||
createNew = true
|
||||
}
|
||||
} else {
|
||||
createNew = true
|
||||
}
|
||||
}
|
||||
|
||||
if createNew {
|
||||
let fileName = String(format: "log-%d-%d-%d_%02d-%02d-%02d.%03d.txt", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds)])
|
||||
|
||||
let path = self.basePath + "/" + fileName
|
||||
|
||||
if let file = ManagedFile(queue: self.queue, path: path, mode: .append) {
|
||||
self.file = (file, 0)
|
||||
currentFile = file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentFile = currentFile {
|
||||
if let data = content.data(using: .utf8) {
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
let _ = currentFile.write(bytes, count: data.count)
|
||||
}
|
||||
var newline: UInt8 = 0x0a
|
||||
let _ = currentFile.write(&newline, count: 1)
|
||||
if let file = self.file {
|
||||
self.file = (file.0, file.1 + data.count + 1)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func shortLog(_ tag: String, _ what: @autoclosure () -> String) {
|
||||
let string = what()
|
||||
|
||||
var rawTime = time_t()
|
||||
time(&rawTime)
|
||||
var timeinfo = tm()
|
||||
localtime_r(&rawTime, &timeinfo)
|
||||
|
||||
var curTime = timeval()
|
||||
gettimeofday(&curTime, nil)
|
||||
let milliseconds = curTime.tv_usec / 1000
|
||||
|
||||
let timestamp: Double = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970
|
||||
|
||||
self.queue.async {
|
||||
let content = WriteBuffer()
|
||||
var binaryEventMarkerValue: UInt64 = binaryEventMarker
|
||||
content.write(&binaryEventMarkerValue, offset: 0, length: 8)
|
||||
let stringData = string.data(using: .utf8) ?? Data()
|
||||
var lengthValue: Int32 = 8 + Int32(stringData.count)
|
||||
content.write(&lengthValue, offset: 0, length: 4)
|
||||
var timestampValue: Double = timestamp
|
||||
content.write(×tampValue, offset: 0, length: 8)
|
||||
content.write(stringData)
|
||||
let contentData = content.makeData()
|
||||
|
||||
var currentFile: ManagedFile?
|
||||
var openNew = false
|
||||
if let (file, length) = self.shortFile {
|
||||
if length >= self.maxShortLength {
|
||||
self.shortFile = nil
|
||||
openNew = true
|
||||
} else {
|
||||
currentFile = file
|
||||
}
|
||||
} else {
|
||||
openNew = true
|
||||
}
|
||||
if openNew {
|
||||
let _ = try? FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
var createNew = false
|
||||
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
|
||||
var minCreationDate: (Date, URL)?
|
||||
var maxCreationDate: (Date, URL)?
|
||||
var count = 0
|
||||
for url in files {
|
||||
if url.lastPathComponent.hasPrefix("critlog-") {
|
||||
if let values = try? url.resourceValues(forKeys: Set([URLResourceKey.creationDateKey])), let creationDate = values.creationDate {
|
||||
count += 1
|
||||
if minCreationDate == nil || minCreationDate!.0 > creationDate {
|
||||
minCreationDate = (creationDate, url)
|
||||
}
|
||||
if maxCreationDate == nil || maxCreationDate!.0 < creationDate {
|
||||
maxCreationDate = (creationDate, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (_, url) = minCreationDate, count >= self.maxFiles {
|
||||
let _ = try? FileManager.default.removeItem(at: url)
|
||||
}
|
||||
if let (_, url) = maxCreationDate {
|
||||
var value = stat()
|
||||
if stat(url.path, &value) == 0 && Int(value.st_size) < self.maxShortLength {
|
||||
if let file = ManagedFile(queue: self.queue, path: url.path, mode: .append) {
|
||||
self.shortFile = (file, Int(value.st_size))
|
||||
currentFile = file
|
||||
}
|
||||
} else {
|
||||
createNew = true
|
||||
}
|
||||
} else {
|
||||
createNew = true
|
||||
}
|
||||
}
|
||||
|
||||
if createNew {
|
||||
let fileName = String(format: "critlog-%d-%d-%d_%02d-%02d-%02d.%03d.txt", arguments: [Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), Int(timeinfo.tm_sec), Int(milliseconds)])
|
||||
|
||||
let path = self.basePath + "/" + fileName
|
||||
|
||||
if let file = ManagedFile(queue: self.queue, path: path, mode: .append) {
|
||||
self.shortFile = (file, 0)
|
||||
currentFile = file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let currentFile = currentFile {
|
||||
let contentDataCount = contentData.count
|
||||
contentData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
let _ = currentFile.write(bytes, count: contentDataCount)
|
||||
}
|
||||
if let shortFile = self.shortFile {
|
||||
self.shortFile = (shortFile.0, shortFile.1 + contentDataCount)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
|
||||
public extension MemoryBuffer {
|
||||
convenience init(_ buffer: Buffer) {
|
||||
let memory = malloc(Int(buffer.size))!
|
||||
memcpy(memory, buffer.data, Int(buffer.size))
|
||||
self.init(memory: memory, capacity: Int(buffer.size), length: Int(buffer.size), freeWhenDone: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension Buffer {
|
||||
convenience init(bufferNoCopy: MemoryBuffer) {
|
||||
self.init(memory: bufferNoCopy.memory, size: bufferNoCopy.length, capacity: bufferNoCopy.length, freeWhenDone: false)
|
||||
}
|
||||
|
||||
convenience init(buffer: MemoryBuffer) {
|
||||
let memory = malloc(buffer.length)!
|
||||
memcpy(memory, buffer.memory, buffer.length)
|
||||
self.init(memory: memory, size: buffer.length, capacity: buffer.length, freeWhenDone: true)
|
||||
}
|
||||
}
|
||||
294
submodules/TelegramCore/Sources/Utils/MessageUtils.swift
Normal file
294
submodules/TelegramCore/Sources/Utils/MessageUtils.swift
Normal file
@@ -0,0 +1,294 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
|
||||
import SyncCore
|
||||
|
||||
public extension MessageFlags {
|
||||
var isSending: Bool {
|
||||
return (self.contains(.Unsent) || self.contains(.Sending)) && !self.contains(.Failed)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
var visibleButtonKeyboardMarkup: ReplyMarkupMessageAttribute? {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? ReplyMarkupMessageAttribute {
|
||||
if !attribute.flags.contains(.inline) && !attribute.rows.isEmpty {
|
||||
if attribute.flags.contains(.personal) {
|
||||
if !personal {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var visibleReplyMarkupPlaceholder: String? {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? ReplyMarkupMessageAttribute {
|
||||
if !attribute.flags.contains(.inline) {
|
||||
if attribute.flags.contains(.personal) {
|
||||
if !personal {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return attribute.placeholder
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var muted: Bool {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? NotificationInfoMessageAttribute {
|
||||
return attribute.flags.contains(.muted)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var personal: Bool {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? NotificationInfoMessageAttribute {
|
||||
return attribute.flags.contains(.personal)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var requestsSetupReply: Bool {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? ReplyMarkupMessageAttribute {
|
||||
if !attribute.flags.contains(.inline) {
|
||||
if attribute.flags.contains(.personal) {
|
||||
if !personal {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return attribute.flags.contains(.setupReply)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isScam: Bool {
|
||||
if let author = self.author, author.isScam {
|
||||
return true
|
||||
}
|
||||
if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isScam {
|
||||
return true
|
||||
}
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isScam {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var isFake: Bool {
|
||||
if let author = self.author, author.isFake {
|
||||
return true
|
||||
}
|
||||
if let forwardAuthor = self.forwardInfo?.author, forwardAuthor.isFake {
|
||||
return true
|
||||
}
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? InlineBotMessageAttribute, let peerId = attribute.peerId, let bot = self.peers[peerId] as? TelegramUser, bot.isFake {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var sourceReference: SourceReferenceMessageAttribute? {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
return attribute
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var effectiveAuthor: Peer? {
|
||||
if let forwardInfo = self.forwardInfo, let sourceReference = self.sourceReference, forwardInfo.author?.id == sourceReference.messageId.peerId {
|
||||
if let peer = self.peers[sourceReference.messageId.peerId] {
|
||||
return peer
|
||||
}
|
||||
} else if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported), let author = forwardInfo.author {
|
||||
return author
|
||||
}
|
||||
return self.author
|
||||
}
|
||||
}
|
||||
|
||||
func messagesIdsGroupedByPeerId(_ ids: Set<MessageId>) -> [PeerId: [MessageId]] {
|
||||
var dict: [PeerId: [MessageId]] = [:]
|
||||
|
||||
for id in ids {
|
||||
let peerId = id.peerId
|
||||
if dict[peerId] == nil {
|
||||
dict[peerId] = [id]
|
||||
} else {
|
||||
dict[peerId]!.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
func messagesIdsGroupedByPeerId(_ ids: [MessageId]) -> [PeerId: [MessageId]] {
|
||||
var dict: [PeerId: [MessageId]] = [:]
|
||||
|
||||
for id in ids {
|
||||
let peerId = id.peerId
|
||||
if dict[peerId] == nil {
|
||||
dict[peerId] = [id]
|
||||
} else {
|
||||
dict[peerId]!.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
return dict
|
||||
}
|
||||
|
||||
func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Message? {
|
||||
guard case let .Id(id) = message.id else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var messagePeers = SimpleDictionary<PeerId, Peer>()
|
||||
|
||||
var author: Peer?
|
||||
if let authorId = message.authorId {
|
||||
author = peers[authorId]
|
||||
if let author = author {
|
||||
messagePeers[author.id] = author
|
||||
}
|
||||
}
|
||||
|
||||
if let peer = peers[id.peerId] {
|
||||
messagePeers[peer.id] = peer
|
||||
|
||||
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
|
||||
if let channelPeer = peers[migrationReference.peerId] {
|
||||
messagePeers[channelPeer.id] = channelPeer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for media in message.media {
|
||||
for peerId in media.peerIds {
|
||||
if let peer = peers[peerId] {
|
||||
messagePeers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var forwardInfo: MessageForwardInfo?
|
||||
if let info = message.forwardInfo {
|
||||
forwardInfo = MessageForwardInfo(author: info.authorId.flatMap({ peers[$0] }), source: info.sourceId.flatMap({ peers[$0] }), sourceMessageId: info.sourceMessageId, date: info.date, authorSignature: info.authorSignature, psaType: info.psaType, flags: info.flags)
|
||||
if let author = forwardInfo?.author {
|
||||
messagePeers[author.id] = author
|
||||
}
|
||||
if let source = forwardInfo?.source {
|
||||
messagePeers[source.id] = source
|
||||
}
|
||||
}
|
||||
|
||||
var hasher = Hasher()
|
||||
hasher.combine(id.id)
|
||||
hasher.combine(id.peerId)
|
||||
|
||||
let hashValue = Int64(hasher.finalize())
|
||||
let first = UInt32((hashValue >> 32) & 0xffffffff)
|
||||
let second = UInt32(hashValue & 0xffffffff)
|
||||
let stableId = first &+ second
|
||||
|
||||
return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
func effectivelyIncoming(_ accountPeerId: PeerId) -> Bool {
|
||||
if self.id.peerId == accountPeerId {
|
||||
if self.forwardInfo != nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else if self.flags.contains(.Incoming) {
|
||||
return true
|
||||
} else if let channel = self.peers[self.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func effectivelyFailed(timestamp: Int32) -> Bool {
|
||||
if self.flags.contains(.Failed) {
|
||||
return true
|
||||
} else if self.id.namespace == Namespaces.Message.ScheduledCloud && self.timestamp != scheduleWhenOnlineTimestamp {
|
||||
return timestamp > self.timestamp + 60
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
var secretMediaDuration: Int32? {
|
||||
var found = false
|
||||
for attribute in self.attributes {
|
||||
if let _ = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||
found = true
|
||||
break
|
||||
} else if let _ = attribute as? AutoclearTimeoutMessageAttribute {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
for media in self.media {
|
||||
switch media {
|
||||
case _ as TelegramMediaImage:
|
||||
return nil
|
||||
case let file as TelegramMediaFile:
|
||||
return file.duration
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
var isSentOrAcknowledged: Bool {
|
||||
if self.flags.contains(.Failed) {
|
||||
return false
|
||||
} else if self.flags.isSending {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? OutgoingMessageInfoAttribute {
|
||||
if attribute.acknowledged {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user