Skip to content

Commit

Permalink
WIP: Setup Ouisync backend in the extension
Browse files Browse the repository at this point in the history
  • Loading branch information
inetic committed Jul 23, 2024
1 parent 4a48a81 commit 5a17357
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 9 deletions.
3 changes: 2 additions & 1 deletion bindings/dart/lib/internal/message_matcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class MessageMatcher {
final message = deserialize(bytes.sublist(8));

// DEBUG
//print('recv: id: $id, message: $message');
print('recv: id: $id, message: $message');

if (message is! Map) {
return;
Expand Down Expand Up @@ -103,6 +103,7 @@ class MessageMatcher {
}

void _handleResponseSuccess(Completer<Object?> completer, Object? payload) {
print(">>>>>>>>>>>>>>>>> received $payload");
if (payload == "none") {
completer.complete(null);
return;
Expand Down
6 changes: 4 additions & 2 deletions bindings/swift/OuisyncLib/Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.8
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -20,7 +20,9 @@ let package = Package(
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "OuisyncLib",
dependencies: [.product(name:"MessagePack", package: "MessagePack.swift")]),
dependencies: [.product(name:"MessagePack", package: "MessagePack.swift"), "OuisyncLibFFI"]
),
.target(name: "OuisyncLibFFI", dependencies: []),
.testTarget(
name: "OuisyncLibTests",
dependencies: ["OuisyncLib"]),
Expand Down
140 changes: 140 additions & 0 deletions bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncFFI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// File.swift
//
//
// Created by Peter Jankuliak on 19/07/2024.
//

import Foundation
import OuisyncLibFFI

typealias FFISessionKind = UInt8
typealias FFIContext = UnsafeRawPointer
typealias FFICallback = @convention(c) (FFIContext?, UnsafePointer<UInt8>, CUnsignedLongLong) -> Void;
typealias FFISessionCreate = @convention(c) (FFISessionKind, UnsafePointer<UInt8>, UnsafePointer<UInt8>, UnsafeRawPointer?, FFICallback) -> SessionCreateResult;
typealias FFISessionGrab = @convention(c) (UnsafeRawPointer?, FFICallback) -> SessionCreateResult;
typealias FFISessionClose = @convention(c) (SessionHandle, FFIContext?, FFICallback) -> Void;
typealias FFISessionChannelSend = @convention(c) (SessionHandle, UnsafeRawPointer, UInt64) -> Void;

class SessionCreateError : Error, CustomStringConvertible {
let message: String
init(_ message: String) { self.message = message }
var description: String { message }
}

public class OuisyncFFI {
let handle: UnsafeMutableRawPointer
let ffiSessionGrab: FFISessionGrab
let ffiSessionCreate: FFISessionCreate
let ffiSessionChannelSend: FFISessionChannelSend
let ffiSessionClose: FFISessionClose
let sessionKindShared: FFISessionKind = 0;

public init() {
handle = dlopen("libouisync_ffi.dylib", RTLD_NOW)!
ffiSessionGrab = unsafeBitCast(dlsym(handle, "session_grab"), to: FFISessionGrab.self)
ffiSessionChannelSend = unsafeBitCast(dlsym(handle, "session_channel_send"), to: FFISessionChannelSend.self)
ffiSessionClose = unsafeBitCast(dlsym(handle, "session_close"), to: FFISessionClose.self)
ffiSessionCreate = unsafeBitCast(dlsym(handle, "session_create"), to: FFISessionCreate.self)
}

// Blocks until Dart creates a session, then returns it.
func waitForSession(_ context: UnsafeRawPointer, _ callback: FFICallback) async throws -> SessionHandle {
// TODO: Might be worth change the ffi function to call a callback when the session becomes created instead of bussy sleeping.
var elapsed: UInt64 = 0;
while true {
let result = ffiSessionGrab(context, callback)
if result.errorCode == 0 {
NSLog("😀 Got Ouisync session");
return result.session
}
NSLog("🤨 Ouisync session not yet ready. Code: \(result.errorCode) Message:\(String(cString: result.errorMessage!))");

let millisecond: UInt64 = 1_000_000
let second: UInt64 = 1000 * millisecond

var timeout = 200 * millisecond

if elapsed > 10 * second {
timeout = second
}

try await Task.sleep(nanoseconds: timeout)
elapsed += timeout;
}
}

func channelSend(_ session: SessionHandle, _ data: [UInt8]) {
let count = data.count;
data.withUnsafeBufferPointer({ maybePointer in
if let pointer = maybePointer.baseAddress {
ffiSessionChannelSend(session, pointer, UInt64(count))
}
})
}

func closeSession(_ session: SessionHandle) async {
typealias C = CheckedContinuation<Void, Never>

class Context {
let session: SessionHandle
let continuation: C
init(_ session: SessionHandle, _ continuation: C) {
self.session = session
self.continuation = continuation
}
}

await withCheckedContinuation(function: "FFI.closeSession", { continuation in
let context = Self.toRetainedPtr(obj: Context(session, continuation))
let callback: FFICallback = { context, dataPointer, size in
let context: Context = OuisyncFFI.fromRetainedPtr(ptr: context!)
context.continuation.resume()
}
ffiSessionClose(session, context, callback)
})
}

// Retained pointers have their reference counter incremented by 1.
// https://stackoverflow.com/a/33310021/273348
static func toUnretainedPtr<T : AnyObject>(obj : T) -> UnsafeRawPointer {
return UnsafeRawPointer(Unmanaged.passUnretained(obj).toOpaque())
}

static func fromUnretainedPtr<T : AnyObject>(ptr : UnsafeRawPointer) -> T {
return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue()
}

static func toRetainedPtr<T : AnyObject>(obj : T) -> UnsafeRawPointer {
return UnsafeRawPointer(Unmanaged.passRetained(obj).toOpaque())
}

static func fromRetainedPtr<T : AnyObject>(ptr : UnsafeRawPointer) -> T {
return Unmanaged<T>.fromOpaque(ptr).takeRetainedValue()
}
}

// ---------------------------------------------------------------------------------------

typealias Rx = AsyncStream<[UInt8]>
typealias Tx = AsyncStream<[UInt8]>.Continuation

class Wrap<T> {
let inner: T
init(_ inner: T) { self.inner = inner }
}

class Channel {
let rx: Rx
let tx: Tx

init(_ rx: Rx, _ tx: Tx) { self.rx = rx; self.tx = tx }
}

func makeStream() -> (Rx, Tx) {
var continuation: Rx.Continuation!
let stream = Rx() { continuation = $0 }
return (stream, continuation!)
}

// ---------------------------------------------------------------------------------------
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public class IncomingMessage { // TODO: Rename to ResponseMessage
let body: MessagePackValue;
switch payload {
case .response(let response):
body = MessagePackValue.map(["success": response.value])
body = MessagePackValue.map(["success": Self.responseValue(response.value)])
case .notification(let notification):
body = MessagePackValue.map(["notification": notification.value])
case .error(let error):
Expand All @@ -201,6 +201,16 @@ public class IncomingMessage { // TODO: Rename to ResponseMessage
return message
}

static func responseValue(_ value: MessagePackValue) -> MessagePackValue {
switch value {
case .nil: return .string("none")
default:
// The flutter code doesn't read the key which is supposed to be a type,
// would still be nice to have a proper mapping.
return .map(["todo-type": value])
}
}

public static func deserialize(_ bytes: [UInt8]) -> IncomingMessage? {
guard let (id, data) = readMessageId(bytes) else {
return nil
Expand Down
55 changes: 50 additions & 5 deletions bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,54 @@

import Foundation
import MessagePack
import OuisyncLibFFI

public class OuisyncSession {
// Used to send and receive messages from the Ouisync library
let librarySender: OuisyncLibrarySenderProtocol
var sessionHandle: SessionHandle
let ffi: OuisyncFFI

var nextMessageId: MessageId = 0
var pendingResponses: [MessageId: CheckedContinuation<Response, any Error>] = [:]
var notificationSubscriptions: NotificationStream.State = NotificationStream.State()

public init(_ libraryClient: OuisyncLibrarySenderProtocol) {
self.librarySender = libraryClient
fileprivate init(_ sessionHandle: SessionHandle, _ ffi: OuisyncFFI) {
self.sessionHandle = sessionHandle
self.ffi = ffi
}

public static func create(_ configPath: String, _ logPath: String, _ ffi: OuisyncFFI) throws -> OuisyncSession {
let session = OuisyncSession(0, ffi)

let callback: FFICallback = { context, dataPointer, size in
let session: OuisyncSession = OuisyncFFI.fromUnretainedPtr(ptr: context!)
let data = Array(UnsafeBufferPointer(start: dataPointer, count: Int(exactly: size)!))
session.onReceiveDataFromOuisyncLib(data)
}

let result = ffi.ffiSessionCreate(ffi.sessionKindShared, configPath, logPath, OuisyncFFI.toUnretainedPtr(obj: session), callback);

if result.errorCode != 0 {
throw SessionCreateError("Failed to create session, code:\(result.errorCode), message:\(result.errorMessage!)")
}

session.sessionHandle = result.session
return session
}

// Can be called from a separate thread.
public func invoke(_ requestMsg: OutgoingMessage) async -> IncomingMessage {
let responsePayload: IncomingPayload

NSLog("AAAAAA \(requestMsg.request.functionName)")
do {
responsePayload = .response(try await sendRequest(requestMsg.request))
} catch let e as OuisyncError {
responsePayload = .error(e)
} catch let e {
fatalError("Unhandled exception in OuisyncSession.invoke: \(e)")
}

return IncomingMessage(requestMsg.messageId, responsePayload)
}

public func listRepositories() async throws -> [OuisyncRepository] {
Expand All @@ -35,6 +72,7 @@ public class OuisyncSession {
return NotificationStream(subscriptionId, notificationSubscriptions)
}

// Can be called from a separate thread.
internal func sendRequest(_ request: MessageRequest) async throws -> Response {
let messageId = generateMessageId()

Expand All @@ -50,6 +88,7 @@ public class OuisyncSession {
return try await onResponse
}

// Can be called from a separate thread.
fileprivate func generateMessageId() -> MessageId {
synchronized(self) {
let messageId = nextMessageId
Expand All @@ -59,7 +98,13 @@ public class OuisyncSession {
}

fileprivate func sendDataToOuisyncLib(_ data: [UInt8]) {
librarySender.sendDataToOuisyncLib(data);
//librarySender.sendDataToOuisyncLib(data);
let count = data.count;
data.withUnsafeBufferPointer({ maybePointer in
if let pointer = maybePointer.baseAddress {
ffi.ffiSessionChannelSend(sessionHandle, pointer, UInt64(count))
}
})
}

// Use this function to pass data from the backend.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// FileProviderProxy.h
// Runner
//
// Created by Peter Jankuliak on 03/04/2024.
//

#ifndef OuisyncFFI_h
#define OuisyncFFI_h

#include <stdint.h>

typedef uint64_t SessionHandle;

struct SessionCreateResult {
SessionHandle session;
uint16_t errorCode;
const char* errorMessage;
};

typedef struct SessionCreateResult SessionCreateResult;

#endif /* OuisyncFFI_h */
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Blank file because xcode complains if there is nothing to compile.

0 comments on commit 5a17357

Please sign in to comment.