fix warnings
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

This commit is contained in:
wt
2026-08-10 17:18:01 +07:00
parent e14dc25891
commit dee1eab897
4 changed files with 288 additions and 210 deletions
+267 -195
View File
@@ -3,7 +3,7 @@ import Combine
import Foundation
#if os(iOS)
import UIKit
import UIKit
#endif
@MainActor
@@ -92,14 +92,14 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
}
}
#if os(iOS)
try AVAudioSession.sharedInstance().setCategory(
.playAndRecord,
mode: .videoRecording,
options: [.defaultToSpeaker, .allowBluetoothHFP]
)
try AVAudioSession.sharedInstance().setActive(true)
#endif
#if os(iOS)
try AVAudioSession.sharedInstance().setCategory(
.playAndRecord,
mode: .videoRecording,
options: [.defaultToSpeaker, .allowBluetoothHFP]
)
try AVAudioSession.sharedInstance().setActive(true)
#endif
let graph = try await configureCaptureGraph()
guard !Task.isCancelled, sessionGeneration == generation else {
@@ -118,10 +118,12 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
func start() throws {
guard lifecycle == .prepared,
isPrepared,
session.isRunning else { throw VideoNoteRecorderError.notPrepared }
isPrepared,
session.isRunning
else { throw VideoNoteRecorderError.notPrepared }
guard !movieOutput.isRecording,
activeRecordingURL == nil else {
activeRecordingURL == nil
else {
throw VideoNoteRecorderError.recordingBusy
}
@@ -168,8 +170,9 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
guard let self else { return }
await self.stopMovieOutput()
guard !Task.isCancelled,
self.lifecycle == .stopping,
let url = self.activeRecordingURL else { return }
self.lifecycle == .stopping,
let url = self.activeRecordingURL
else { return }
self.scheduleFinalizationTimeout(for: url)
}
case .stopping:
@@ -180,48 +183,51 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
}
func switchCamera() async throws {
#if os(iOS)
guard isPrepared,
lifecycle == .prepared,
let currentInput = cameraInput else {
throw VideoNoteRecorderError.notPrepared
}
let nextPosition: AVCaptureDevice.Position = isUsingFrontCamera ? .back : .front
guard let camera = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: nextPosition
) else {
throw VideoNoteRecorderError.alternateCameraUnavailable
}
let nextInput = try AVCaptureDeviceInput(device: camera)
let session = session
try await withCheckedThrowingContinuation {
(continuation: CheckedContinuation<Void, Error>) in
sessionQueue.async {
session.beginConfiguration()
session.removeInput(currentInput)
guard session.canAddInput(nextInput) else {
if session.canAddInput(currentInput) {
session.addInput(currentInput)
}
session.commitConfiguration()
continuation.resume(throwing: VideoNoteRecorderError.configurationFailed)
return
}
session.addInput(nextInput)
session.commitConfiguration()
continuation.resume()
#if os(iOS)
guard isPrepared,
lifecycle == .prepared,
let currentInput = cameraInput
else {
throw VideoNoteRecorderError.notPrepared
}
let nextPosition: AVCaptureDevice.Position = isUsingFrontCamera ? .back : .front
guard
let camera = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: nextPosition
)
else {
throw VideoNoteRecorderError.alternateCameraUnavailable
}
}
cameraInput = nextInput
isUsingFrontCamera = nextPosition == .front
configureVideoConnection()
#else
throw VideoNoteRecorderError.alternateCameraUnavailable
#endif
let nextInput = try AVCaptureDeviceInput(device: camera)
let session = session
try await withCheckedThrowingContinuation {
(continuation: CheckedContinuation<Void, Error>) in
sessionQueue.async {
session.beginConfiguration()
session.removeInput(currentInput)
guard session.canAddInput(nextInput) else {
if session.canAddInput(currentInput) {
session.addInput(currentInput)
}
session.commitConfiguration()
continuation.resume(throwing: VideoNoteRecorderError.configurationFailed)
return
}
session.addInput(nextInput)
session.commitConfiguration()
continuation.resume()
}
}
cameraInput = nextInput
isUsingFrontCamera = nextPosition == .front
configureVideoConnection()
#else
throw VideoNoteRecorderError.alternateCameraUnavailable
#endif
}
func toggleMicrophoneMuted() {
@@ -261,7 +267,8 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
private func requestStop(afterMinimumDuration minimumDuration: TimeInterval) {
guard isRecording,
lifecycle == .starting || lifecycle == .recording else { return }
lifecycle == .starting || lifecycle == .recording
else { return }
stopRequested = true
requestedMinimumDuration = max(
requestedMinimumDuration,
@@ -271,11 +278,13 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
}
private func recordingDidStart(url: URL) {
guard VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: activeRecordingURL,
outputURL: url
), isRecording,
lifecycle == .starting || lifecycle == .stopping else { return }
guard
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: activeRecordingURL,
outputURL: url
), isRecording,
lifecycle == .starting || lifecycle == .stopping
else { return }
startupTimeoutTask?.cancel()
startupTimeoutTask = nil
@@ -293,11 +302,12 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
guard let self else { return }
await self.stopMovieOutput()
guard !Task.isCancelled,
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
self.scheduleFinalizationTimeout(for: url)
}
return
@@ -319,9 +329,10 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
private func scheduleStopAfterActualStartIfNeeded() {
guard stopRequested,
lifecycle == .recording,
startedAt != nil,
let url = activeRecordingURL else { return }
lifecycle == .recording,
startedAt != nil,
let url = activeRecordingURL
else { return }
stopRequestTask?.cancel()
let minimumDuration = requestedMinimumDuration
@@ -331,20 +342,22 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
stopRequestTask = Task { @MainActor [weak self] in
while true {
guard !Task.isCancelled,
let self,
self.lifecycle == .recording,
VideoNoteRecordingLifecycle.acceptsCompletion(
let self,
self.lifecycle == .recording,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
let snapshot = await self.movieOutputSnapshot()
guard !Task.isCancelled,
self.lifecycle == .recording,
VideoNoteRecordingLifecycle.acceptsCompletion(
self.lifecycle == .recording,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
if !snapshot.isRecording {
// AVFoundation stopped without the user's stop reaching
@@ -374,30 +387,34 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
}
guard !Task.isCancelled,
let self,
self.lifecycle == .recording,
VideoNoteRecordingLifecycle.acceptsCompletion(
let self,
self.lifecycle == .recording,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
self.lifecycle = .stopping
await self.stopMovieOutput()
guard !Task.isCancelled,
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
self.scheduleFinalizationTimeout(for: url)
}
}
private func recordingDidFinish(url: URL, error: Error?) {
guard VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: activeRecordingURL,
outputURL: url
) else {
guard
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: activeRecordingURL,
outputURL: url
)
else {
// A delayed callback from an earlier attempt must not tear down a
// newer capture graph. Do not delete it here: macOS can spell the
// same temporary URL through a filesystem alias, and preserving a
@@ -433,10 +450,11 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
fallbackDuration: fallbackDuration
)
guard !Task.isCancelled,
VideoNoteRecordingLifecycle.acceptsCompletion(
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
self.fileValidationTask = nil
if self.discardCurrentRecording {
@@ -446,29 +464,33 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
}
if let inspection,
VideoNoteRecordingCompletionPolicy.wasRequestedOrReachedLimit(
stopRequested: wasStopRequested,
error: error,
recordedDuration: inspection.duration,
maximumDuration: VideoNoteStopPolicy.maximumCaptureDuration
),
VideoNoteStopPolicy.isValidFinalDuration(inspection.duration) {
self.finishAndReset(.success(Recording(
url: url,
duration: inspection.duration
)))
VideoNoteRecordingCompletionPolicy.wasRequestedOrReachedLimit(
stopRequested: wasStopRequested,
error: error,
recordedDuration: inspection.duration,
maximumDuration: VideoNoteStopPolicy.maximumCaptureDuration
),
VideoNoteStopPolicy.isValidFinalDuration(inspection.duration)
{
self.finishAndReset(
.success(
Recording(
url: url,
duration: inspection.duration
)))
return
}
try? FileManager.default.removeItem(at: url)
let failure: Error
if !wasStopRequested,
!VideoNoteRecordingCompletionPolicy.wasRequestedOrReachedLimit(
stopRequested: false,
error: error,
recordedDuration: inspection?.duration ?? fallbackDuration,
maximumDuration: VideoNoteStopPolicy.maximumCaptureDuration
) {
!VideoNoteRecordingCompletionPolicy.wasRequestedOrReachedLimit(
stopRequested: false,
error: error,
recordedDuration: inspection?.duration ?? fallbackDuration,
maximumDuration: VideoNoteStopPolicy.maximumCaptureDuration
)
{
failure = VideoNoteRecorderError.recordingInterrupted
} else if !VideoNoteRecordingCompletionPolicy.shouldKeepOutput(for: error), let error {
failure = error
@@ -501,10 +523,11 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
return await withCheckedContinuation { continuation in
sessionQueue.async {
let seconds = CMTimeGetSeconds(output.recordedDuration)
continuation.resume(returning: MovieOutputSnapshot(
isRecording: output.isRecording,
recordedDuration: seconds.isFinite && seconds > 0 ? seconds : 0
))
continuation.resume(
returning: MovieOutputSnapshot(
isRecording: output.isRecording,
recordedDuration: seconds.isFinite && seconds > 0 ? seconds : 0
))
}
}
}
@@ -532,21 +555,23 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
nanoseconds: VideoNoteStopPolicy.startupTimeoutNanoseconds
)
guard !Task.isCancelled,
let self,
self.lifecycle == .starting,
VideoNoteRecordingLifecycle.acceptsCompletion(
let self,
self.lifecycle == .starting,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
self.lifecycle = .stopping
await self.stopMovieOutput()
guard !Task.isCancelled,
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
self.scheduleFinalizationTimeout(for: url)
}
}
@@ -558,12 +583,13 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
nanoseconds: VideoNoteStopPolicy.finalizationTimeoutNanoseconds
)
guard !Task.isCancelled,
let self,
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
let self,
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
// A few macOS camera drivers stop the file output but delay its
// delegate callback until the capture session itself is flushed.
@@ -574,42 +600,47 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
nanoseconds: VideoNoteStopPolicy.finalizationRecoveryGraceNanoseconds
)
guard !Task.isCancelled,
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
let fallbackDuration = self.measuredWallClockDuration
let inspection = await self.fileInspector.inspect(
url: url,
fallbackDuration: fallbackDuration
)
guard !Task.isCancelled,
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
self.lifecycle == .stopping,
VideoNoteRecordingLifecycle.acceptsCompletion(
activeURL: self.activeRecordingURL,
outputURL: url
) else { return }
)
else { return }
self.finalizationTimeoutTask = nil
if self.discardCurrentRecording {
try? FileManager.default.removeItem(at: url)
self.finishAndReset(nil)
} else if let inspection,
VideoNoteRecordingCompletionPolicy.wasRequestedOrReachedLimit(
stopRequested: self.stopRequested,
error: nil,
recordedDuration: inspection.duration,
maximumDuration: VideoNoteStopPolicy.maximumCaptureDuration
),
VideoNoteStopPolicy.isValidFinalDuration(inspection.duration) {
VideoNoteRecordingCompletionPolicy.wasRequestedOrReachedLimit(
stopRequested: self.stopRequested,
error: nil,
recordedDuration: inspection.duration,
maximumDuration: VideoNoteStopPolicy.maximumCaptureDuration
),
VideoNoteStopPolicy.isValidFinalDuration(inspection.duration)
{
// Some macOS camera drivers finalize the .mov but omit the
// delegate callback. A playable file is more authoritative
// than the missing callback and remains safe to send.
self.finishAndReset(.success(Recording(
url: url,
duration: inspection.duration
)))
self.finishAndReset(
.success(
Recording(
url: url,
duration: inspection.duration
)))
} else if !self.stopRequested {
try? FileManager.default.removeItem(at: url)
self.finishAndReset(.failure(VideoNoteRecorderError.recordingInterrupted))
@@ -653,9 +684,10 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
isRecording = false
isUsingFrontCamera = true
isMicrophoneMuted = false
#if os(iOS)
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
#endif
#if os(iOS)
try? AVAudioSession.sharedInstance().setActive(
false, options: .notifyOthersOnDeactivation)
#endif
}
private func waitForPreviousRecordingToFinish() async throws {
@@ -673,29 +705,61 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
private func configureVideoConnection() {
guard let connection = movieOutput.connection(with: .video) else { return }
#if os(iOS)
configureInteroperableVideoCodec(for: connection)
#endif
#if os(iOS)
configureInteroperableVideoCodec(for: connection)
#endif
if connection.isVideoMirroringSupported {
connection.automaticallyAdjustsVideoMirroring = false
connection.isVideoMirrored = isUsingFrontCamera
}
#if os(iOS)
if connection.isVideoOrientationSupported {
connection.videoOrientation = Self.currentVideoOrientation
}
#endif
#if os(iOS)
if #available(iOS 17.0, *) {
let angle = videoRotationAngle
if connection.isVideoRotationAngleSupported(angle) {
connection.videoRotationAngle = angle
}
} else {
if connection.isVideoOrientationSupported {
connection.videoOrientation = Self.currentVideoOrientation
}
}
#endif
}
#if os(iOS)
private func configureInteroperableVideoCodec(for connection: AVCaptureConnection) {
guard movieOutput.availableVideoCodecTypes.contains(.h264) else { return }
movieOutput.setOutputSettings(
[AVVideoCodecKey: AVVideoCodecType.h264],
for: connection
)
}
#endif
#if os(iOS)
private var videoRotationAngle: CGFloat {
switch UIDevice.current.orientation {
case .portraitUpsideDown: return 180
case .landscapeLeft: return 90
case .landscapeRight: return 270
default: return 0
}
}
@available(iOS, deprecated: 17.0)
private static var currentVideoOrientation: AVCaptureVideoOrientation {
switch UIDevice.current.orientation {
case .portraitUpsideDown:
return .portraitUpsideDown
case .landscapeLeft:
return .landscapeRight
case .landscapeRight:
return .landscapeLeft
default:
return .portrait
}
}
#endif
#if os(iOS)
private func configureInteroperableVideoCodec(for connection: AVCaptureConnection) {
guard movieOutput.availableVideoCodecTypes.contains(.h264) else { return }
movieOutput.setOutputSettings(
[AVVideoCodecKey: AVVideoCodecType.h264],
for: connection
)
}
#endif
private func resetCaptureGraph() {
let session = session
@@ -728,19 +792,21 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
sessionQueue.async {
session.beginConfiguration()
do {
session.sessionPreset = session.canSetSessionPreset(.vga640x480)
session.sessionPreset =
session.canSetSessionPreset(.vga640x480)
? .vga640x480
: .medium
#if os(iOS)
let camera = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: .front
) ?? AVCaptureDevice.default(for: .video)
#else
let camera = AVCaptureDevice.default(for: .video)
#endif
#if os(iOS)
let camera =
AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: .front
) ?? AVCaptureDevice.default(for: .video)
#else
let camera = AVCaptureDevice.default(for: .video)
#endif
guard let camera else {
throw VideoNoteRecorderError.cameraUnavailable
}
@@ -768,11 +834,12 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
preferredTimescale: 600
)
session.commitConfiguration()
continuation.resume(returning: CaptureGraph(
cameraInput: cameraInput,
microphoneInput: microphoneInput,
usesFrontCamera: camera.position == .front
))
continuation.resume(
returning: CaptureGraph(
cameraInput: cameraInput,
microphoneInput: microphoneInput,
usesFrontCamera: camera.position == .front
))
} catch {
session.commitConfiguration()
continuation.resume(throwing: error)
@@ -792,8 +859,9 @@ final class VideoNoteRecorder: NSObject, ObservableObject, AVCaptureFileOutputRe
}
}
guard !Task.isCancelled,
sessionGeneration == generation,
session.isRunning else {
sessionGeneration == generation,
session.isRunning
else {
throw CancellationError()
}
}
@@ -824,16 +892,18 @@ private actor VideoNoteFileInspector {
try? await Task.sleep(nanoseconds: delay)
}
guard let values = try? url.resourceValues(forKeys: [.isRegularFileKey, .fileSizeKey]),
values.isRegularFile == true,
let size = values.fileSize,
size > 1_024 else { continue }
values.isRegularFile == true,
let size = values.fileSize,
size > 1_024
else { continue }
let asset = AVURLAsset(url: url)
guard let duration = try? await asset.load(.duration),
duration.seconds.isFinite,
duration.seconds > 0.04,
let tracks = try? await asset.loadTracks(withMediaType: .video),
!tracks.isEmpty else { continue }
duration.seconds.isFinite,
duration.seconds > 0.04,
let tracks = try? await asset.loadTracks(withMediaType: .video),
!tracks.isEmpty
else { continue }
return Inspection(duration: duration.seconds)
}
@@ -842,9 +912,10 @@ private actor VideoNoteFileInspector {
// of macOS camera drivers that delay movie metadata longer than the
// delegate callback itself.
guard fallbackDuration > 0.25,
let values = try? url.resourceValues(forKeys: [.fileSizeKey]),
let size = values.fileSize,
size > 16_384 else { return nil }
let values = try? url.resourceValues(forKeys: [.fileSizeKey]),
let size = values.fileSize,
size > 16_384
else { return nil }
return Inspection(duration: fallbackDuration)
}
}
@@ -872,20 +943,20 @@ enum VideoNoteStopPolicy {
}
#if os(iOS)
private extension VideoNoteRecorder {
static var currentVideoOrientation: AVCaptureVideoOrientation {
switch UIDevice.current.orientation {
case .portraitUpsideDown:
return .portraitUpsideDown
case .landscapeLeft:
return .landscapeRight
case .landscapeRight:
return .landscapeLeft
default:
return .portrait
extension VideoNoteRecorder {
fileprivate static var currentVideoOrientation: AVCaptureVideoOrientation {
switch UIDevice.current.orientation {
case .portraitUpsideDown:
return .portraitUpsideDown
case .landscapeLeft:
return .landscapeRight
case .landscapeRight:
return .landscapeLeft
default:
return .portrait
}
}
}
}
#endif
private enum VideoNoteRecorderError: LocalizedError {
@@ -920,7 +991,8 @@ private enum VideoNoteRecorderError: LocalizedError {
case .recordingBusy:
return "Предыдущая запись ещё завершается. Подождите секунду и повторите."
case .recordingInterrupted:
return "Камера прервала запись раньше времени. Видеосообщение не отправлено. Повторите запись."
return
"Камера прервала запись раньше времени. Видеосообщение не отправлено. Повторите запись."
case .finalizationTimedOut:
return "Камера не смогла завершить видеофайл. Повторите запись."
case .emptyRecording:
+1 -1
View File
@@ -889,7 +889,7 @@ private final class LumaJingleSession: JingleSession {
// MARK: - Live WebRTC call
private final class LumaWebRTCCall: NSObject, @unchecked Sendable, RTCPeerConnectionDelegate {
private final class LumaWebRTCCall: NSObject, RTCPeerConnectionDelegate, @unchecked Sendable {
let id = UUID()
let startedAt = Date()
unowned let engine: LumaCallEngine
+7 -7
View File
@@ -6,17 +6,17 @@ import Martin
final class LumaRoomStore: RoomStore {
typealias Room = RoomBase
private let dispatcher = QueueDispatcher(label: "LumaRoomStore")
private let queue = DispatchQueue(label: "LumaRoomStore")
private var roomsByJID: [BareJID: RoomBase] = [:]
func rooms(for context: Context) -> [RoomBase] {
dispatcher.sync {
queue.sync {
Array(roomsByJID.values)
}
}
func room(for context: Context, with jid: BareJID) -> RoomBase? {
dispatcher.sync {
queue.sync {
roomsByJID[jid]
}
}
@@ -26,8 +26,8 @@ final class LumaRoomStore: RoomStore {
with jid: BareJID,
nickname: String,
password: String?
) -> ConversationCreateResult<RoomBase> {
dispatcher.sync {
) -> ConversationCreateResult {
queue.sync {
if let room = roomsByJID[jid] {
return .found(room)
}
@@ -36,7 +36,7 @@ final class LumaRoomStore: RoomStore {
jid: jid,
nickname: nickname,
password: password,
dispatcher: dispatcher
dispatcher: queue
)
roomsByJID[jid] = room
return .created(room)
@@ -44,7 +44,7 @@ final class LumaRoomStore: RoomStore {
}
func close(room: RoomBase) -> Bool {
dispatcher.sync {
queue.sync {
guard roomsByJID[room.jid] === room else { return false }
roomsByJID.removeValue(forKey: room.jid)
return true
+13 -7
View File
@@ -1838,7 +1838,7 @@ final class XMPPService {
makeMembersOnly: Bool
) async throws -> Bool {
let roomJID = JID(room.jid)
let configuration: JabberDataElement = try await withCheckedThrowingContinuation {
let configuration: DataForm = try await withCheckedThrowingContinuation {
continuation in
muc.getRoomConfiguration(roomJid: roomJID) { result in
switch result {
@@ -1849,25 +1849,31 @@ final class XMPPService {
}
var supportsNonAnonymousRooms = false
if let field: ListSingleField = configuration.getField(named: "muc#roomconfig_whois") {
if let field: DataForm.ListSingleField = configuration.getField(
named: "muc#roomconfig_whois")
{
field.value = "anyone"
supportsNonAnonymousRooms = true
} else if let field: TextSingleField = configuration.getField(named: "muc#roomconfig_whois")
} else if let field: DataForm.TextSingleField = configuration.getField(
named: "muc#roomconfig_whois")
{
field.value = "anyone"
supportsNonAnonymousRooms = true
}
if let field: ListMultiField = configuration.getField(named: "muc#roomconfig_getmemberlist")
if let field: DataForm.ListMultiField = configuration.getField(
named: "muc#roomconfig_getmemberlist")
{
field.value = Array(Set(field.value + ["moderator", "participant"])).sorted()
}
if makeMembersOnly,
let field: BooleanField = configuration.getField(named: "muc#roomconfig_membersonly")
let field: DataForm.BooleanField = configuration.getField(
named: "muc#roomconfig_membersonly")
{
field.value = true
}
if makeMembersOnly,
let field: BooleanField = configuration.getField(named: "muc#roomconfig_persistentroom")
let field: DataForm.BooleanField = configuration.getField(
named: "muc#roomconfig_persistentroom")
{
field.value = true
}
@@ -1876,7 +1882,7 @@ final class XMPPService {
(continuation: CheckedContinuation<Void, Error>) in
muc.setRoomConfiguration(roomJid: roomJID, configuration: configuration) { result in
switch result {
case .success(let value): continuation.resume(returning: value)
case .success: continuation.resume()
case .failure(let error): continuation.resume(throwing: error)
}
}