Respond to CR.

This commit is contained in:
Matthew Chen 2018-03-14 10:02:44 -03:00
parent f10b549940
commit 4746948dfe
12 changed files with 186 additions and 194 deletions

View file

@ -18,7 +18,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
var callUIAdapter: CallUIAdapter {
return SignalApp.shared().callUIAdapter
}
let contactsManager: OWSContactsManager
// MARK: Properties
@ -134,7 +134,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
self.call = call
self.thread = TSContactThread.getOrCreateThread(contactId: call.remotePhoneNumber)
super.init(nibName: nil, bundle: nil)
allAudioSources = Set(callUIAdapter.audioService.availableInputs)
assert(callUIAdapter.audioService.delegate == nil)
@ -144,9 +144,9 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
func observeNotifications() {
NotificationCenter.default.addObserver(self,
selector:#selector(didBecomeActive),
name:NSNotification.Name.OWSApplicationDidBecomeActive,
object:nil)
selector: #selector(didBecomeActive),
name: NSNotification.Name.OWSApplicationDidBecomeActive,
object: nil)
}
deinit {
@ -202,8 +202,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
func createViews() {
self.view.isUserInteractionEnabled = true
self.view.addGestureRecognizer(OWSAnyTouchGestureRecognizer(target:self,
action:#selector(didTouchRootView)))
self.view.addGestureRecognizer(OWSAnyTouchGestureRecognizer(target: self,
action: #selector(didTouchRootView)))
// Dark blurred background.
let blurEffect = UIBlurEffect(style: .dark)
@ -252,7 +252,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
contactNameLabel.trailingBuffer = ScaleFromIPhone5(80.0)
// label config
contactNameLabel.font = UIFont.ows_lightFont(withSize:ScaleFromIPhone5To7Plus(32, 40))
contactNameLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(32, 40))
contactNameLabel.textColor = UIColor.white
contactNameLabel.layer.shadowOffset = CGSize.zero
contactNameLabel.layer.shadowOpacity = 0.35
@ -261,7 +261,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
self.view.addSubview(contactNameLabel)
callStatusLabel = UILabel()
callStatusLabel.font = UIFont.ows_regularFont(withSize:ScaleFromIPhone5To7Plus(19, 25))
callStatusLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(19, 25))
callStatusLabel.textColor = UIColor.white
callStatusLabel.layer.shadowOffset = CGSize.zero
callStatusLabel.layer.shadowOpacity = 0.35
@ -287,41 +287,41 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
settingsNagDescriptionLabel = UILabel()
settingsNagDescriptionLabel.text = NSLocalizedString("CALL_VIEW_SETTINGS_NAG_DESCRIPTION_ALL",
comment: "Reminder to the user of the benefits of enabling CallKit and disabling CallKit privacy.")
settingsNagDescriptionLabel.font = UIFont.ows_regularFont(withSize:ScaleFromIPhone5To7Plus(16, 18))
settingsNagDescriptionLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(16, 18))
settingsNagDescriptionLabel.textColor = UIColor.white
settingsNagDescriptionLabel.numberOfLines = 0
settingsNagDescriptionLabel.lineBreakMode = .byWordWrapping
viewStack.addSubview(settingsNagDescriptionLabel)
settingsNagDescriptionLabel.autoPinWidthToSuperview()
settingsNagDescriptionLabel.autoPinEdge(toSuperviewEdge:.top)
settingsNagDescriptionLabel.autoPinEdge(toSuperviewEdge: .top)
let buttonHeight = ScaleFromIPhone5To7Plus(35, 45)
let descriptionVSpacingHeight = ScaleFromIPhone5To7Plus(30, 60)
let callSettingsButton = OWSFlatButton.button(title:NSLocalizedString("CALL_VIEW_SETTINGS_NAG_SHOW_CALL_SETTINGS",
let callSettingsButton = OWSFlatButton.button(title: NSLocalizedString("CALL_VIEW_SETTINGS_NAG_SHOW_CALL_SETTINGS",
comment: "Label for button that shows the privacy settings."),
font:OWSFlatButton.fontForHeight(buttonHeight),
titleColor:UIColor.white,
backgroundColor:UIColor.ows_signalBrandBlue,
target:self,
selector:#selector(didPressShowCallSettings))
font: OWSFlatButton.fontForHeight(buttonHeight),
titleColor: UIColor.white,
backgroundColor: UIColor.ows_signalBrandBlue,
target: self,
selector: #selector(didPressShowCallSettings))
viewStack.addSubview(callSettingsButton)
callSettingsButton.autoSetDimension(.height, toSize:buttonHeight)
callSettingsButton.autoSetDimension(.height, toSize: buttonHeight)
callSettingsButton.autoPinWidthToSuperview()
callSettingsButton.autoPinEdge(.top, to:.bottom, of:settingsNagDescriptionLabel, withOffset:descriptionVSpacingHeight)
callSettingsButton.autoPinEdge(.top, to: .bottom, of: settingsNagDescriptionLabel, withOffset: descriptionVSpacingHeight)
let notNowButton = OWSFlatButton.button(title:NSLocalizedString("CALL_VIEW_SETTINGS_NAG_NOT_NOW_BUTTON",
let notNowButton = OWSFlatButton.button(title: NSLocalizedString("CALL_VIEW_SETTINGS_NAG_NOT_NOW_BUTTON",
comment: "Label for button that dismiss the call view's settings nag."),
font:OWSFlatButton.fontForHeight(buttonHeight),
titleColor:UIColor.white,
backgroundColor:UIColor.ows_signalBrandBlue,
target:self,
selector:#selector(didPressDismissNag))
font: OWSFlatButton.fontForHeight(buttonHeight),
titleColor: UIColor.white,
backgroundColor: UIColor.ows_signalBrandBlue,
target: self,
selector: #selector(didPressDismissNag))
viewStack.addSubview(notNowButton)
notNowButton.autoSetDimension(.height, toSize:buttonHeight)
notNowButton.autoSetDimension(.height, toSize: buttonHeight)
notNowButton.autoPinWidthToSuperview()
notNowButton.autoPinEdge(toSuperviewEdge:.bottom)
notNowButton.autoPinEdge(.top, to:.bottom, of:callSettingsButton, withOffset:12)
notNowButton.autoPinEdge(toSuperviewEdge: .bottom)
notNowButton.autoPinEdge(.top, to: .bottom, of: callSettingsButton, withOffset: 12)
}
func buttonSize() -> CGFloat {
@ -336,31 +336,31 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
// textMessageButton = createButton(imageName:"message-active-wide",
// action:#selector(didPressTextMessage))
audioSourceButton = createButton(imageName:"audio-call-speaker-inactive",
action:#selector(didPressAudioSource))
audioSourceButton = createButton(imageName: "audio-call-speaker-inactive",
action: #selector(didPressAudioSource))
audioSourceButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_AUDIO_SOURCE_LABEL",
comment: "Accessibility label for selection the audio source")
hangUpButton = createButton(imageName:"hangup-active-wide",
action:#selector(didPressHangup))
hangUpButton = createButton(imageName: "hangup-active-wide",
action: #selector(didPressHangup))
hangUpButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_HANGUP_LABEL",
comment: "Accessibility label for hang up call")
audioModeMuteButton = createButton(imageName:"audio-call-mute-inactive",
action:#selector(didPressMute))
audioModeMuteButton = createButton(imageName: "audio-call-mute-inactive",
action: #selector(didPressMute))
audioModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL",
comment: "Accessibility label for muting the microphone")
videoModeMuteButton = createButton(imageName:"video-mute-unselected",
action:#selector(didPressMute))
videoModeMuteButton = createButton(imageName: "video-mute-unselected",
action: #selector(didPressMute))
videoModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL", comment: "Accessibility label for muting the microphone")
audioModeVideoButton = createButton(imageName:"audio-call-video-inactive",
action:#selector(didPressVideo))
audioModeVideoButton = createButton(imageName: "audio-call-video-inactive",
action: #selector(didPressVideo))
audioModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_VIDEO_LABEL", comment: "Accessibility label to switch to video call")
videoModeVideoButton = createButton(imageName:"video-video-unselected",
action:#selector(didPressVideo))
videoModeVideoButton = createButton(imageName: "video-video-unselected",
action: #selector(didPressVideo))
videoModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_AUDIO_LABEL", comment: "Accessibility label to switch to audio only")
setButtonSelectedImage(button: audioModeMuteButton, imageName: "audio-call-mute-active")
@ -368,7 +368,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
setButtonSelectedImage(button: audioModeVideoButton, imageName: "audio-call-video-active")
setButtonSelectedImage(button: videoModeVideoButton, imageName: "video-video-selected")
ongoingCallView = createContainerForCallControls(controlGroups : [
ongoingCallView = createContainerForCallControls(controlGroups: [
[audioModeMuteButton, audioSourceButton, audioModeVideoButton ],
[videoModeMuteButton, hangUpButton, videoModeVideoButton ]
])
@ -379,7 +379,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
let actionSheetController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let dismissAction = UIAlertAction(title: CommonStrings.dismissButton, style: .cancel, handler: nil)
let dismissAction = UIAlertAction(title: CommonStrings.dismissButton, style: .cancel, handler: nil)
actionSheetController.addAction(dismissAction)
let currentAudioSource = callUIAdapter.audioService.currentAudioSource(call: self.call)
@ -402,9 +402,9 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
}
func setButtonSelectedImage(button: UIButton, imageName: String) {
let image = UIImage(named:imageName)
let image = UIImage(named: imageName)
assert(image != nil)
button.setImage(image, for:.selected)
button.setImage(image, for: .selected)
}
func updateAvatarImage() {
@ -413,16 +413,16 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
func createIncomingCallControls() {
acceptIncomingButton = createButton(imageName:"call-active-wide",
action:#selector(didPressAnswerCall))
acceptIncomingButton = createButton(imageName: "call-active-wide",
action: #selector(didPressAnswerCall))
acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL",
comment: "Accessibility label for accepting incoming calls")
declineIncomingButton = createButton(imageName:"hangup-active-wide",
action:#selector(didPressDeclineCall))
declineIncomingButton = createButton(imageName: "hangup-active-wide",
action: #selector(didPressDeclineCall))
declineIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_DECLINE_INCOMING_CALL_LABEL",
comment: "Accessibility label for declining incoming calls")
incomingCallView = createContainerForCallControls(controlGroups : [
incomingCallView = createContainerForCallControls(controlGroups: [
[acceptIncomingButton, declineIncomingButton ]
])
}
@ -432,7 +432,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
self.view.addSubview(containerView)
var rows: [UIView] = []
for controlGroup in controlGroups {
rows.append(rowWithSubviews(subviews:controlGroup))
rows.append(rowWithSubviews(subviews: controlGroup))
}
let rowspacing = ScaleFromIPhone5To7Plus(6, 7)
var prevRow: UIView?
@ -440,29 +440,29 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
containerView.addSubview(row)
row.autoHCenterInSuperview()
if prevRow != nil {
row.autoPinEdge(.top, to:.bottom, of:prevRow!, withOffset:rowspacing)
row.autoPinEdge(.top, to: .bottom, of: prevRow!, withOffset: rowspacing)
}
prevRow = row
}
containerView.setContentHuggingVerticalHigh()
rows.first!.autoPinEdge(toSuperviewEdge:.top)
rows.last!.autoPinEdge(toSuperviewEdge:.bottom)
rows.first!.autoPinEdge(toSuperviewEdge: .top)
rows.last!.autoPinEdge(toSuperviewEdge: .bottom)
return containerView
}
func createButton(imageName: String, action: Selector) -> UIButton {
let image = UIImage(named:imageName)
let image = UIImage(named: imageName)
assert(image != nil)
let button = UIButton()
button.setImage(image, for:.normal)
button.setImage(image, for: .normal)
button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(),
left: buttonInset(),
bottom: buttonInset(),
right: buttonInset())
button.addTarget(self, action:action, for:.touchUpInside)
button.autoSetDimension(.width, toSize:buttonSize())
button.autoSetDimension(.height, toSize:buttonSize())
button.addTarget(self, action: action, for: .touchUpInside)
button.autoSetDimension(.width, toSize: buttonSize())
button.autoSetDimension(.height, toSize: buttonSize())
return button
}
@ -470,7 +470,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
func rowWithSubviews(subviews: [UIView]) -> UIView {
let row = UIView()
row.setContentHuggingVerticalHigh()
row.autoSetDimension(.height, toSize:buttonSize())
row.autoSetDimension(.height, toSize: buttonSize())
if subviews.count > 1 {
// If there's more than one subview in the row,
@ -485,8 +485,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
let spacer = UIView()
spacer.isHidden = true
row.addSubview(spacer)
spacer.autoPinEdge(.left, to:.right, of:lastSubview!)
spacer.autoPinEdge(.right, to:.left, of:subview)
spacer.autoPinEdge(.left, to: .right, of: lastSubview!)
spacer.autoPinEdge(.right, to: .left, of: subview)
spacer.setContentHuggingHorizontalLow()
spacer.autoVCenterInSuperview()
@ -500,8 +500,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
lastSubview = subview
}
subviews.first!.autoPinEdge(toSuperviewEdge:.left)
subviews.last!.autoPinEdge(toSuperviewEdge:.right)
subviews.first!.autoPinEdge(toSuperviewEdge: .left)
subviews.last!.autoPinEdge(toSuperviewEdge: .right)
} else if subviews.count == 1 {
// If there's only one subview in this row, center it.
let subview = subviews.first!
@ -544,25 +544,25 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
blurView.autoPinEdgesToSuperviewEdges()
localVideoView.autoPinTrailingToSuperview(withMargin: videoPreviewHMargin)
localVideoView.autoPinEdge(toSuperviewEdge:.top, withInset:topMargin)
localVideoView.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin)
let localVideoSize = ScaleFromIPhone5To7Plus(80, 100)
localVideoView.autoSetDimension(.width, toSize:localVideoSize)
localVideoView.autoSetDimension(.height, toSize:localVideoSize)
localVideoView.autoSetDimension(.width, toSize: localVideoSize)
localVideoView.autoSetDimension(.height, toSize: localVideoSize)
remoteVideoView.autoPinEdgesToSuperviewEdges()
contactNameLabel.autoPinEdge(toSuperviewEdge:.top, withInset:topMargin)
contactNameLabel.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin)
contactNameLabel.autoPinLeadingToSuperview(withMargin: contactHMargin)
contactNameLabel.setContentHuggingVerticalHigh()
contactNameLabel.setCompressionResistanceHigh()
callStatusLabel.autoPinEdge(.top, to:.bottom, of:contactNameLabel, withOffset:contactVSpacing)
callStatusLabel.autoPinEdge(.top, to: .bottom, of: contactNameLabel, withOffset: contactVSpacing)
callStatusLabel.autoPinLeadingToSuperview(withMargin: contactHMargin)
callStatusLabel.setContentHuggingVerticalHigh()
callStatusLabel.setCompressionResistanceHigh()
contactAvatarContainerView.autoPinEdge(.top, to:.bottom, of:callStatusLabel, withOffset:+avatarTopSpacing)
contactAvatarContainerView.autoPinEdge(.bottom, to:.top, of:ongoingCallView, withOffset:-avatarBottomSpacing)
contactAvatarContainerView.autoPinEdge(.top, to: .bottom, of: callStatusLabel, withOffset: +avatarTopSpacing)
contactAvatarContainerView.autoPinEdge(.bottom, to: .top, of: ongoingCallView, withOffset: -avatarBottomSpacing)
contactAvatarContainerView.autoPinWidthToSuperview(withMargin: avatarTopSpacing)
contactAvatarView.autoCenterInSuperview()
@ -579,19 +579,19 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
}
// Ongoing call controls
ongoingCallView.autoPinEdge(toSuperviewEdge:.bottom, withInset:ongoingBottomMargin)
ongoingCallView.autoPinWidthToSuperview(withMargin:ongoingHMargin)
ongoingCallView.autoPinEdge(toSuperviewEdge: .bottom, withInset: ongoingBottomMargin)
ongoingCallView.autoPinWidthToSuperview(withMargin: ongoingHMargin)
ongoingCallView.setContentHuggingVerticalHigh()
// Incoming call controls
incomingCallView.autoPinEdge(toSuperviewEdge:.bottom, withInset:incomingBottomMargin)
incomingCallView.autoPinWidthToSuperview(withMargin:incomingHMargin)
incomingCallView.autoPinEdge(toSuperviewEdge: .bottom, withInset: incomingBottomMargin)
incomingCallView.autoPinWidthToSuperview(withMargin: incomingHMargin)
incomingCallView.setContentHuggingVerticalHigh()
// Settings nag views
settingsNagView.autoPinEdge(toSuperviewEdge:.bottom, withInset:settingsNagBottomMargin)
settingsNagView.autoPinWidthToSuperview(withMargin:settingsNagHMargin)
settingsNagView.autoPinEdge(.top, to:.bottom, of:callStatusLabel)
settingsNagView.autoPinEdge(toSuperviewEdge: .bottom, withInset: settingsNagBottomMargin)
settingsNagView.autoPinWidthToSuperview(withMargin: settingsNagHMargin)
settingsNagView.autoPinEdge(.top, to: .bottom, of: callStatusLabel)
}
updateRemoteVideoLayout()
@ -648,11 +648,11 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
return NSLocalizedString("IN_CALL_SECURING", comment: "Call setup status label")
case .connected:
let callDuration = call.connectionDuration()
let callDurationDate = Date(timeIntervalSinceReferenceDate:callDuration)
let callDurationDate = Date(timeIntervalSinceReferenceDate: callDuration)
if dateFormatter == nil {
dateFormatter = DateFormatter()
dateFormatter!.dateFormat = "HH:mm:ss"
dateFormatter!.timeZone = TimeZone(identifier:"UTC")!
dateFormatter!.timeZone = TimeZone(identifier: "UTC")!
}
var formattedDate = dateFormatter!.string(from: callDurationDate)
if formattedDate.hasPrefix("00:") {
@ -771,10 +771,10 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
switch callState {
case .remoteHangup, .remoteBusy, .localFailure:
Logger.debug("\(TAG) dismissing after delay because new state is \(callState)")
dismissIfPossible(shouldDelay:true)
dismissIfPossible(shouldDelay: true)
case .localHangup:
Logger.debug("\(TAG) dismissing immediately from local hangup")
dismissIfPossible(shouldDelay:false)
dismissIfPossible(shouldDelay: false)
default: break
}
@ -782,9 +782,9 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
if callDurationTimer == nil {
let kDurationUpdateFrequencySeconds = 1 / 20.0
callDurationTimer = WeakTimer.scheduledTimer(timeInterval: TimeInterval(kDurationUpdateFrequencySeconds),
target:self,
userInfo:nil,
repeats:true) {[weak self] _ in
target: self,
userInfo: nil,
repeats: true) {[weak self] _ in
self?.updateCallDuration()
}
}
@ -831,7 +831,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
callUIAdapter.localHangupCall(call)
dismissIfPossible(shouldDelay:false)
dismissIfPossible(shouldDelay: false)
}
func didPressMute(sender muteButton: UIButton) {
@ -861,7 +861,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
func didPressTextMessage(sender button: UIButton) {
Logger.info("\(TAG) called \(#function)")
dismissIfPossible(shouldDelay:false)
dismissIfPossible(shouldDelay: false)
}
func didPressAnswerCall(sender: UIButton) {
@ -885,7 +885,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
callUIAdapter.declineCall(call)
dismissIfPossible(shouldDelay:false)
dismissIfPossible(shouldDelay: false)
}
func didPressShowCallSettings(sender: UIButton) {
@ -901,7 +901,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
// Construct the "settings" view & push the "privacy settings" view.
let navigationController = AppSettingsViewController.inModalNavigationController()
navigationController.pushViewController(PrivacySettingsTableViewController(), animated:false)
navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false)
fromViewController?.present(navigationController, animated: true, completion: nil)
})
@ -1022,7 +1022,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
updateRemoteVideoLayout()
}
internal func dismissIfPossible(shouldDelay: Bool, ignoreNag ignoreNagParam: Bool = false, completion: (() -> Swift.Void)? = nil) {
internal func dismissIfPossible(shouldDelay: Bool, ignoreNag ignoreNagParam: Bool = false, completion: (() -> Void)? = nil) {
callUIAdapter.audioService.delegate = nil
let ignoreNag: Bool = {
@ -1071,11 +1071,11 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
hasDismissed = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
guard let strongSelf = self else { return }
strongSelf.dismiss(animated: true, completion:completion)
strongSelf.dismiss(animated: true, completion: completion)
}
} else {
hasDismissed = true
self.dismiss(animated: false, completion:completion)
self.dismiss(animated: false, completion: completion)
}
}

View file

@ -194,10 +194,10 @@ enum GiphyAssetRequestState: UInt {
redundantLength = segmentStart + segmentLength - contentLength
segmentStart = contentLength - segmentLength
}
let assetSegment = GiphyAssetSegment(index:index,
segmentStart:segmentStart,
segmentLength:segmentLength,
redundantLength:redundantLength)
let assetSegment = GiphyAssetSegment(index: index,
segmentStart: segmentStart,
segmentLength: segmentLength,
redundantLength: redundantLength)
segments.append(assetSegment)
nextSegmentStart = segmentStart + segmentLength
index += 1
@ -222,7 +222,7 @@ enum GiphyAssetRequestState: UInt {
public func firstWaitingSegment() -> GiphyAssetSegment? {
AssertIsOnMainThread()
return firstSegmentWithState(state:.waiting)
return firstSegmentWithState(state: .waiting)
}
public func downloadingSegmentsCount() -> UInt {
@ -287,8 +287,8 @@ enum GiphyAssetRequestState: UInt {
Logger.verbose("\(TAG) filePath: \(filePath).")
do {
try assetData.write(to: NSURL.fileURL(withPath:filePath), options: .atomicWrite)
let asset = GiphyAsset(rendition: rendition, filePath : filePath)
try assetData.write(to: NSURL.fileURL(withPath: filePath), options: .atomicWrite)
let asset = GiphyAsset(rendition: rendition, filePath: filePath)
return asset
} catch let error as NSError {
owsFail("\(GiphyAsset.TAG) file write failed: \(filePath), \(error)")
@ -360,7 +360,7 @@ enum GiphyAssetRequestState: UInt {
DispatchQueue.global().async {
do {
let fileManager = FileManager.default
try fileManager.removeItem(atPath:filePathCopy)
try fileManager.removeItem(atPath: filePathCopy)
} catch let error as NSError {
owsFail("\(GiphyAsset.TAG) file cleanup failed: \(filePathCopy), \(error)")
}
@ -406,7 +406,7 @@ class LRUCache<KeyType: Hashable & Equatable, ValueType> {
return
}
cacheOrder.removeFirst()
cacheMap.removeValue(forKey:staleKey)
cacheMap.removeValue(forKey: staleKey)
}
}
}
@ -468,9 +468,9 @@ extension URLSessionTask {
configuration.urlCache = nil
configuration.requestCachePolicy = .reloadIgnoringCacheData
configuration.httpMaximumConnectionsPerHost = 10
let session = URLSession(configuration:configuration,
delegate:self,
delegateQueue:nil)
let session = URLSession(configuration: configuration,
delegate: self,
delegateQueue: nil)
return session
}()
@ -481,7 +481,7 @@ extension URLSessionTask {
// evacuated from the cache; if a cache consumer (e.g. view) is
// still using the asset, the asset won't be deleted on disk until
// it is no longer in use.
private var assetMap = LRUCache<NSURL, GiphyAsset>(maxSize:100)
private var assetMap = LRUCache<NSURL, GiphyAsset>(maxSize: 100)
// TODO: We could use a proper queue, e.g. implemented with a linked
// list.
private var assetRequestQueue = [GiphyAssetRequest]()
@ -496,7 +496,7 @@ extension URLSessionTask {
failure:@escaping ((GiphyAssetRequest) -> Void)) -> GiphyAssetRequest? {
AssertIsOnMainThread()
if let asset = assetMap.get(key:rendition.url) {
if let asset = assetMap.get(key: rendition.url) {
// Synchronous cache hit.
Logger.verbose("\(self.TAG) asset cache hit: \(rendition.url)")
success(nil, asset)
@ -507,10 +507,10 @@ extension URLSessionTask {
//
// Asset requests are done queued and performed asynchronously.
Logger.verbose("\(self.TAG) asset cache miss: \(rendition.url)")
let assetRequest = GiphyAssetRequest(rendition:rendition,
priority:priority,
success:success,
failure:failure)
let assetRequest = GiphyAssetRequest(rendition: rendition,
priority: priority,
success: success,
failure: failure)
assetRequestQueue.append(assetRequest)
// Process the queue (which may start this request)
// asynchronously so that the caller has time to store
@ -540,8 +540,8 @@ extension URLSessionTask {
// Move write off main thread.
DispatchQueue.global().async {
guard let asset = assetRequest.writeAssetToFile(gifFolderPath:self.gifFolderPath) else {
self.segmentRequestDidFail(assetRequest:assetRequest, assetSegment:assetSegment)
guard let asset = assetRequest.writeAssetToFile(gifFolderPath: self.gifFolderPath) else {
self.segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
self.assetRequestDidSucceed(assetRequest: assetRequest, asset: asset)
@ -555,9 +555,9 @@ extension URLSessionTask {
private func assetRequestDidSucceed(assetRequest: GiphyAssetRequest, asset: GiphyAsset) {
DispatchQueue.main.async {
self.assetMap.set(key:assetRequest.rendition.url, value:asset)
self.removeAssetRequestFromQueue(assetRequest:assetRequest)
assetRequest.requestDidSucceed(asset:asset)
self.assetMap.set(key: assetRequest.rendition.url, value: asset)
self.removeAssetRequestFromQueue(assetRequest: assetRequest)
assetRequest.requestDidSucceed(asset: asset)
}
}
@ -567,14 +567,14 @@ extension URLSessionTask {
DispatchQueue.main.async {
assetSegment.state = .failed
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest:assetRequest)
self.assetRequestDidFail(assetRequest: assetRequest)
}
}
private func assetRequestDidFail(assetRequest: GiphyAssetRequest) {
DispatchQueue.main.async {
self.removeAssetRequestFromQueue(assetRequest:assetRequest)
self.removeAssetRequestFromQueue(assetRequest: assetRequest)
assetRequest.requestDidFail()
}
}
@ -616,17 +616,17 @@ extension URLSessionTask {
guard UIApplication.shared.applicationState == .active else {
// If app is not active, fail the asset request.
assetRequest.state = .failed
assetRequestDidFail(assetRequest:assetRequest)
assetRequestDidFail(assetRequest: assetRequest)
processRequestQueueSync()
return
}
if let asset = assetMap.get(key:assetRequest.rendition.url) {
if let asset = assetMap.get(key: assetRequest.rendition.url) {
// Deferred cache hit, avoids re-downloading assets that were
// downloaded while this request was queued.
assetRequest.state = .complete
assetRequestDidSucceed(assetRequest : assetRequest, asset: asset)
assetRequestDidSucceed(assetRequest: assetRequest, asset: asset)
return
}
@ -639,11 +639,11 @@ extension URLSessionTask {
request.httpMethod = "HEAD"
request.httpShouldUsePipelining = true
let task = giphyDownloadSession.dataTask(with:request, completionHandler: { data, response, error -> Void in
let task = giphyDownloadSession.dataTask(with: request, completionHandler: { data, response, error -> Void in
if let data = data, data.count > 0 {
owsFail("\(self.TAG) HEAD request has unexpected body: \(data.count).")
}
self.handleAssetSizeResponse(assetRequest:assetRequest, response:response, error:error)
self.handleAssetSizeResponse(assetRequest: assetRequest, response: response, error: error)
})
assetRequest.contentLengthTask = task
task.resume()
@ -660,7 +660,7 @@ extension URLSessionTask {
request.httpShouldUsePipelining = true
let rangeHeaderValue = "bytes=\(assetSegment.segmentStart)-\(assetSegment.segmentStart + assetSegment.segmentLength - 1)"
request.addValue(rangeHeaderValue, forHTTPHeaderField: "Range")
let task = giphyDownloadSession.dataTask(with:request)
let task = giphyDownloadSession.dataTask(with: request)
task.assetRequest = assetRequest
task.assetSegment = assetSegment
assetSegment.task = task
@ -674,31 +674,31 @@ extension URLSessionTask {
private func handleAssetSizeResponse(assetRequest: GiphyAssetRequest, response: URLResponse?, error: Error?) {
guard error == nil else {
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest:assetRequest)
self.assetRequestDidFail(assetRequest: assetRequest)
return
}
guard let httpResponse = response as? HTTPURLResponse else {
owsFail("\(self.TAG) Asset size response is invalid.")
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest:assetRequest)
self.assetRequestDidFail(assetRequest: assetRequest)
return
}
guard let contentLengthString = httpResponse.allHeaderFields["Content-Length"] as? String else {
owsFail("\(self.TAG) Asset size response is missing content length.")
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest:assetRequest)
self.assetRequestDidFail(assetRequest: assetRequest)
return
}
guard let contentLength = Int(contentLengthString) else {
owsFail("\(self.TAG) Asset size response has unparsable content length.")
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest:assetRequest)
self.assetRequestDidFail(assetRequest: assetRequest)
return
}
guard contentLength > 0 else {
owsFail("\(self.TAG) Asset size response has invalid content length.")
assetRequest.state = .failed
self.assetRequestDidFail(assetRequest:assetRequest)
self.assetRequestDidFail(assetRequest: assetRequest)
return
}
@ -777,13 +777,13 @@ extension URLSessionTask {
let assetSegment = dataTask.assetSegment
guard !assetRequest.wasCancelled else {
dataTask.cancel()
segmentRequestDidFail(assetRequest:assetRequest, assetSegment:assetSegment)
segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
assetSegment.append(data:data)
assetSegment.append(data: data)
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Swift.Void) {
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
completionHandler(nil)
}
@ -795,32 +795,32 @@ extension URLSessionTask {
let assetSegment = task.assetSegment
guard !assetRequest.wasCancelled else {
task.cancel()
segmentRequestDidFail(assetRequest:assetRequest, assetSegment:assetSegment)
segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
if let error = error {
Logger.error("\(TAG) download failed with error: \(error)")
segmentRequestDidFail(assetRequest:assetRequest, assetSegment:assetSegment)
segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
guard let httpResponse = task.response as? HTTPURLResponse else {
Logger.error("\(TAG) missing or unexpected response: \(String(describing: task.response))")
segmentRequestDidFail(assetRequest:assetRequest, assetSegment:assetSegment)
segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
let statusCode = httpResponse.statusCode
guard statusCode >= 200 && statusCode < 400 else {
Logger.error("\(TAG) response has invalid status code: \(statusCode)")
segmentRequestDidFail(assetRequest:assetRequest, assetSegment:assetSegment)
segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
guard assetSegment.totalDataSize() == assetSegment.segmentLength else {
Logger.error("\(TAG) segment is missing data: \(statusCode)")
segmentRequestDidFail(assetRequest:assetRequest, assetSegment:assetSegment)
segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment)
return
}
segmentRequestDidSucceed(assetRequest : assetRequest, assetSegment: assetSegment)
segmentRequestDidSucceed(assetRequest: assetRequest, assetSegment: assetSegment)
}
// MARK: Temp Directory
@ -835,20 +835,20 @@ extension URLSessionTask {
let fileManager = FileManager.default
// Try to delete existing folder if necessary.
if fileManager.fileExists(atPath:dirPath) {
try fileManager.removeItem(atPath:dirPath)
if fileManager.fileExists(atPath: dirPath) {
try fileManager.removeItem(atPath: dirPath)
gifFolderPath = dirPath
}
// Try to create folder if necessary.
if !fileManager.fileExists(atPath:dirPath) {
try fileManager.createDirectory(atPath:dirPath,
withIntermediateDirectories:true,
attributes:nil)
if !fileManager.fileExists(atPath: dirPath) {
try fileManager.createDirectory(atPath: dirPath,
withIntermediateDirectories: true,
attributes: nil)
gifFolderPath = dirPath
}
// Don't back up Giphy downloads.
OWSFileSystem.protectFileOrFolder(atPath:dirPath)
OWSFileSystem.protectFileOrFolder(atPath: dirPath)
} catch let error as NSError {
owsFail("\(GiphyAsset.TAG) ensureTempFolder failed: \(dirPath), \(error)")
gifFolderPath = tempDirPath

View file

@ -364,6 +364,7 @@ NS_ASSUME_NONNULL_BEGIN
[self ensureBackupExportState];
} else {
DDLogWarn(@"%@ obsolete job succeeded: %@", self.logTag, [backupJob class]);
return;
}
@ -387,7 +388,7 @@ NS_ASSUME_NONNULL_BEGIN
[self ensureBackupExportState];
} else {
DDLogInfo(@"%@ stale backup job failed.", self.logTag);
DDLogInfo(@"%@ obsolete backup job failed.", self.logTag);
return;
}

View file

@ -21,8 +21,8 @@ import CloudKit
@objc
public class func saveTestFileToCloud(fileUrl: URL,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
saveFileToCloud(fileUrl: fileUrl,
recordName: NSUUID().uuidString,
recordType: signalBackupRecordType,
@ -36,8 +36,8 @@ import CloudKit
// complete.
@objc
public class func saveEphemeralDatabaseFileToCloud(fileUrl: URL,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
saveFileToCloud(fileUrl: fileUrl,
recordName: "ephemeralFile-\(NSUUID().uuidString)",
recordType: signalBackupRecordType,
@ -50,9 +50,9 @@ import CloudKit
// backups can reuse the same record.
@objc
public class func savePersistentFileOnceToCloud(fileId: String,
fileUrlBlock: @escaping (Swift.Void) -> URL?,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
fileUrlBlock: @escaping (()) -> URL?,
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
saveFileOnceToCloud(recordName: "persistentFile-\(fileId)",
recordType: signalBackupRecordType,
fileUrlBlock: fileUrlBlock,
@ -62,8 +62,8 @@ import CloudKit
@objc
public class func upsertManifestFileToCloud(fileUrl: URL,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
// We want to use a well-known record id and type for manifest files.
upsertFileToCloud(fileUrl: fileUrl,
recordName: manifestRecordName,
@ -76,8 +76,8 @@ import CloudKit
public class func saveFileToCloud(fileUrl: URL,
recordName: String,
recordType: String,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
let recordID = CKRecordID(recordName: recordName)
let record = CKRecord(recordType: recordType, recordID: recordID)
let asset = CKAsset(fileURL: fileUrl)
@ -90,8 +90,8 @@ import CloudKit
@objc
public class func saveRecordToCloud(record: CKRecord,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
let myContainer = CKContainer.default()
let privateDatabase = myContainer.privateCloudDatabase
@ -117,8 +117,8 @@ import CloudKit
@objc
public class func deleteRecordFromCloud(recordName: String,
success: @escaping (Swift.Void) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (()) -> Void,
failure: @escaping (Error) -> Void) {
let recordID = CKRecordID(recordName: recordName)
@ -146,8 +146,8 @@ import CloudKit
public class func upsertFileToCloud(fileUrl: URL,
recordName: String,
recordType: String,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
checkForFileInCloud(recordName: recordName,
success: { (record) in
@ -178,9 +178,9 @@ import CloudKit
@objc
public class func saveFileOnceToCloud(recordName: String,
recordType: String,
fileUrlBlock: @escaping (Swift.Void) -> URL?,
success: @escaping (String) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
fileUrlBlock: @escaping (()) -> URL?,
success: @escaping (String) -> Void,
failure: @escaping (Error) -> Void) {
checkForFileInCloud(recordName: recordName,
success: { (record) in
@ -208,8 +208,8 @@ import CloudKit
}
private class func checkForFileInCloud(recordName: String,
success: @escaping (CKRecord?) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (CKRecord?) -> Void,
failure: @escaping (Error) -> Void) {
let recordId = CKRecordID(recordName: recordName)
let fetchOperation = CKFetchRecordsOperation(recordIDs: [recordId ])
// Don't download the file; we're just using the fetch to check whether or
@ -246,8 +246,8 @@ import CloudKit
}
@objc
public class func checkForManifestInCloud(success: @escaping (Bool) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
public class func checkForManifestInCloud(success: @escaping (Bool) -> Void,
failure: @escaping (Error) -> Void) {
checkForFileInCloud(recordName: manifestRecordName,
success: { (record) in
@ -257,8 +257,8 @@ import CloudKit
}
@objc
public class func fetchAllRecordNames(success: @escaping ([String]) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
public class func fetchAllRecordNames(success: @escaping ([String]) -> Void,
failure: @escaping (Error) -> Void) {
let query = CKQuery(recordType: signalBackupRecordType, predicate: NSPredicate(value: true))
// Fetch the first page of results for this query.
@ -272,8 +272,8 @@ import CloudKit
private class func fetchAllRecordNamesStep(query: CKQuery,
previousRecordNames: [String],
cursor: CKQueryCursor?,
success: @escaping ([String]) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping ([String]) -> Void,
failure: @escaping (Error) -> Void) {
var allRecordNames = previousRecordNames
@ -314,8 +314,8 @@ import CloudKit
@objc
public class func downloadManifestFromCloud(
success: @escaping (Data) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (Data) -> Void,
failure: @escaping (Error) -> Void) {
downloadDataFromCloud(recordName: manifestRecordName,
success: success,
failure: failure)
@ -323,8 +323,8 @@ import CloudKit
@objc
public class func downloadDataFromCloud(recordName: String,
success: @escaping (Data) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (Data) -> Void,
failure: @escaping (Error) -> Void) {
downloadFromCloud(recordName: recordName,
success: { (asset) in
@ -346,8 +346,8 @@ import CloudKit
@objc
public class func downloadFileFromCloud(recordName: String,
toFileUrl: URL,
success: @escaping (Swift.Void) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (()) -> Void,
failure: @escaping (Error) -> Void) {
downloadFromCloud(recordName: recordName,
success: { (asset) in
@ -367,8 +367,8 @@ import CloudKit
}
private class func downloadFromCloud(recordName: String,
success: @escaping (CKAsset) -> Swift.Void,
failure: @escaping (Error) -> Swift.Void) {
success: @escaping (CKAsset) -> Void,
failure: @escaping (Error) -> Void) {
let recordId = CKRecordID(recordName: recordName)
let fetchOperation = CKFetchRecordsOperation(recordIDs: [recordId ])
@ -400,7 +400,7 @@ import CloudKit
}
@objc
public class func checkCloudKitAccess(completion: @escaping (Bool) -> Swift.Void) {
public class func checkCloudKitAccess(completion: @escaping (Bool) -> Void) {
CKContainer.default().accountStatus(completionHandler: { (accountStatus, error) in
DispatchQueue.main.async {
switch accountStatus {

View file

@ -389,7 +389,6 @@ typedef void (^DebugLogUploadFailure)(DebugLogUploader *uploader, NSError *error
[tempDirectory stringByAppendingPathComponent:[logsName stringByAppendingPathExtension:@"zip"]];
NSString *zipDirPath = [tempDirectory stringByAppendingPathComponent:logsName];
[OWSFileSystem ensureDirectoryExists:zipDirPath];
[OWSFileSystem protectFileOrFolderAtPath:zipDirPath];
NSArray<NSString *> *logFilePaths = DebugLogger.sharedLogger.allLogFilePaths;
if (logFilePaths.count < 1) {

View file

@ -42,7 +42,7 @@ class VerifyingTSAccountManager: FailingTSAccountManager {
success()
}
override func registerForManualMessageFetching(success successBlock: @escaping () -> Swift.Void, failure failureBlock: @escaping (Error) -> Swift.Void) {
override func registerForManualMessageFetching(success successBlock: @escaping () -> Void, failure failureBlock: @escaping (Error) -> Void) {
successBlock()
}
}

View file

@ -1124,8 +1124,6 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640;
profileAvatarsDirPath = OWSProfileManager.sharedDataProfileAvatarsDirPath;
[OWSFileSystem ensureDirectoryExists:profileAvatarsDirPath];
[OWSFileSystem protectFileOrFolderAtPath:profileAvatarsDirPath];
});
return profileAvatarsDirPath;
}

View file

@ -39,7 +39,6 @@ const NSUInteger kMaxDebugLogFileSize = 1024 * 1024 * 3;
{
NSString *dirPath = [[OWSFileSystem cachesDirectoryPath] stringByAppendingPathComponent:@"Logs"];
[OWSFileSystem ensureDirectoryExists:dirPath];
[OWSFileSystem protectFileOrFolderAtPath:dirPath];
return dirPath;
}
@ -48,7 +47,6 @@ const NSUInteger kMaxDebugLogFileSize = 1024 * 1024 * 3;
NSString *dirPath =
[[OWSFileSystem appSharedDataDirectoryPath] stringByAppendingPathComponent:@"ShareExtensionLogs"];
[OWSFileSystem ensureDirectoryExists:dirPath];
[OWSFileSystem protectFileOrFolderAtPath:dirPath];
return dirPath;
}

View file

@ -55,7 +55,7 @@ import Foundation
}
@objc
public class func showErrorAlert(message message: String) {
public class func showErrorAlert(message: String) {
self.showAlert(title: CommonStrings.errorAlertTitle, message: message, buttonTitle: nil)
}

View file

@ -210,8 +210,6 @@ NS_ASSUME_NONNULL_BEGIN
attachmentsFolder = TSAttachmentStream.sharedDataAttachmentsDirPath;
[OWSFileSystem ensureDirectoryExists:attachmentsFolder];
[OWSFileSystem protectFileOrFolderAtPath:attachmentsFolder];
});
return attachmentsFolder;
}

View file

@ -21,8 +21,6 @@ typedef NSData *_Nullable (^BackupStorageKeySpecBlock)(void);
- (instancetype)initBackupStorageWithDatabaseDirPath:(NSString *)databaseDirPath
keySpecBlock:(BackupStorageKeySpecBlock)keySpecBlock NS_DESIGNATED_INITIALIZER;
- (YapDatabaseConnection *)dbConnection;
- (void)runSyncRegistrations;
- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion;
- (BOOL)areAllRegistrationsComplete;

View file

@ -210,7 +210,7 @@ NS_ASSUME_NONNULL_BEGIN
if (exists) {
OWSAssert(isDirectory);
return YES;
return [self protectFileOrFolderAtPath:dirPath];
} else {
DDLogInfo(@"%@ Creating directory at: %@", self.logTag, dirPath);
@ -223,7 +223,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSFail(@"%@ Failed to create directory: %@, error: %@", self.logTag, dirPath, error);
return NO;
}
return YES;
return [self protectFileOrFolderAtPath:dirPath];
}
}