Skip to content

Commit 9c52359

Browse files
authored
feat: create conversation import use case - WPB-14599 (#2402)
1 parent 86260e1 commit 9c52359

19 files changed

+2047
-241
lines changed

wire-ios-data-model/Source/Model/UserClient/UserClient.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ public class UserClient: ZMManagedObject, UserClientType {
157157
return userClient
158158
}
159159

160+
public func markAsSelfClient() {
161+
guard let context = managedObjectContext else { return }
162+
context.setPersistentStoreMetadata(remoteIdentifier, key: ZMPersistedClientIdKey)
163+
_ = context.makeMetadataPersistent()
164+
}
165+
160166
public static func fetchUserClient(
161167
withRemoteId remoteIdentifier: String,
162168
forUser user: ZMUser,

wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ extension SessionManager {
107107

108108
// MARK: - Import
109109

110+
// TODO: [WPB-14616] delete import related code when the restore button from the authentication flow is removed
111+
110112
/// Restores the account database from the Wire iOS database back up file.
111113
/// @param completion called when the restoration is ended. If success, Result.success with the new restored account
112114
/// is called.
@@ -256,7 +258,7 @@ private extension BackupMetadata {
256258

257259
// MARK: - Zip Helper
258260

259-
extension URL {
261+
private extension URL {
260262
func zipDirectory(to url: URL) -> Bool {
261263
SSZipArchive.createZipFile(atPath: url.path, withContentsOfDirectory: path)
262264
}

wire-ios-sync-engine/Source/SessionManager/SessionManager.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,7 @@ public final class SessionManager: NSObject, SessionManagerType {
747747
account,
748748
from: selectedAccount,
749749
userSessionCanBeTornDown: { [weak self] in
750-
self?.activeUserSession = nil
751-
tearDownCompletion?()
750+
self?.tearDownActiveSession(completion: tearDownCompletion)
752751
guard let self else {
753752
completion?(nil)
754753
return
@@ -1008,6 +1007,16 @@ public final class SessionManager: NSObject, SessionManagerType {
10081007
delegate?.sessionManagerAsksToRetryStart()
10091008
}
10101009

1010+
// TODO: [WPB-14616] use this method for restoring a backup from the settings
1011+
/// The active user session will be torn down and the app goes into migration state.
1012+
public func prepareForRestoreWithMigration(completion: @escaping () -> Void) {
1013+
guard let delegate else { return completion() }
1014+
1015+
delegate.sessionManagerWillMigrateAccount {
1016+
self.tearDownActiveSession(completion: completion)
1017+
}
1018+
}
1019+
10111020
private func setupUserSession(
10121021
account: Account,
10131022
onCompletion: @escaping (ZMUserSession?) -> Void
@@ -1183,6 +1192,11 @@ public final class SessionManager: NSObject, SessionManagerType {
11831192
}
11841193
}
11851194

1195+
private func tearDownActiveSession(completion: (() -> Void)?) {
1196+
activeUserSession = nil
1197+
completion?()
1198+
}
1199+
11861200
// Creates the user session for @c account given, calls @c completion when done.
11871201
private func startBackgroundSession(
11881202
for account: Account,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
enum BackupRestoreError: Error {
20+
case noActiveAccount
21+
case compressionError
22+
case invalidFileExtension
23+
case keyCreationFailed
24+
case decryptionError
25+
case unknown
26+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import Foundation
20+
21+
struct ImportBackupEntityStorage: ImportBackupEntityStorageProtocol {
22+
23+
var importsDirectory: URL {
24+
CoreDataStack.importsDirectory
25+
}
26+
27+
@MainActor
28+
func replacePersistentStore(
29+
accountIdentifier: UUID,
30+
from backupDirectory: URL,
31+
applicationContainer: URL,
32+
dispatchGroup: ZMSDispatchGroup
33+
) async throws -> URL {
34+
35+
try await withCheckedThrowingContinuation { continuation in
36+
37+
CoreDataStack.importLocalStorage(
38+
accountIdentifier: accountIdentifier,
39+
from: backupDirectory,
40+
applicationContainer: applicationContainer,
41+
dispatchGroup: dispatchGroup
42+
) { result in
43+
continuation.resume(with: result)
44+
}
45+
46+
}
47+
48+
}
49+
50+
@MainActor
51+
func createContextProvider(
52+
account: Account,
53+
applicationContainer: URL,
54+
dispatchGroup: ZMSDispatchGroup?
55+
) async throws -> any ContextProvider {
56+
57+
let stack = CoreDataStack(
58+
account: account,
59+
applicationContainer: applicationContainer,
60+
dispatchGroup: dispatchGroup
61+
)
62+
63+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
64+
65+
stack.loadStores { error in
66+
if let error {
67+
continuation.resume(throwing: error)
68+
} else {
69+
continuation.resume()
70+
}
71+
}
72+
73+
}
74+
75+
return stack
76+
77+
}
78+
79+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import ZipArchive
20+
21+
struct ImportBackupFileArchiver: ImportBackupFileArchiverProtocol {
22+
23+
func unzipFile(at sourceURL: URL, to destinationURL: URL) throws {
24+
25+
let success = SSZipArchive.unzipFile(
26+
atPath: sourceURL.path,
27+
toDestination: destinationURL.path
28+
)
29+
30+
guard success else {
31+
throw BackupRestoreError.compressionError
32+
}
33+
34+
}
35+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import Foundation
20+
import WireCrypto
21+
22+
struct ImportBackupStreamDecryptor: ImportBackupStreamDecryptorProtocol {
23+
24+
func decrypt(
25+
input: InputStream,
26+
output: OutputStream,
27+
accountID: UUID,
28+
password: String
29+
) throws {
30+
31+
do {
32+
33+
let passphrase = ChaCha20Poly1305.StreamEncryption.Passphrase(
34+
password: password,
35+
uuid: accountID
36+
)
37+
38+
try ChaCha20Poly1305.StreamEncryption.decrypt(
39+
input: input,
40+
output: output,
41+
passphrase: passphrase
42+
)
43+
44+
} catch ChaCha20Poly1305.StreamEncryption.EncryptionError.decryptionFailed {
45+
throw BackupRestoreError.decryptionError
46+
47+
} catch ChaCha20Poly1305.StreamEncryption.EncryptionError.keyGenerationFailed {
48+
throw BackupRestoreError.keyCreationFailed
49+
}
50+
51+
}
52+
53+
}

0 commit comments

Comments
 (0)