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 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,25 +187,32 @@ 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
}
guard let archivePath = self.archivePath else {
return
}
while true {
if self.activeEntries.count >= 3 {
break
}
if self.pendingEntries.isEmpty {
break
}
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)...")
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) {
print("[Done in \(CACurrentMediaTime() - startTime) s] Extract \(entry.0.path) to \(tempFile.path)")
Logger.shared.log("ChatImportScreen", "[Done in \(CACurrentMediaTime() - startTime) s] Extract \(entry.0.path) to \(tempFile.path)")
subscriber.putNext(tempFile)
subscriber.putCompletion()
} else {
@ -230,7 +237,10 @@ private final class ImportManager {
}
}
self.activeEntries[entry.1] = (uploadedEntrySignal
let disposable = MetaDisposable()
self.activeEntries[entry.1] = disposable
disposable.set((uploadedEntrySignal
|> deliverOnMainQueue).start(next: { [weak self] progress in
guard let strongSelf = self else {
return
@ -250,7 +260,8 @@ private final class ImportManager {
}
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,11 +780,9 @@ 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
}
}
}))
}, error: { [weak self] error in
guard let strongSelf = self else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -401,12 +401,14 @@ public class ShareRootControllerImpl {
return
}
let fileExtension = (fileName as NSString).pathExtension
guard fileExtension.lowercased() == "zip" else {
beginShare()
return
}
var archivePathValue: String?
var otherEntries: [(SSZipEntry, String, ChatHistoryImport.MediaType)] = []
var mainFile: TempBoxFile?
if fileExtension.lowercased() == "zip" {
let archivePath = url.path
archivePathValue = archivePath
guard let entries = SSZipArchive.getEntriesForFile(atPath: archivePath) else {
beginShare()
@ -441,9 +443,6 @@ public class ShareRootControllerImpl {
let stickerRegex = try! NSRegularExpression(pattern: "[\\d]+-STICKER-.*?\\.webp")
let voiceRegex = try! NSRegularExpression(pattern: "[\\d]+-AUDIO-.*?\\.opus")
var otherEntries: [(SSZipEntry, String, ChatHistoryImport.MediaType)] = []
var mainFile: TempBoxFile?
do {
for entry in entries {
let entryPath = entry.path.replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "..", with: "_")
@ -475,7 +474,25 @@ public class ShareRootControllerImpl {
}
}
}
} catch {
}
} 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