Skip to content

Commit

Permalink
basic UI for iroh-share backup transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
r10s committed Aug 31, 2022
1 parent 5115045 commit 57ee543
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 4 deletions.
8 changes: 8 additions & 0 deletions DcCore/DcCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
30E8F2482449C98600CE2C90 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F2472449C98600CE2C90 /* UIView+Extensions.swift */; };
30E8F24B2449CF6500CE2C90 /* InitialsBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F24A2449CF6500CE2C90 /* InitialsBadge.swift */; };
30E8F24D2449D30200CE2C90 /* DcColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F24C2449D30200CE2C90 /* DcColors.swift */; };
78A8733F287766FA00690A0B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 78A8733E287766FA00690A0B /* libc++.tbd */; };
78A873412877679B00690A0B /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78A873402877679B00690A0B /* SystemConfiguration.framework */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -69,13 +71,17 @@
30E8F2472449C98600CE2C90 /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
30E8F24A2449CF6500CE2C90 /* InitialsBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialsBadge.swift; sourceTree = "<group>"; };
30E8F24C2449D30200CE2C90 /* DcColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DcColors.swift; sourceTree = "<group>"; };
78A8733E287766FA00690A0B /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/lib/libc++.tbd"; sourceTree = DEVELOPER_DIR; };
78A873402877679B00690A0B /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
3042192E243DE0F200516852 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A873412877679B00690A0B /* SystemConfiguration.framework in Frameworks */,
78A8733F287766FA00690A0B /* libc++.tbd in Frameworks */,
30421959243DE6AD00516852 /* libdeltachat.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -149,6 +155,8 @@
30421957243DE61400516852 /* Frameworks */ = {
isa = PBXGroup;
children = (
78A873402877679B00690A0B /* SystemConfiguration.framework */,
78A8733E287766FA00690A0B /* libc++.tbd */,
30421958243DE61400516852 /* libdeltachat.a */,
);
name = Frameworks;
Expand Down
32 changes: 32 additions & 0 deletions DcCore/DcCore/DC/Wrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,15 @@ public class DcContext {
public func imex(what: Int32, directory: String, passphrase: String? = nil) {
dc_imex(contextPointer, what, directory, passphrase)
}

public func send_backup(directory: String, passphrase: String? = nil) -> DcBackupSender? {
guard let dcBackupSenderPointer = dc_send_backup(contextPointer, directory, passphrase) else { return nil }
return DcBackupSender(dcBackupSenderPointer)
}

public func receiveBackup(qrCode: String, passphrase: String? = nil) {
dc_receive_backup(contextPointer, qrCode, passphrase)
}

public func imexHasBackup(filePath: String) -> String? {
var file: String?
Expand Down Expand Up @@ -1402,6 +1411,29 @@ public class DcLot {
}
}

public class DcBackupSender {
private var dcBackupSenderPointer: OpaquePointer?

// takes ownership of specified pointer
public init(_ dcBackupSenderPointer: OpaquePointer) {
print(">>>> 💙 init DcBackupSender")
self.dcBackupSenderPointer = dcBackupSenderPointer
}

deinit {
print(">>>> 💙 deinit DcBackupSender")
dc_backup_sender_unref(dcBackupSenderPointer)
}

public func qr_code(context: DcContext) -> String? {
guard let cString = dc_backup_sender_qr(context.contextPointer, dcBackupSenderPointer) else { return nil }
let swiftString = String(cString: cString)
dc_str_unref(cString)
return swiftString

}
}

public class DcProvider {
private var dcProviderPointer: OpaquePointer?

Expand Down
1 change: 1 addition & 0 deletions DcCore/DcCore/DC/wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ typedef dc_msg_t dc_msg_t;
typedef dc_lot_t dc_lot_t;
typedef dc_array_t dc_array_t;
typedef dc_chatlist_t dc_chatlist_t;
typedef dc_backup_sender_t dc_backup_sender_t;

#endif /* wrapper_h */
4 changes: 4 additions & 0 deletions deltachat-ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
7837B64021E54DC600CDE126 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 7837B63F21E54DC600CDE126 /* .swiftlint.yml */; };
785BE16821E247F1003BE98C /* MessageInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785BE16721E247F1003BE98C /* MessageInfoViewController.swift */; };
789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */; };
78A8733D2877608200690A0B /* QrBackupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A8733C2877608200690A0B /* QrBackupViewController.swift */; };
78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3921D3CFBC00D4B15E /* SettingsController.swift */; };
78E45E4421D3F14A00D4B15E /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */; };
78ED838321D5379000243125 /* TextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78ED838221D5379000243125 /* TextFieldCell.swift */; };
Expand Down Expand Up @@ -409,6 +410,7 @@
785BE16721E247F1003BE98C /* MessageInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInfoViewController.swift; sourceTree = "<group>"; };
787D6699229F2237000A7A9D /* libdeltachat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdeltachat.a; path = "deltachat-ios/libraries/deltachat-core-rust/target/universal/debug/libdeltachat.a"; sourceTree = "<group>"; };
789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeReaderController.swift; sourceTree = "<group>"; };
78A8733C2877608200690A0B /* QrBackupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrBackupViewController.swift; sourceTree = "<group>"; };
78C7036A21D46752005D4525 /* deltachat-ios.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "deltachat-ios.entitlements"; sourceTree = "<group>"; };
78E45E3921D3CFBC00D4B15E /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -941,6 +943,7 @@
789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */,
AECEF03D244F2D55006C90DA /* QrPageController.swift */,
30149D9222F21129003C12B5 /* QrViewController.swift */,
78A8733C2877608200690A0B /* QrBackupViewController.swift */,
B21005DA23383664004C70C5 /* SettingsClassicViewController.swift */,
30B0ACF924AB5B99004D5E29 /* SettingsEphemeralMessageController.swift */,
78E45E3921D3CFBC00D4B15E /* SettingsController.swift */,
Expand Down Expand Up @@ -1497,6 +1500,7 @@
30653081254358B10093E196 /* QuoteView.swift in Sources */,
3067AAC62667F3FE00525036 /* ImageFormat.swift in Sources */,
30E348DF24F3F819005C93D1 /* ChatTableView.swift in Sources */,
78A8733D2877608200690A0B /* QrBackupViewController.swift in Sources */,
30EF7308252F6A3300E2C54A /* PaddingTextView.swift in Sources */,
30E348E124F53772005C93D1 /* ImageTextCell.swift in Sources */,
3008CB7624F95B6D00E6A617 /* AudioController.swift in Sources */,
Expand Down
239 changes: 239 additions & 0 deletions deltachat-ios/Controller/QrBackupViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import Foundation
import UIKit
import DcCore
import SDWebImageSVGKitPlugin

protocol QrBackupViewControllerDelegate: NSObject {
func onBackupFinished(sender: QrBackupViewController)
func onBackupStarted()
func isPreviousBackupRunning() -> Bool
func onDismissed()
}

class QrBackupViewController: UIViewController {

private let dcContext: DcContext
private let dcAccounts: DcAccounts
private var dcBackupSender: DcBackupSender?
// this delegate is not weak on purpose: we manually remove the referece once the ViewController has
// been dismissed if no backup is in progress OR if the ongoing backup finished and the
// ViewController has been dismissed in the past
public var delegate: QrBackupViewControllerDelegate?

private lazy var qrContentView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFit
view.translatesAutoresizingMaskIntoConstraints = false
view.accessibilityHint = String.localized("scan_to_transfer")
return view
}()

private lazy var progressContainer: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 20
view.clipsToBounds = true
view.layer.borderColor = DcColors.grey50.cgColor
view.layer.borderWidth = 1
view.backgroundColor = DcColors.defaultInverseColor.withAlphaComponent(0.5)
return view
}()

private lazy var progress: UIActivityIndicatorView = {
let progress = UIActivityIndicatorView(style: .white)
progress.translatesAutoresizingMaskIntoConstraints = false
return progress
}()

private lazy var blurView: UIVisualEffectView = {
let blurEffect = UIBlurEffect(style: .light)
let view = UIVisualEffectView(effect: blurEffect)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()

private var isBackupRunning = false
private var progressObserver: NSObjectProtocol?

init(dcAccounts: DcAccounts) {
self.dcAccounts = dcAccounts
self.dcContext = dcAccounts.getSelected()
super.init(nibName: nil, bundle: nil)
}

required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - lifecycle
override func viewDidLoad() {
super.viewDidLoad()
logger.debug(">>>>>>>>>>>>>>>>> viewDidLoad")
title = String.localized("setup_new_device")
setupSubviews()
view.backgroundColor = DcColors.defaultBackgroundColor
if let delegate = delegate, !delegate.isPreviousBackupRunning() {
logger.debug(">>>> viewDidLoad no previous Backup Running starting Backup")
startBackup()
}
}

override func viewDidDisappear(_ animated: Bool) {
logger.debug(">>>> onViewDidDisappear")
delegate?.onDismissed()
if isBackupRunning {
logger.debug(">>>> stop ongoingProcess")
self.dcContext.stopOngoingProcess()
} else {
delegate = nil
dcBackupSender = nil
logger.debug(">>>> nil delegate")
}
}

public func startBackup() {
let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
if !documents.isEmpty {
self.isBackupRunning = true
logger.debug(">>>> startBackup isBackupRunning: \(isBackupRunning)")
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
self.delegate?.onBackupStarted()
self.dcAccounts.stopIo()
self.dcBackupSender = self.dcContext.send_backup(directory: documents[0], passphrase: nil)
self.dcAccounts.startIo()
self.delegate?.onBackupFinished(sender: self)
DispatchQueue.main.async {
self.isBackupRunning = false
logger.debug(">>>> startBackup finishing - isBackupRunning: \(self.isBackupRunning)")
logger.debug(">>>> startBackup has parent: \(self.parent != nil)")
if self.parent == nil {
logger.debug(">>>> nil delegate")
self.delegate = nil
self.dcBackupSender = nil
} else {
let image = self.getQrImage(svg: self.dcBackupSender?.qr_code(context: self.dcContext))
self.qrContentView.image = image
self.progress.stopAnimating()
self.progressContainer.isHidden = true
}
}
}
} else {
logger.error("document directory not found")
self.dcBackupSender = nil
}
}

// MARK: - setup
private func setupSubviews() {
view.addSubview(qrContentView)
view.addSubview(progressContainer)
progressContainer.addSubview(blurView)
progressContainer.addSubview(progress)
let qrDefaultWidth = qrContentView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor, multiplier: 0.75)
qrDefaultWidth.priority = UILayoutPriority(500)
qrDefaultWidth.isActive = true
let qrMinWidth = qrContentView.widthAnchor.constraint(lessThanOrEqualToConstant: 260)
qrMinWidth.priority = UILayoutPriority(999)
qrMinWidth.isActive = true
view.addConstraints([
qrContentView.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor, multiplier: 1.05),
qrContentView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
qrContentView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
progressContainer.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
progressContainer.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
progressContainer.constraintHeightTo(100),
progressContainer.constraintWidthTo(100),
progress.constraintCenterXTo(progressContainer),
progress.constraintCenterYTo(progressContainer)
])
blurView.fillSuperview()
progressContainer.isHidden = false
progress.startAnimating()
}

private func getQrImage(svg: String?) -> UIImage? {
if let svg = svg {
let svgData = svg.data(using: .utf8)
return SDImageSVGKCoder.shared.decodedImage(with: svgData, options: [:])
}
return nil
}

}


/// Does a best effort attempt to trigger the local network privacy alert.
///
/// It works by sending a UDP datagram to the discard service (port 9) of every
/// IP address associated with a broadcast-capable interface. This should
/// trigger the local network privacy alert, assuming the alert hasn’t already
/// been displayed for this app.
///
/// This code takes a ‘best effort’. It handles errors by ignoring them. As
/// such, there’s guarantee that it’ll actually trigger the alert.
///
/// - note: iOS devices don’t actually run the discard service. I’m using it
/// here because I need a port to send the UDP datagram to and port 9 is
/// always going to be safe (either the discard service is running, in which
/// case it will discard the datagram, or it’s not, in which case the TCP/IP
/// stack will discard it).
///
/// There should be a proper API for this (r. 69157424).
///
/// For more background on this, see [Triggering the Local Network Privacy Alert](https://developer.apple.com/forums/thread/663768).
/// [via https://developer.apple.com/forums/thread/663768 ]
func triggerLocalNetworkPrivacyAlert() {
let sock4 = socket(AF_INET, SOCK_DGRAM, 0)
guard sock4 >= 0 else { return }
defer { close(sock4) }
let sock6 = socket(AF_INET6, SOCK_DGRAM, 0)
guard sock6 >= 0 else { return }
defer { close(sock6) }

let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces()
var message = [UInt8]("!".utf8)
for address in addresses {
address.withUnsafeBytes { buf in
let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self)
let saLen = socklen_t(buf.count)
let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6
_ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen)
}
}
}

/// Returns the addresses of the discard service (port 9) on every
/// broadcast-capable interface.
///
/// Each array entry is contains either a `sockaddr_in` or `sockaddr_in6`.
private func addressesOfDiscardServiceOnBroadcastCapableInterfaces() -> [Data] {
var addrList: UnsafeMutablePointer<ifaddrs>? = nil
let err = getifaddrs(&addrList)
guard err == 0, let start = addrList else { return [] }
defer { freeifaddrs(start) }
return sequence(first: start, next: { $0.pointee.ifa_next })
.compactMap { i -> Data? in
guard
(i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0,
let sa = i.pointee.ifa_addr
else { return nil }
var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len)))
switch CInt(sa.pointee.sa_family) {
case AF_INET:
result.withUnsafeMutableBytes { buf in
let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self)
sin.pointee.sin_port = UInt16(9).bigEndian
}
case AF_INET6:
result.withUnsafeMutableBytes { buf in
let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self)
sin6.pointee.sin6_port = UInt16(9).bigEndian
}
default:
return nil
}
return result
}
}
4 changes: 3 additions & 1 deletion deltachat-ios/Controller/QrPageController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,15 @@ extension QrPageController: UIPageViewControllerDataSource, UIPageViewController
if viewController is QrViewController {
return nil
}

return QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if viewController is QrViewController {
return makeQRReader()
}

return nil
}

Expand Down Expand Up @@ -211,7 +213,7 @@ extension QrPageController: QrCodeReaderDelegate {
}))
present(alert, animated: true, completion: nil)

case DC_QR_ACCOUNT:
case DC_QR_ACCOUNT, DC_QR_BACKUP:
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.appCoordinator.presentWelcomeController(accountCode: code)
}
Expand Down
Loading

0 comments on commit 57ee543

Please sign in to comment.