Skip to content

Commit

Permalink
Fix adding to database
Browse files Browse the repository at this point in the history
  • Loading branch information
danpashin committed Jun 14, 2024
1 parent da4ca4c commit 32dc60f
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 80 deletions.
4 changes: 4 additions & 0 deletions twackup-gui/Twackup.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
0BAC44D629562C6B0038074C /* DpkgListModel+DZNEmptyDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BAC44D529562C6B0038074C /* DpkgListModel+DZNEmptyDataSet.swift */; };
0BAC44D92956EFCB0038074C /* LogViewController+DZNEmptyDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BAC44D82956EFCB0038074C /* LogViewController+DZNEmptyDataSet.swift */; };
0BB369E52952EF6B004AAFFF /* ScrollableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB369E42952EF6B004AAFFF /* ScrollableViewController.swift */; };
0BBD2FDF2C1C67D500438457 /* DpkgProgressNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BBD2FDE2C1C67D500438457 /* DpkgProgressNotifier.swift */; };
0BE2E0B22C1B346B00CADFE3 /* SwiftConversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE2E0B12C1B346B00CADFE3 /* SwiftConversions.swift */; };
0BE78040294DC60C00AE77CE /* DpkgListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE7803F294DC60C00AE77CE /* DpkgListModel.swift */; };
0BF07A502941027800EEC8FC /* SplitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BF07A4F2941027800EEC8FC /* SplitController.swift */; };
Expand Down Expand Up @@ -140,6 +141,7 @@
0BAC44D529562C6B0038074C /* DpkgListModel+DZNEmptyDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DpkgListModel+DZNEmptyDataSet.swift"; sourceTree = "<group>"; };
0BAC44D82956EFCB0038074C /* LogViewController+DZNEmptyDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LogViewController+DZNEmptyDataSet.swift"; sourceTree = "<group>"; };
0BB369E42952EF6B004AAFFF /* ScrollableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableViewController.swift; sourceTree = "<group>"; };
0BBD2FDE2C1C67D500438457 /* DpkgProgressNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DpkgProgressNotifier.swift; sourceTree = "<group>"; };
0BE2E0B12C1B346B00CADFE3 /* SwiftConversions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftConversions.swift; sourceTree = "<group>"; };
0BE7803F294DC60C00AE77CE /* DpkgListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DpkgListModel.swift; sourceTree = "<group>"; };
0BF07A4F2941027800EEC8FC /* SplitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -254,6 +256,7 @@
children = (
0BF07A61294335F700EEC8FC /* FFILogger.swift */,
0B8661882930AF7100EA4301 /* Dpkg.swift */,
0BBD2FDE2C1C67D500438457 /* DpkgProgressNotifier.swift */,
0BE2E0B12C1B346B00CADFE3 /* SwiftConversions.swift */,
0B88C3052930B2EB00BA5312 /* Package */,
);
Expand Down Expand Up @@ -661,6 +664,7 @@
0BF07A502941027800EEC8FC /* SplitController.swift in Sources */,
0B88C31C2930DE8C00BA5312 /* KeyValueLabelView.swift in Sources */,
0B85ABB5294C80B200B6C5CF /* DebTableViewCell.swift in Sources */,
0BBD2FDF2C1C67D500438457 /* DpkgProgressNotifier.swift in Sources */,
0B434F9E2935E97200311FB6 /* DatabasePackageDetailedView.swift in Sources */,
0B2636DF2948B867003EA806 /* Preferences.swift in Sources */,
0BE2E0B22C1B346B00CADFE3 /* SwiftConversions.swift in Sources */,
Expand Down
71 changes: 9 additions & 62 deletions twackup-gui/Twackup/Sources/FFI Models/Dpkg.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@

import Sentry

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, debURL: URL)

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

actor Dpkg {
enum MessageLevel: UInt8 {
case debug
Expand All @@ -30,15 +19,14 @@ actor Dpkg {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}()

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

private let innerDpkg: UnsafeMutablePointer<TwDpkg_t>

private let preferences: Preferences

private var buildParameters = TwBuildParameters_t()

nonisolated let progressNotifier = DpkgProgressNotifier()

init(path: String, preferences: Preferences, lock: Bool = false) {
innerDpkg = tw_init(path, lock)
self.preferences = preferences
Expand Down Expand Up @@ -78,7 +66,7 @@ actor Dpkg {
var ffiResults = slice_boxed_TwPackagesRebuildResult()
withUnsafeMutablePointer(to: &ffiResults) { buildParameters.results = $0 }

buildParameters.functions = createProgressFuncs()
buildParameters.functions = progressNotifier.ffiFunctions

// Since Swift enums have values equal to FFI ones, it is safe to just pass them by without any checks
buildParameters.preferences.compression_level = await .init(UInt32(preferences.compression.level.rawValue))
Expand All @@ -90,7 +78,7 @@ actor Dpkg {
}
defer { buildParameters.out_dir.deallocate() }

let status = packages
let status = packages
.map { $0.inner }
.map { Optional($0) } // Apple moment
.withUnsafeBufferPointer { pointer in
Expand All @@ -110,6 +98,7 @@ actor Dpkg {

let results: [Result<URL, Error>] = UnsafeBufferPointer(start: ffiResults.ptr, count: ffiResults.len)
.map { result in
// package is not used yet. When it will be - should call `tw_package_retain` on it
if !result.success {
return .failure(NSError(domain: "ru.danpashin.twackup", code: 0, userInfo: [
NSLocalizedDescriptionKey: "\(String(ffiSlice: result.error) ?? "")"
Expand All @@ -118,59 +107,17 @@ actor Dpkg {

// safe to unwrap here 'cause Rust string is UTF-8 encoded string
let path = String(ffiSlice: result.deb_path)!

// URL will copy string
return .success(URL(fileURLWithPath: path))
}

tw_free_rebuild_results(ffiResults)

return []
}

private func createProgressFuncs() -> TwProgressFunctions {
var funcs = TwProgressFunctions()

// not a memory leak actually. It lives as long as self does
funcs.context = Unmanaged<Dpkg>.passUnretained(self).toOpaque()
funcs.started_processing = { context, package in
guard let context,
let ffiPackage = FFIPackage(package)
else {
tw_package_release(package.inner)
return
}

let dpkg = Unmanaged<Dpkg>.fromOpaque(context).takeUnretainedValue()
Task(priority: .utility) {
await dpkg.buildProgressDelegate?.startProcessing(package: ffiPackage)
}
}
funcs.finished_processing = { context, package, debPath in
guard let context,
let ffiPackage = FFIPackage(package),
let debPath = String(ffiSlice: debPath)
else {
tw_package_release(package.inner)
return
}

let dpkg = Unmanaged<Dpkg>.fromOpaque(context).takeUnretainedValue()
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()
Task(priority: .utility) {
await dpkg.buildProgressDelegate?.finishedAll()
}
}

return funcs
return results
}
}

#if swift(>=6.0)
extension UnsafeMutablePointer<TwDpkg_t>: @unchecked @retroactive Sendable {}
extension TwDpkg_t: @unchecked @retroactive Sendable {}
#endif
97 changes: 97 additions & 0 deletions twackup-gui/Twackup/Sources/FFI Models/DpkgProgressNotifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// DpkgProgressNotifier.swift
// Twackup
//
// Created by Daniil on 14.06.2024.
//
protocol DpkgProgressSubscriber: Hashable, Sendable {
/// Being called when package is ready to start it's rebuilding operation
func startProcessing(package: FFIPackage) async

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

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

final class DpkgProgressNotifier: @unchecked Sendable {
private(set) var ffiFunctions = TwProgressFunctions()

private var untypedSubscribers: Set<AnyHashable> = []

private var subscribers: [any DpkgProgressSubscriber] {
// Since subscriber can only be added via addSubscriber() it must conform to DpkgProgressSubscriber
// So force casting is safe here
untypedSubscribers.map { $0 as! any DpkgProgressSubscriber } // swiftlint:disable:this force_cast
}

init() {
initFuncs()
}

func addSubscriber(_ subscriber: any DpkgProgressSubscriber) {
untypedSubscribers.insert(AnyHashable(subscriber))
}

func removeSubscriber(_ subscriber: any DpkgProgressSubscriber) {
untypedSubscribers.remove(subscriber)
}

// MARK: - Private methods

private func initFuncs() {
ffiFunctions.context = Unmanaged.passUnretained(self).toOpaque()
ffiFunctions.started_processing = { context, package in
guard let context, let ffiPackage = FFIPackage(package) else {
tw_package_release(package.inner)
return
}

let dpkg = Unmanaged<DpkgProgressNotifier>.fromOpaque(context).takeUnretainedValue()
dpkg.startProcessing(ffiPackage)
}
ffiFunctions.finished_processing = { context, package, debPath in
guard let context, let ffiPackage = FFIPackage(package),
let debPath = String(ffiSlice: debPath)
else {
tw_package_release(package.inner)
return
}

let dpkg = Unmanaged<DpkgProgressNotifier>.fromOpaque(context).takeUnretainedValue()
let debURL = URL(fileURLWithPath: debPath)
dpkg.finishedProcessing(ffiPackage, debURL: debURL)
}
ffiFunctions.finished_all = { context in
guard let context else { return }

let dpkg = Unmanaged<DpkgProgressNotifier>.fromOpaque(context).takeUnretainedValue()
dpkg.finishedAll()
}
}

private func startProcessing(_ package: FFIPackage) {
subscribers.forEach { subscriber in
Task(priority: .utility) {
await subscriber.startProcessing(package: package)
}
}
}

private func finishedProcessing(_ package: FFIPackage, debURL: URL) {
subscribers.forEach { subscriber in
Task(priority: .utility) {
await subscriber.finishedProcessing(package: package, debURL: debURL)
}
}
}

private func finishedAll() {
subscribers.forEach { subscriber in
Task(priority: .utility) {
await subscriber.finishedAll()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class DebPackage: NSManagedObject, Package {
architecture = package.architecture
section = package.section
installedSize = package.installedSize
buildDate = Date()
}

func isEqualTo(_ other: Package) -> Bool {
Expand Down
57 changes: 39 additions & 18 deletions twackup-gui/Twackup/Sources/Models/Packages/PackagesRebuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import Sentry

// swiftlint:disable legacy_objc_type
actor PackagesRebuilder: DpkgProgress {
actor PackagesRebuilder: DpkgProgressSubscriber, Equatable, Hashable {
let mainModel: MainModel

nonisolated private let uuid = UUID()

private final class PerformanceMeasurer: @unchecked Sendable {
let rootTransaction: Span

Expand All @@ -32,7 +34,8 @@ actor PackagesRebuilder: DpkgProgress {
init(mainModel: MainModel, updateHandler: (@Sendable (Progress) -> Void)? = nil) {
self.mainModel = mainModel
self.updateHandler = updateHandler
// mainModel.dpkg.buildProgressDelegate = self

mainModel.dpkg.progressNotifier.addSubscriber(self)
}

/// Performs package rebuilding from fs files to debs
Expand All @@ -50,28 +53,45 @@ actor PackagesRebuilder: DpkgProgress {
rootTransaction: SentrySDK.startTransaction(name: "multiple-debs-rebuild", operation: "lib")
)

func log(error: Error) async {
await FFILogger.shared.log("\(error)", level: .error)
SentrySDK.capture(error: error)
}

do {
let results = try await mainModel.dpkg.rebuild(packages: packages)
for result in results {
switch result {
case .success: continue

case .failure(let error):
await FFILogger.shared.log("\(error)", level: .error)
SentrySDK.capture(error: error)
case .failure(let error): await log(error: error)
}
}
} catch {
await FFILogger.shared.log("\(error)", level: .error)
SentrySDK.capture(error: error)
await log(error: error)
}

await addPackagesToDatabase()
performanceMeasurer?.rootTransaction.finish()
performanceMeasurer = nil
}

func startProcessing(package: Package) {
// MARK: - Private methods

/// Adds builded packages to database
/// - Parameter completion: completion that will be executed at end of save operation
private func addPackagesToDatabase() async {
guard let performanceMeasurer else { return }

let databaseTransaction = performanceMeasurer.rootTransaction.startChild(operation: "database-packages-save")

await mainModel.database.add(packages: donePackages)
databaseTransaction.finish()
await NotificationCenter.default.post(name: DebsListModel.NotificationName, object: nil)
}

// MARK: - DpkgProgressSubscriber

func startProcessing(package: FFIPackage) {
guard let performanceMeasurer else { return }
let transaction = performanceMeasurer.rootTransaction.startChild(
operation: "single-deb-rebuild",
Expand All @@ -80,7 +100,7 @@ actor PackagesRebuilder: DpkgProgress {
performanceMeasurer.childTransactions.setObject(transaction, forKey: package.id as NSString)
}

func finishedProcessing(package: Package, debURL: URL) {
func finishedProcessing(package: FFIPackage, debURL: URL) {
if let performanceMeasurer {
if let transaction = performanceMeasurer.childTransactions.object(forKey: package.id as NSString) {
transaction.finish()
Expand All @@ -96,16 +116,17 @@ actor PackagesRebuilder: DpkgProgress {
func finishedAll() {
}

/// Adds builded packages to database
/// - Parameter completion: completion that will be executed at end of save operation
private func addPackagesToDatabase() async {
guard let performanceMeasurer else { return }
// MARK: - PackagesRebuilder

let databaseTransaction = performanceMeasurer.rootTransaction.startChild(operation: "database-packages-save")
static func == (lhs: PackagesRebuilder, rhs: PackagesRebuilder) -> Bool {
lhs.uuid == rhs.uuid
}

await mainModel.database.add(packages: donePackages)
databaseTransaction.finish()
await NotificationCenter.default.post(name: DebsListModel.NotificationName, object: nil)
// MARK: - Hashable

nonisolated func hash(into hasher: inout Hasher) {
hasher.combine(uuid)
}
}

// swiftlint:enable legacy_objc_type

0 comments on commit 32dc60f

Please sign in to comment.