Skip to content

Commit

Permalink
Swift Concurrency (may not work as expected?)
Browse files Browse the repository at this point in the history
  • Loading branch information
danpashin committed Jun 11, 2024
1 parent 55a7279 commit f1675c2
Show file tree
Hide file tree
Showing 34 changed files with 343 additions and 306 deletions.
6 changes: 3 additions & 3 deletions twackup-gui/Twackup.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1410;
LastUpgradeCheck = 1410;
LastUpgradeCheck = 1600;
TargetAttributes = {
0B8661642930ADA300EA4301 = {
CreatedOnToolsVersion = 14.1;
Expand Down Expand Up @@ -802,7 +802,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(TARGET_NAME)/Bridging-Header.h";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand Down Expand Up @@ -849,7 +849,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(TARGET_NAME)/Bridging-Header.h";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
5 changes: 4 additions & 1 deletion twackup-gui/Twackup/Sources/App Lifecycle/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let preferences = Preferences()

private(set) lazy var mainModel: MainModel = {
let rootfullDir = "/var/lib/dpkg"
let rootlessDir = "/var/jb/var/lib/dpkg"
Expand All @@ -19,7 +21,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
dpkgDir = rootlessDir
}

return MainModel(database: Database(), dpkg: Dpkg(path: dpkgDir))
let dpkg = Dpkg(path: dpkgDir, preferences: preferences)
return MainModel(database: Database(), dpkg: dpkg, preferences: preferences )
}()

func application(
Expand Down
52 changes: 34 additions & 18 deletions twackup-gui/Twackup/Sources/FFI Models/Dpkg.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,41 @@

import Sentry

protocol DpkgProgressDelegate: AnyObject {
protocol DpkgProgress: Actor {
/// Being called when package is ready to start it's rebuilding operation
func startProcessing(package: Package)

/// Being called when package just finished it's rebuilding operation
func finishedProcessing(package: Package, debPath: URL)
func finishedProcessing(package: Package, debURL: URL)

/// Being called when all packages are processed
func finishedAll()
}

class Dpkg {
actor Dpkg {
enum MessageLevel: UInt8 {
case debug
case info
case warning
case error
}

static let defaultSaveDirectory: URL = {
nonisolated static let defaultSaveDirectory: URL = {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}()

/// Delegate which will be called on build state update
weak var buildProgressDelegate: DpkgProgressDelegate?
weak var buildProgressDelegate: DpkgProgress?

private let innerDpkg: UnsafeMutablePointer<TwDpkg_t>

init(path: String, lock: Bool = false) {
private let preferences: Preferences

private var buildParameters = TwBuildParameters_t()

init(path: String, preferences: Preferences, lock: Bool = false) {
innerDpkg = tw_init(path, lock)
self.preferences = preferences
}

deinit {
Expand Down Expand Up @@ -76,19 +81,21 @@ class Dpkg {
/// - outDir: Directory that will contain debs of packages
/// - Returns: Array with results.
/// Every result contains full deb path if rebuild is success or error if not
func rebuild(packages: [FFIPackage], outDir: URL = defaultSaveDirectory) throws -> [Result<URL, NSError>] {
let preferences = Preferences()
func rebuild(packages: [FFIPackage], outDir: URL = defaultSaveDirectory) async throws -> [Result<URL, Error>] {
var ffiResults = slice_boxed_TwPackagesRebuildResult()
withUnsafeMutablePointer(to: &ffiResults) { buildParameters.results = $0 }

var buildParameters = TwBuildParameters_t()
buildParameters.functions = createProgressFuncs()

// Since Swift enums have values equal to FFI ones, it is safe to just pass them by without any checks
buildParameters.preferences.compression_level = .init(UInt32(preferences.compression.level.rawValue))
buildParameters.preferences.compression_type = .init(UInt32(preferences.compression.kind.rawValue))
buildParameters.preferences.follow_symlinks = preferences.followSymlinks
buildParameters.preferences.compression_level = await .init(UInt32(preferences.compression.level.rawValue))
buildParameters.preferences.compression_type = await .init(UInt32(preferences.compression.kind.rawValue))
buildParameters.preferences.follow_symlinks = await preferences.followSymlinks

var ffiResults = slice_boxed_TwPackagesRebuildResult()
withUnsafeMutablePointer(to: &ffiResults) { buildParameters.results = $0 }
outDir.path.utf8CString.withUnsafeBufferPointer { buffer in
buildParameters.out_dir = UnsafePointer(strdup(buffer.baseAddress!))
}
defer { buildParameters.out_dir.deallocate() }

let status = outDir.path.utf8CString.withUnsafeBufferPointer { pointer in
// safe to unwrap?
Expand All @@ -110,7 +117,7 @@ class Dpkg {
])
}

let results: [Result<URL, NSError>] = UnsafeBufferPointer(start: ffiResults.ptr, count: ffiResults.len)
let results: [Result<URL, Error>] = UnsafeBufferPointer(start: ffiResults.ptr, count: ffiResults.len)
.map { result in
if !result.success {
return .failure(NSError(domain: "ru.danpashin.twackup", code: 0, userInfo: [
Expand Down Expand Up @@ -138,7 +145,9 @@ class Dpkg {
guard let context, let package, let ffiPackage = FFIPackage(package.pointee) else { return }

let dpkg = Unmanaged<Dpkg>.fromOpaque(context).takeUnretainedValue()
dpkg.buildProgressDelegate?.startProcessing(package: ffiPackage)
Task(priority: .utility) {
await dpkg.buildProgressDelegate?.startProcessing(package: ffiPackage)
}
}
funcs.finished_processing = { context, package, debPath in
// Package is a stack pointer so it doesn't need to be released
Expand All @@ -149,14 +158,21 @@ class Dpkg {
else { return }

let dpkg = Unmanaged<Dpkg>.fromOpaque(context).takeUnretainedValue()
dpkg.buildProgressDelegate?.finishedProcessing(package: ffiPackage, debPath: URL(fileURLWithPath: debPath))
Task(priority: .utility) {
let debURL = URL(fileURLWithPath: debPath)
await dpkg.buildProgressDelegate?.finishedProcessing(package: ffiPackage, debURL: debURL)
}
}
funcs.finished_all = { context in
guard let context else { return }
let dpkg = Unmanaged<Dpkg>.fromOpaque(context).takeUnretainedValue()
dpkg.buildProgressDelegate?.finishedAll()
Task(priority: .utility) {
await dpkg.buildProgressDelegate?.finishedAll()
}
}

return funcs
}
}

extension UnsafeMutablePointer<TwDpkg_t>: @unchecked @retroactive Sendable {}
19 changes: 11 additions & 8 deletions twackup-gui/Twackup/Sources/FFI Models/FFILogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
// Created by Daniil on 09.12.2022.
//

protocol FFILoggerSubscriber: NSObjectProtocol {
func log(message: FFILogger.Message, level: FFILogger.Level)
protocol FFILoggerSubscriber: NSObjectProtocol, Sendable {
func log(message: FFILogger.Message, level: FFILogger.Level) async

func flush()
func flush() async
}

class FFILogger {
actor FFILogger {
@objc
enum Level: UInt32 {
case off
Expand Down Expand Up @@ -43,16 +43,19 @@ class FFILogger {
}

let logger = Unmanaged<FFILogger>.fromOpaque(context).takeUnretainedValue()
logger.log(message: Message(text: msgText, target: msgTarget), level: level)

Task(priority: .utility) {
await logger.log(message: Message(text: msgText, target: msgTarget), level: level)
}
}

tw_enable_logging(funcs, .init(UInt32(level.rawValue)))
}

func log(message: Message, level: Level) {
DispatchQueue.global().async { [self] in
for subscriber in subscribers {
subscriber.log(message: message, level: level)
for subscriber in subscribers {
Task {
await subscriber.log(message: message, level: level)
}
}
}
Expand Down
24 changes: 13 additions & 11 deletions twackup-gui/Twackup/Sources/FFI Models/Package/FFIPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by Daniil on 24.11.2022.
//

class FFIPackage: Package {
final class FFIPackage: Package, Sendable {
let pkg: TwPackage_t

let id: String
Expand All @@ -18,36 +18,36 @@ class FFIPackage: Package {
return pkg.section.swiftSection
}

private(set) lazy var icon: URL? = {
var icon: URL? {
guard let icon = String(ffiSlice: pkg.get_field(pkg.inner_ptr, TW_PACKAGE_FIELD_ICON)) else { return nil }

return URL(string: icon)
}()
}

private(set) lazy var depiction: URL? = {
var depiction: URL? {
var depiction = String(ffiSlice: pkg.get_field(pkg.inner_ptr, TW_PACKAGE_FIELD_DEPICTION))
if depiction == nil {
depiction = String(ffiSlice: pkg.get_field(pkg.inner_ptr, TW_PACKAGE_FIELD_HOMEPAGE))
}

guard let depiction else { return nil }
return URL(string: depiction)
}()
}

private(set) lazy var humanDescription: String? = {
var humanDescription: String? {
String(ffiSlice: pkg.get_field(pkg.inner_ptr, TW_PACKAGE_FIELD_DESCRIPTION))
}()
}

private(set) lazy var architecture: String? = {
var architecture: String? {
String(ffiSlice: pkg.get_field(pkg.inner_ptr, TW_PACKAGE_FIELD_ARCHITECTURE))
}()
}

private(set) lazy var installedSize: Int64 = {
var installedSize: Int64 {
let field = TW_PACKAGE_FIELD_INSTALLED_SIZE
guard let stringSize = String(ffiSlice: pkg.get_field(pkg.inner_ptr, field)) else { return 0 }
guard let size = Int64(stringSize) else { return 0 }
return size * 1_000
}()
}

init?(_ pkg: TwPackage_t) {
self.pkg = pkg
Expand All @@ -72,3 +72,5 @@ class FFIPackage: Package {
id == other.id && version == other.version
}
}

extension TwPackage_t: @unchecked Sendable {}
64 changes: 37 additions & 27 deletions twackup-gui/Twackup/Sources/Models/Database/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,27 @@
// Created by Daniil on 28.11.2022.
//

import CoreData
@preconcurrency import CoreData
import os

class Database {
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Twackup")
container.loadPersistentStores { _, error in
final class Database: Sendable {
private let persistentContainer: NSPersistentContainer

private let context: NSManagedObjectContext

init() {
persistentContainer = NSPersistentContainer(name: "Twackup")
persistentContainer.loadPersistentStores { _, error in
if let error = error as NSError? {
FFILogger.shared.log("Unresolved error \(error), \(error.userInfo)")
Task(priority: .utility) {
await FFILogger.shared.log("Unresolved error \(error), \(error.userInfo)")
}
}
}
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

private lazy var context: NSManagedObjectContext = persistentContainer.newBackgroundContext()
context = persistentContainer.newBackgroundContext()
}

private func saveContext(_ context: NSManagedObjectContext) {
if !context.hasChanges { return }
Expand Down Expand Up @@ -49,8 +54,8 @@ class Database {
return nil
}

func addBuildedPackages(_ packages: [BuildedPackage], completion: (() -> Void)? = nil) {
persistentContainer.performBackgroundTask { context in
func add(packages: [BuildedPackage]) async {
await background { context in
var index = 0
let total = packages.count

Expand All @@ -71,39 +76,35 @@ class Database {
// swiftlint:enable trailing_closure

_ = self.execute(request: request, context: context)

completion?()
}
}

func fetchBuildedPackages() -> [DebPackage] {
(try? self.context.fetch(DebPackage.fetchRequest())) ?? []
func fetchPackages() throws -> [DebPackage] {
try self.context.fetch(DebPackage.fetchRequest())
}

func fetch(package: Package) -> DebPackage? {
try? context.fetch(DebPackage.fetchRequest(package: package)).first
func fetch(package: Package) throws -> DebPackage? {
let possiblePackages = try context.fetch(DebPackage.fetchRequest(package: package))
return possiblePackages.first
}

func delete(package: DebPackage, completion: (() -> Void)? = nil) {
delete(packages: [package], completion: completion)
func delete(package: DebPackage) async {
await delete(packages: [package])
}

func delete(packages: [DebPackage], completion: (() -> Void)? = nil) {
persistentContainer.performBackgroundTask { context in
func delete(packages: [DebPackage]) async {
await background { context in
if packages.isEmpty {
completion?()
return
}

let request = NSBatchDeleteRequest(objectIDs: packages.map { $0.objectID })
_ = self.execute(request: request, context: context)

completion?()
}
}

func delete(packages: [Package], completion: (() -> Void)? = nil) {
delete(packages: packages.compactMap { $0 as? DebPackage }, completion: completion)
func delete(packages: [Package]) async {
await delete(packages: packages.compactMap { $0 as? DebPackage })
}

func packagesSize() -> Int64 {
Expand All @@ -126,4 +127,13 @@ class Database {

return size
}

private func background(_ work: @escaping (NSManagedObjectContext) -> Void) async {
await withCheckedContinuation { continuation in
persistentContainer.performBackgroundTask { context in
work(context)
continuation.resume()
}
}
}
}
Loading

0 comments on commit f1675c2

Please sign in to comment.