mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Chat import fixes
This commit is contained in:
parent
127b41a959
commit
ff7446ba69
@ -65,7 +65,7 @@ private final class ImportManager {
|
||||
}
|
||||
|
||||
private let account: Account
|
||||
private let archivePath: String
|
||||
private let archivePath: String?
|
||||
private let entries: [(SSZipEntry, String, ChatHistoryImport.MediaType)]
|
||||
|
||||
private var session: ChatHistoryImport.Session?
|
||||
@ -89,7 +89,7 @@ private final class ImportManager {
|
||||
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.archivePath = archivePath
|
||||
self.entries = entries
|
||||
@ -187,70 +187,81 @@ private final class ImportManager {
|
||||
guard let session = self.session else {
|
||||
return
|
||||
}
|
||||
if self.activeEntries.count >= 3 {
|
||||
return
|
||||
}
|
||||
if self.pendingEntries.isEmpty {
|
||||
if self.pendingEntries.isEmpty && self.activeEntries.isEmpty {
|
||||
self.complete()
|
||||
return
|
||||
}
|
||||
if case .error = self.stateValue {
|
||||
return
|
||||
}
|
||||
|
||||
let entry = self.pendingEntries.removeFirst()
|
||||
let archivePath = self.archivePath
|
||||
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
|
||||
let tempFile = TempBox.shared.tempFile(fileName: entry.0.path)
|
||||
print("Extracting \(entry.0.path) to \(tempFile.path)...")
|
||||
let startTime = CACurrentMediaTime()
|
||||
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
|
||||
guard let archivePath = self.archivePath else {
|
||||
return
|
||||
}
|
||||
|
||||
let account = self.account
|
||||
while true {
|
||||
if self.activeEntries.count >= 3 {
|
||||
break
|
||||
}
|
||||
if self.pendingEntries.isEmpty {
|
||||
break
|
||||
}
|
||||
|
||||
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 entry = self.pendingEntries.removeFirst()
|
||||
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
|
||||
let tempFile = TempBox.shared.tempFile(fileName: entry.0.path)
|
||||
Logger.shared.log("ChatImportScreen", "Extracting \(entry.0.path) to \(tempFile.path)...")
|
||||
let startTime = CACurrentMediaTime()
|
||||
if SSZipArchive.extractFileFromArchive(atPath: archivePath, filePath: entry.0.path, toPath: tempFile.path) {
|
||||
Logger.shared.log("ChatImportScreen", "[Done in \(CACurrentMediaTime() - startTime) s] Extract \(entry.0.path) to \(tempFile.path)")
|
||||
subscriber.putNext(tempFile)
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putError(.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
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()
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -652,7 +663,7 @@ public final class ChatImportActivityScreen: ViewController {
|
||||
private var presentationData: PresentationData
|
||||
fileprivate let cancel: () -> Void
|
||||
fileprivate var peerId: PeerId
|
||||
private let archivePath: String
|
||||
private let archivePath: String?
|
||||
private let mainEntry: TempBoxFile
|
||||
private let totalBytes: 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.cancel = cancel
|
||||
self.peerId = peerId
|
||||
@ -769,10 +780,8 @@ public final class ChatImportActivityScreen: ViewController {
|
||||
}
|
||||
strongSelf.controllerNode.updateState(state: state, animated: true)
|
||||
if case let .progress(_, _, totalMediaBytes, totalUploadedMediaBytes) = state {
|
||||
if let progressEstimator = strongSelf.progressEstimator {
|
||||
let progress = Float(totalUploadedMediaBytes) / Float(totalMediaBytes)
|
||||
strongSelf.totalMediaProgress = progress
|
||||
}
|
||||
let progress = Float(totalUploadedMediaBytes) / Float(totalMediaBytes)
|
||||
strongSelf.totalMediaProgress = progress
|
||||
}
|
||||
}))
|
||||
}, error: { [weak self] error in
|
||||
|
@ -5,6 +5,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface MTGzip : NSObject
|
||||
|
||||
+ (NSData * _Nullable)decompress:(NSData *)data;
|
||||
+ (NSData * _Nullable)compress:(NSData *)data;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -50,4 +50,42 @@
|
||||
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
|
||||
|
@ -49,7 +49,7 @@ public enum ChatHistoryImport {
|
||||
}
|
||||
|
||||
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
|
||||
return .generic
|
||||
}
|
||||
|
@ -22,6 +22,20 @@ enum UploadPartError {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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>)
|
||||
if asBigPart {
|
||||
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))
|
||||
}
|
||||
|
||||
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
|
||||
if error.errorCode == 400 {
|
||||
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
|
||||
let request = MTRequest()
|
||||
|
||||
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
|
||||
var saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
|
||||
if asBigPart {
|
||||
let totalParts: Int32
|
||||
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 = 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
|
||||
if let result = saveFilePart.2.parse(Buffer(data: response)) {
|
||||
return BoxedMessage(result)
|
||||
|
@ -113,7 +113,7 @@ private enum HeaderPartState {
|
||||
}
|
||||
|
||||
private final class MultipartUploadManager {
|
||||
let parallelParts: Int = 3
|
||||
let parallelParts: Int
|
||||
var defaultPartSize: Int
|
||||
var bigTotalParts: Int?
|
||||
var bigParts: Bool
|
||||
@ -140,13 +140,19 @@ private final class MultipartUploadManager {
|
||||
|
||||
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
|
||||
|
||||
var fileId: Int64 = 0
|
||||
arc4random_buf(&fileId, 8)
|
||||
self.fileId = fileId
|
||||
|
||||
if increaseParallelParts {
|
||||
self.parallelParts = 30
|
||||
} else {
|
||||
self.parallelParts = 3
|
||||
}
|
||||
|
||||
self.forceNoBigParts = forceNoBigParts
|
||||
self.useLargerParts = useLargerParts
|
||||
|
||||
@ -381,7 +387,7 @@ enum MultipartUploadError {
|
||||
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 {
|
||||
case download(Download)
|
||||
case multiplexed(manager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64)
|
||||
@ -447,12 +453,12 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload
|
||||
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 {
|
||||
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):
|
||||
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
|
||||
subscriber.putNext(.progress(progress))
|
||||
|
@ -401,81 +401,98 @@ public class ShareRootControllerImpl {
|
||||
return
|
||||
}
|
||||
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 mainFile: TempBoxFile?
|
||||
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))
|
||||
|
||||
if fileExtension.lowercased() == "zip" {
|
||||
let archivePath = url.path
|
||||
archivePathValue = archivePath
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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)) {
|
||||
@ -561,7 +578,7 @@ public class ShareRootControllerImpl {
|
||||
navigationController.view.endEditing(true)
|
||||
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
|
||||
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
|
||||
@ -675,7 +692,7 @@ public class ShareRootControllerImpl {
|
||||
navigationController.view.endEditing(true)
|
||||
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
|
||||
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
|
||||
@ -737,7 +754,7 @@ public class ShareRootControllerImpl {
|
||||
navigationController.view.endEditing(true)
|
||||
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user