Chat import fixes

This commit is contained in:
Ali 2021-01-26 18:40:22 +05:00
parent 127b41a959
commit ff7446ba69
7 changed files with 230 additions and 143 deletions

View File

@ -65,7 +65,7 @@ private final class ImportManager {
} }
private let account: Account private let account: Account
private let archivePath: String private let archivePath: String?
private let entries: [(SSZipEntry, String, ChatHistoryImport.MediaType)] private let entries: [(SSZipEntry, String, ChatHistoryImport.MediaType)]
private var session: ChatHistoryImport.Session? private var session: ChatHistoryImport.Session?
@ -89,7 +89,7 @@ private final class ImportManager {
return self.statePromise.get() return self.statePromise.get()
} }
init(account: Account, peerId: PeerId, mainFile: TempBoxFile, archivePath: String, entries: [(SSZipEntry, String, ChatHistoryImport.MediaType)]) { init(account: Account, peerId: PeerId, mainFile: TempBoxFile, archivePath: String?, entries: [(SSZipEntry, String, ChatHistoryImport.MediaType)]) {
self.account = account self.account = account
self.archivePath = archivePath self.archivePath = archivePath
self.entries = entries self.entries = entries
@ -187,70 +187,81 @@ private final class ImportManager {
guard let session = self.session else { guard let session = self.session else {
return return
} }
if self.activeEntries.count >= 3 { if self.pendingEntries.isEmpty && self.activeEntries.isEmpty {
return
}
if self.pendingEntries.isEmpty {
self.complete() self.complete()
return return
} }
if case .error = self.stateValue { if case .error = self.stateValue {
return return
} }
guard let archivePath = self.archivePath else {
return
}
let entry = self.pendingEntries.removeFirst() while true {
let archivePath = self.archivePath if self.activeEntries.count >= 3 {
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in break
let tempFile = TempBox.shared.tempFile(fileName: entry.0.path) }
print("Extracting \(entry.0.path) to \(tempFile.path)...") if self.pendingEntries.isEmpty {
let startTime = CACurrentMediaTime() break
if SSZipArchive.extractFileFromArchive(atPath: archivePath, filePath: entry.0.path, toPath: tempFile.path) {
print("[Done in \(CACurrentMediaTime() - startTime) s] Extract \(entry.0.path) to \(tempFile.path)")
subscriber.putNext(tempFile)
subscriber.putCompletion()
} else {
subscriber.putError(.generic)
} }
return EmptyDisposable let entry = self.pendingEntries.removeFirst()
} let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
let tempFile = TempBox.shared.tempFile(fileName: entry.0.path)
let account = self.account Logger.shared.log("ChatImportScreen", "Extracting \(entry.0.path) to \(tempFile.path)...")
let startTime = CACurrentMediaTime()
let uploadedEntrySignal: Signal<Float, ImportError> = unpackedFile if SSZipArchive.extractFileFromArchive(atPath: archivePath, filePath: entry.0.path, toPath: tempFile.path) {
|> mapToSignal { tempFile -> Signal<Float, ImportError> in Logger.shared.log("ChatImportScreen", "[Done in \(CACurrentMediaTime() - startTime) s] Extract \(entry.0.path) to \(tempFile.path)")
return ChatHistoryImport.uploadMedia(account: account, session: session, file: tempFile, fileName: entry.0.path, mimeType: entry.1, type: entry.2) subscriber.putNext(tempFile)
|> mapError { error -> ImportError in subscriber.putCompletion()
switch error { } else {
case .chatAdminRequired: subscriber.putError(.generic)
return .chatAdminRequired }
case .generic:
return .generic return EmptyDisposable
}
let account = self.account
let uploadedEntrySignal: Signal<Float, ImportError> = unpackedFile
|> mapToSignal { tempFile -> Signal<Float, ImportError> in
return ChatHistoryImport.uploadMedia(account: account, session: session, file: tempFile, fileName: entry.0.path, mimeType: entry.1, type: entry.2)
|> mapError { error -> ImportError in
switch error {
case .chatAdminRequired:
return .chatAdminRequired
case .generic:
return .generic
}
} }
} }
let disposable = MetaDisposable()
self.activeEntries[entry.1] = disposable
disposable.set((uploadedEntrySignal
|> deliverOnMainQueue).start(next: { [weak self] progress in
guard let strongSelf = self else {
return
}
if let (size, _) = strongSelf.entryProgress[entry.0.path] {
strongSelf.entryProgress[entry.0.path] = (size, Int(progress * Float(entry.0.uncompressedSize)))
strongSelf.updateProgress()
}
}, error: { [weak self] error in
guard let strongSelf = self else {
return
}
strongSelf.failWithError(error)
}, completed: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.activeEntries.removeValue(forKey: entry.0.path)
strongSelf.updateState()
}))
} }
self.activeEntries[entry.1] = (uploadedEntrySignal
|> deliverOnMainQueue).start(next: { [weak self] progress in
guard let strongSelf = self else {
return
}
if let (size, _) = strongSelf.entryProgress[entry.0.path] {
strongSelf.entryProgress[entry.0.path] = (size, Int(progress * Float(entry.0.uncompressedSize)))
strongSelf.updateProgress()
}
}, error: { [weak self] error in
guard let strongSelf = self else {
return
}
strongSelf.failWithError(error)
}, completed: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.activeEntries.removeValue(forKey: entry.0.path)
strongSelf.updateState()
})
} }
} }
@ -652,7 +663,7 @@ public final class ChatImportActivityScreen: ViewController {
private var presentationData: PresentationData private var presentationData: PresentationData
fileprivate let cancel: () -> Void fileprivate let cancel: () -> Void
fileprivate var peerId: PeerId fileprivate var peerId: PeerId
private let archivePath: String private let archivePath: String?
private let mainEntry: TempBoxFile private let mainEntry: TempBoxFile
private let totalBytes: Int private let totalBytes: Int
private let totalMediaBytes: Int private let totalMediaBytes: Int
@ -673,7 +684,7 @@ public final class ChatImportActivityScreen: ViewController {
} }
} }
public init(context: AccountContext, cancel: @escaping () -> Void, peerId: PeerId, archivePath: String, mainEntry: TempBoxFile, otherEntries: [(SSZipEntry, String, ChatHistoryImport.MediaType)]) { public init(context: AccountContext, cancel: @escaping () -> Void, peerId: PeerId, archivePath: String?, mainEntry: TempBoxFile, otherEntries: [(SSZipEntry, String, ChatHistoryImport.MediaType)]) {
self.context = context self.context = context
self.cancel = cancel self.cancel = cancel
self.peerId = peerId self.peerId = peerId
@ -769,10 +780,8 @@ public final class ChatImportActivityScreen: ViewController {
} }
strongSelf.controllerNode.updateState(state: state, animated: true) strongSelf.controllerNode.updateState(state: state, animated: true)
if case let .progress(_, _, totalMediaBytes, totalUploadedMediaBytes) = state { if case let .progress(_, _, totalMediaBytes, totalUploadedMediaBytes) = state {
if let progressEstimator = strongSelf.progressEstimator { let progress = Float(totalUploadedMediaBytes) / Float(totalMediaBytes)
let progress = Float(totalUploadedMediaBytes) / Float(totalMediaBytes) strongSelf.totalMediaProgress = progress
strongSelf.totalMediaProgress = progress
}
} }
})) }))
}, error: { [weak self] error in }, error: { [weak self] error in

View File

@ -5,6 +5,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface MTGzip : NSObject @interface MTGzip : NSObject
+ (NSData * _Nullable)decompress:(NSData *)data; + (NSData * _Nullable)decompress:(NSData *)data;
+ (NSData * _Nullable)compress:(NSData *)data;
@end @end

View File

@ -50,4 +50,42 @@
return (retCode == Z_STREAM_END ? result : nil); return (retCode == Z_STREAM_END ? result : nil);
} }
+ (NSData * _Nullable)compress:(NSData *)data {
if (data.length == 0) {
return data;
}
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = (uint)data.length;
stream.next_in = (Bytef *)(void *)data.bytes;
stream.total_out = 0;
stream.avail_out = 0;
static const NSUInteger ChunkSize = 16384;
NSMutableData *output = nil;
int compression = Z_BEST_COMPRESSION;
if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK)
{
output = [NSMutableData dataWithLength:ChunkSize];
while (stream.avail_out == 0)
{
if (stream.total_out >= output.length)
{
output.length += ChunkSize;
}
stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out;
stream.avail_out = (uInt)(output.length - stream.total_out);
deflate(&stream, Z_FINISH);
}
deflateEnd(&stream);
output.length = stream.total_out;
}
return output;
}
@end @end

View File

@ -49,7 +49,7 @@ public enum ChatHistoryImport {
} }
public static func initSession(account: Account, peerId: PeerId, file: TempBoxFile, mediaCount: Int32) -> Signal<Session, InitImportError> { public static func initSession(account: Account, peerId: PeerId, file: TempBoxFile, mediaCount: Int32) -> Signal<Session, InitImportError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false) return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false, useLargerParts: true, increaseParallelParts: true, useMultiplexedRequests: false, useCompression: true)
|> mapError { _ -> InitImportError in |> mapError { _ -> InitImportError in
return .generic return .generic
} }

View File

@ -22,6 +22,20 @@ enum UploadPartError {
case invalidMedia case invalidMedia
} }
private func wrapMethodBody(_ body: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>), useCompression: Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
if useCompression {
if let compressed = MTGzip.compress(body.1.makeData()) {
if compressed.count < body.1.size {
let os = MTOutputStream()
os.write(0x3072cfa1 as Int32)
os.writeBytes(compressed)
return (body.0, Buffer(data: os.currentBytes()), body.2)
}
}
}
return body
}
class Download: NSObject, MTRequestMessageServiceDelegate { class Download: NSObject, MTRequestMessageServiceDelegate {
let datacenterId: Int let datacenterId: Int
@ -82,7 +96,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId) self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
} }
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil) -> Signal<Void, UploadPartError> { static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> {
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
if asBigPart { if asBigPart {
let totalParts: Int32 let totalParts: Int32
@ -96,7 +110,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data)) saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
} }
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, data: saveFilePart, tag: tag, continueInBackground: true) return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true)
|> mapError { error -> UploadPartError in |> mapError { error -> UploadPartError in
if error.errorCode == 400 { if error.errorCode == 400 {
return .invalidMedia return .invalidMedia
@ -109,11 +123,11 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
} }
} }
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil) -> Signal<Void, UploadPartError> { func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> {
return Signal<Void, MTRpcError> { subscriber in return Signal<Void, MTRpcError> { subscriber in
let request = MTRequest() let request = MTRequest()
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) var saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
if asBigPart { if asBigPart {
let totalParts: Int32 let totalParts: Int32
if let bigTotalParts = bigTotalParts { if let bigTotalParts = bigTotalParts {
@ -126,6 +140,8 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data)) saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
} }
saveFilePart = wrapMethodBody(saveFilePart, useCompression: useCompression)
request.setPayload(saveFilePart.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(saveFilePart.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(saveFilePart.0)), responseParser: { response in request.setPayload(saveFilePart.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(saveFilePart.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(saveFilePart.0)), responseParser: { response in
if let result = saveFilePart.2.parse(Buffer(data: response)) { if let result = saveFilePart.2.parse(Buffer(data: response)) {
return BoxedMessage(result) return BoxedMessage(result)

View File

@ -113,7 +113,7 @@ private enum HeaderPartState {
} }
private final class MultipartUploadManager { private final class MultipartUploadManager {
let parallelParts: Int = 3 let parallelParts: Int
var defaultPartSize: Int var defaultPartSize: Int
var bigTotalParts: Int? var bigTotalParts: Int?
var bigParts: Bool var bigParts: Bool
@ -140,13 +140,19 @@ private final class MultipartUploadManager {
let state: MultipartUploadState let state: MultipartUploadState
init(headerSize: Int32, data: Signal<MultipartUploadData, NoError>, encryptionKey: SecretFileEncryptionKey?, hintFileSize: Int?, hintFileIsLarge: Bool, forceNoBigParts: Bool, useLargerParts: Bool, uploadPart: @escaping (UploadPart) -> Signal<Void, UploadPartError>, progress: @escaping (Float) -> Void, completed: @escaping (MultipartIntermediateResult?) -> Void) { init(headerSize: Int32, data: Signal<MultipartUploadData, NoError>, encryptionKey: SecretFileEncryptionKey?, hintFileSize: Int?, hintFileIsLarge: Bool, forceNoBigParts: Bool, useLargerParts: Bool, increaseParallelParts: Bool, uploadPart: @escaping (UploadPart) -> Signal<Void, UploadPartError>, progress: @escaping (Float) -> Void, completed: @escaping (MultipartIntermediateResult?) -> Void) {
self.dataSignal = data self.dataSignal = data
var fileId: Int64 = 0 var fileId: Int64 = 0
arc4random_buf(&fileId, 8) arc4random_buf(&fileId, 8)
self.fileId = fileId self.fileId = fileId
if increaseParallelParts {
self.parallelParts = 30
} else {
self.parallelParts = 3
}
self.forceNoBigParts = forceNoBigParts self.forceNoBigParts = forceNoBigParts
self.useLargerParts = useLargerParts self.useLargerParts = useLargerParts
@ -381,7 +387,7 @@ enum MultipartUploadError {
case generic case generic
} }
func multipartUpload(network: Network, postbox: Postbox, source: MultipartUploadSource, encrypt: Bool, tag: MediaResourceFetchTag?, hintFileSize: Int?, hintFileIsLarge: Bool, forceNoBigParts: Bool, useLargerParts: Bool = false, useMultiplexedRequests: Bool = false) -> Signal<MultipartUploadResult, MultipartUploadError> { func multipartUpload(network: Network, postbox: Postbox, source: MultipartUploadSource, encrypt: Bool, tag: MediaResourceFetchTag?, hintFileSize: Int?, hintFileIsLarge: Bool, forceNoBigParts: Bool, useLargerParts: Bool = false, increaseParallelParts: Bool = false, useMultiplexedRequests: Bool = false, useCompression: Bool = false) -> Signal<MultipartUploadResult, MultipartUploadError> {
enum UploadInterface { enum UploadInterface {
case download(Download) case download(Download)
case multiplexed(manager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64) case multiplexed(manager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64)
@ -447,12 +453,12 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload
fetchedResource = .complete() fetchedResource = .complete()
} }
let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, uploadPart: { part in let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in
switch uploadInterface { switch uploadInterface {
case let .download(download): case let .download(download):
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts) return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression)
case let .multiplexed(multiplexed, datacenterId, consumerId): case let .multiplexed(multiplexed, datacenterId, consumerId):
return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts) return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression)
} }
}, progress: { progress in }, progress: { progress in
subscriber.putNext(.progress(progress)) subscriber.putNext(.progress(progress))

View File

@ -401,81 +401,98 @@ public class ShareRootControllerImpl {
return return
} }
let fileExtension = (fileName as NSString).pathExtension let fileExtension = (fileName as NSString).pathExtension
guard fileExtension.lowercased() == "zip" else {
beginShare()
return
}
let archivePath = url.path
guard let entries = SSZipArchive.getEntriesForFile(atPath: archivePath) else {
beginShare()
return
}
let mainFileNames: [NSRegularExpression] = [
try! NSRegularExpression(pattern: "_chat\\.txt"),
try! NSRegularExpression(pattern: "KakaoTalkChats\\.txt"),
try! NSRegularExpression(pattern: "Talk_.*?\\.txt"),
]
var maybeMainFileName: String?
mainFileLoop: for entry in entries {
let entryFileName = entry.path.replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "..", with: "_")
let fullRange = NSRange(entryFileName.startIndex ..< entryFileName.endIndex, in: entryFileName)
for expression in mainFileNames {
if expression.firstMatch(in: entryFileName, options: [], range: fullRange) != nil {
maybeMainFileName = entryFileName
break mainFileLoop
}
}
}
guard let mainFileName = maybeMainFileName else {
beginShare()
return
}
let photoRegex = try! NSRegularExpression(pattern: ".*?\\.jpg")
let videoRegex = try! NSRegularExpression(pattern: "[\\d]+-VIDEO-.*?\\.mp4")
let stickerRegex = try! NSRegularExpression(pattern: "[\\d]+-STICKER-.*?\\.webp")
let voiceRegex = try! NSRegularExpression(pattern: "[\\d]+-AUDIO-.*?\\.opus")
var archivePathValue: String?
var otherEntries: [(SSZipEntry, String, ChatHistoryImport.MediaType)] = [] var otherEntries: [(SSZipEntry, String, ChatHistoryImport.MediaType)] = []
var mainFile: TempBoxFile? var mainFile: TempBoxFile?
do {
for entry in entries { if fileExtension.lowercased() == "zip" {
let entryPath = entry.path.replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "..", with: "_") let archivePath = url.path
if entryPath.isEmpty { archivePathValue = archivePath
continue
} guard let entries = SSZipArchive.getEntriesForFile(atPath: archivePath) else {
let tempFile = TempBox.shared.tempFile(fileName: entryPath) beginShare()
if entryPath == mainFileName { return
if SSZipArchive.extractFileFromArchive(atPath: archivePath, filePath: entry.path, toPath: tempFile.path) { }
mainFile = tempFile
} let mainFileNames: [NSRegularExpression] = [
} else { try! NSRegularExpression(pattern: "_chat\\.txt"),
let entryFileName = (entryPath as NSString).lastPathComponent try! NSRegularExpression(pattern: "KakaoTalkChats\\.txt"),
if !entryFileName.isEmpty { try! NSRegularExpression(pattern: "Talk_.*?\\.txt"),
let mediaType: ChatHistoryImport.MediaType ]
let fullRange = NSRange(entryFileName.startIndex ..< entryFileName.endIndex, in: entryFileName)
if photoRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil { var maybeMainFileName: String?
mediaType = .photo mainFileLoop: for entry in entries {
} else if videoRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil { let entryFileName = entry.path.replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "..", with: "_")
mediaType = .video let fullRange = NSRange(entryFileName.startIndex ..< entryFileName.endIndex, in: entryFileName)
} else if stickerRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil { for expression in mainFileNames {
mediaType = .sticker if expression.firstMatch(in: entryFileName, options: [], range: fullRange) != nil {
} else if voiceRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil { maybeMainFileName = entryFileName
mediaType = .voice break mainFileLoop
} else {
mediaType = .file
}
otherEntries.append((entry, entryFileName, mediaType))
} }
} }
} }
} catch {
guard let mainFileName = maybeMainFileName else {
beginShare()
return
}
let photoRegex = try! NSRegularExpression(pattern: ".*?\\.jpg")
let videoRegex = try! NSRegularExpression(pattern: "[\\d]+-VIDEO-.*?\\.mp4")
let stickerRegex = try! NSRegularExpression(pattern: "[\\d]+-STICKER-.*?\\.webp")
let voiceRegex = try! NSRegularExpression(pattern: "[\\d]+-AUDIO-.*?\\.opus")
do {
for entry in entries {
let entryPath = entry.path.replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "..", with: "_")
if entryPath.isEmpty {
continue
}
let tempFile = TempBox.shared.tempFile(fileName: entryPath)
if entryPath == mainFileName {
if SSZipArchive.extractFileFromArchive(atPath: archivePath, filePath: entry.path, toPath: tempFile.path) {
mainFile = tempFile
}
} else {
let entryFileName = (entryPath as NSString).lastPathComponent
if !entryFileName.isEmpty {
let mediaType: ChatHistoryImport.MediaType
let fullRange = NSRange(entryFileName.startIndex ..< entryFileName.endIndex, in: entryFileName)
if photoRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil {
mediaType = .photo
} else if videoRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil {
mediaType = .video
} else if stickerRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil {
mediaType = .sticker
} else if voiceRegex.firstMatch(in: entryFileName, options: [], range: fullRange) != nil {
mediaType = .voice
} else {
mediaType = .file
}
otherEntries.append((entry, entryFileName, mediaType))
}
}
}
}
} else if fileExtension.lowercased() == "txt" {
if let mainFileText = try? String(contentsOf: URL(fileURLWithPath: url.path)) {
if !mainFileText.hasPrefix("[LINE]") {
beginShare()
return
}
} else {
beginShare()
return
}
let tempFile = TempBox.shared.tempFile(fileName: "History.txt")
if let _ = try? FileManager.default.copyItem(atPath: url.path, toPath: tempFile.path) {
mainFile = tempFile
} else {
beginShare()
return
}
} }
if let mainFile = mainFile, let mainFileText = try? String(contentsOf: URL(fileURLWithPath: mainFile.path)) { if let mainFile = mainFile, let mainFileText = try? String(contentsOf: URL(fileURLWithPath: mainFile.path)) {
@ -561,7 +578,7 @@ public class ShareRootControllerImpl {
navigationController.view.endEditing(true) navigationController.view.endEditing(true)
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archivePath: archivePath, mainEntry: mainFile, otherEntries: otherEntries)) }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries))
} }
attemptSelectionImpl = { peer in attemptSelectionImpl = { peer in
@ -675,7 +692,7 @@ public class ShareRootControllerImpl {
navigationController.view.endEditing(true) navigationController.view.endEditing(true)
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archivePath: archivePath, mainEntry: mainFile, otherEntries: otherEntries)) }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries))
} }
attemptSelectionImpl = { [weak controller] peer in attemptSelectionImpl = { [weak controller] peer in
@ -737,7 +754,7 @@ public class ShareRootControllerImpl {
navigationController.view.endEditing(true) navigationController.view.endEditing(true)
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archivePath: archivePath, mainEntry: mainFile, otherEntries: otherEntries)) }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries))
} }
attemptSelectionImpl = { [weak controller] peer in attemptSelectionImpl = { [weak controller] peer in