Several fixes in ProfileManager (#685)
- [x] Search not accounted for when reloading profiles - [x] Remote profile being saved twice - [x] Add some logging
This commit is contained in:
parent
d14f22d4a1
commit
f66193cf78
|
@ -32,7 +32,7 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "779910e268e79f1004a95285ac2485255d88bb21"
|
"revision" : "f681f968f39ca514e29ac6c0abcf658c224e4c04"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -59,6 +59,14 @@
|
||||||
argument = "-com.apple.CoreData.SQLDebug 1"
|
argument = "-com.apple.CoreData.SQLDebug 1"
|
||||||
isEnabled = "NO">
|
isEnabled = "NO">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
|
<CommandLineArgument
|
||||||
|
argument = "-com.apple.CoreData.CloudKitDebug 0"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</CommandLineArgument>
|
||||||
|
<CommandLineArgument
|
||||||
|
argument = "-com.apple.CoreData.Logging.stderr 0"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = " -com.apple.CoreData.ConcurrencyDebug 1"
|
argument = " -com.apple.CoreData.ConcurrencyDebug 1"
|
||||||
isEnabled = "YES">
|
isEnabled = "YES">
|
||||||
|
|
|
@ -31,7 +31,7 @@ let package = Package(
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"),
|
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"),
|
||||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "779910e268e79f1004a95285ac2485255d88bb21"),
|
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "f681f968f39ca514e29ac6c0abcf658c224e4c04"),
|
||||||
// .package(path: "../../../passepartoutkit-source"),
|
// .package(path: "../../../passepartoutkit-source"),
|
||||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"),
|
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"),
|
||||||
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),
|
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),
|
||||||
|
|
|
@ -50,7 +50,7 @@ public final class ProfileManager: ObservableObject {
|
||||||
|
|
||||||
private var allProfiles: [Profile.ID: Profile] {
|
private var allProfiles: [Profile.ID: Profile] {
|
||||||
didSet {
|
didSet {
|
||||||
reloadFilteredProfiles()
|
reloadFilteredProfiles(with: searchSubject.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,16 +117,36 @@ extension ProfileManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func save(_ profile: Profile) async throws {
|
public func save(_ profile: Profile, isShared: Bool? = nil) async throws {
|
||||||
|
pp_log(.app, .notice, "Save profile \(profile.id)...")
|
||||||
do {
|
do {
|
||||||
try await beforeSave?(profile)
|
if let existingProfile = allProfiles[profile.id], profile != existingProfile {
|
||||||
try await repository.saveEntities([profile])
|
try await beforeSave?(profile)
|
||||||
allProfiles[profile.id] = profile
|
try await repository.saveEntities([profile])
|
||||||
didChange.send(.save(profile))
|
|
||||||
|
allProfiles[profile.id] = profile
|
||||||
|
didChange.send(.save(profile))
|
||||||
|
} else {
|
||||||
|
pp_log(.app, .notice, "Profile \(profile.id) not modified, not saving")
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
pp_log(.app, .fault, "Unable to save profile \(profile.id): \(error)")
|
pp_log(.app, .fault, "Unable to save profile \(profile.id): \(error)")
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
do {
|
||||||
|
if let isShared, let remoteRepository {
|
||||||
|
if isShared {
|
||||||
|
pp_log(.app, .notice, "Enable remote sharing of profile \(profile.id)...")
|
||||||
|
try await remoteRepository.saveEntities([profile])
|
||||||
|
} else {
|
||||||
|
pp_log(.app, .notice, "Disable remote sharing of profile \(profile.id)...")
|
||||||
|
try await remoteRepository.removeEntities(withIds: [profile.id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
pp_log(.app, .fault, "Unable to save/remove remote profile \(profile.id): \(error)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(withId profileId: Profile.ID) async {
|
public func remove(withId profileId: Profile.ID) async {
|
||||||
|
@ -134,14 +154,15 @@ extension ProfileManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(withIds profileIds: [Profile.ID]) async {
|
public func remove(withIds profileIds: [Profile.ID]) async {
|
||||||
|
pp_log(.app, .notice, "Remove profiles \(profileIds)...")
|
||||||
do {
|
do {
|
||||||
// remove local profiles
|
// remove local profiles
|
||||||
var newAllProfiles = allProfiles
|
var newAllProfiles = allProfiles
|
||||||
try await repository.removeEntities(withIds: profileIds)
|
try await repository.removeEntities(withIds: profileIds)
|
||||||
|
await afterRemove?(profileIds)
|
||||||
profileIds.forEach {
|
profileIds.forEach {
|
||||||
newAllProfiles.removeValue(forKey: $0)
|
newAllProfiles.removeValue(forKey: $0)
|
||||||
}
|
}
|
||||||
await afterRemove?(profileIds)
|
|
||||||
|
|
||||||
// remove remote counterpart too
|
// remove remote counterpart too
|
||||||
try? await remoteRepository?.removeEntities(withIds: profileIds)
|
try? await remoteRepository?.removeEntities(withIds: profileIds)
|
||||||
|
@ -169,22 +190,8 @@ extension ProfileManager {
|
||||||
allRemoteProfiles.keys.contains(profileId)
|
allRemoteProfiles.keys.contains(profileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setRemotelyShared(_ shared: Bool, profileWithId profileId: Profile.ID) async throws {
|
public func eraseRemotelySharedProfiles() async throws {
|
||||||
guard let remoteRepository else {
|
pp_log(.app, .notice, "Erase remotely shared profiles...")
|
||||||
pp_log(.app, .error, "Unable to share remotely when no remoteRepository is set")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let profile = allProfiles[profileId] else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if shared {
|
|
||||||
try await remoteRepository.saveEntities([profile])
|
|
||||||
} else {
|
|
||||||
try await remoteRepository.removeEntities(withIds: [profileId])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func eraseRemoteProfiles() async throws {
|
|
||||||
try await remoteRepository?.removeEntities(withIds: Array(allRemoteProfiles.keys))
|
try await remoteRepository?.removeEntities(withIds: Array(allRemoteProfiles.keys))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,6 +216,7 @@ extension ProfileManager {
|
||||||
|
|
||||||
var builder = profile.builder(withNewId: true)
|
var builder = profile.builder(withNewId: true)
|
||||||
builder.name = firstUniqueName(from: profile.name)
|
builder.name = firstUniqueName(from: profile.name)
|
||||||
|
pp_log(.app, .notice, "Duplicate profile [\(profileId), \(profile.name)] -> [\(builder.id), \(builder.name)]...")
|
||||||
let copy = try builder.tryBuild()
|
let copy = try builder.tryBuild()
|
||||||
|
|
||||||
try await save(copy)
|
try await save(copy)
|
||||||
|
@ -239,7 +247,7 @@ extension ProfileManager {
|
||||||
.first()
|
.first()
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
self?.notifyLocalEntities($0)
|
self?.reloadLocalProfiles($0)
|
||||||
}
|
}
|
||||||
.store(in: &subscriptions)
|
.store(in: &subscriptions)
|
||||||
|
|
||||||
|
@ -247,7 +255,16 @@ extension ProfileManager {
|
||||||
.entitiesPublisher
|
.entitiesPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] in
|
.sink { [weak self] in
|
||||||
self?.notifyRemoteEntities($0)
|
self?.reloadRemoteProfiles($0)
|
||||||
|
}
|
||||||
|
.store(in: &subscriptions)
|
||||||
|
|
||||||
|
remoteRepository?
|
||||||
|
.entitiesPublisher
|
||||||
|
.dropFirst()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] in
|
||||||
|
self?.importRemoteProfiles($0)
|
||||||
}
|
}
|
||||||
.store(in: &subscriptions)
|
.store(in: &subscriptions)
|
||||||
|
|
||||||
|
@ -261,26 +278,26 @@ extension ProfileManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ProfileManager {
|
private extension ProfileManager {
|
||||||
func notifyLocalEntities(_ result: EntitiesResult<Profile>) {
|
func reloadLocalProfiles(_ result: EntitiesResult<Profile>) {
|
||||||
|
pp_log(.app, .info, "Reload local profiles: \(result.entities.map(\.id))")
|
||||||
allProfiles = result.entities.reduce(into: [:]) {
|
allProfiles = result.entities.reduce(into: [:]) {
|
||||||
$0[$1.id] = $1
|
$0[$1.id] = $1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyRemoteEntities(_ result: EntitiesResult<Profile>) {
|
func reloadRemoteProfiles(_ result: EntitiesResult<Profile>) {
|
||||||
let isInitial = allRemoteProfiles.isEmpty
|
pp_log(.app, .info, "Reload remote profiles: \(result.entities.map(\.id))")
|
||||||
allRemoteProfiles = result.entities.reduce(into: [:]) {
|
allRemoteProfiles = result.entities.reduce(into: [:]) {
|
||||||
$0[$1.id] = $1
|
$0[$1.id] = $1
|
||||||
}
|
}
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
|
}
|
||||||
|
|
||||||
// do not import on initial load
|
// pull remote updates into local profiles (best-effort)
|
||||||
guard !isInitial else {
|
func importRemoteProfiles(_ result: EntitiesResult<Profile>) {
|
||||||
return
|
let profilesToImport = result.entities
|
||||||
}
|
pp_log(.app, .info, "Try to import remote profiles: \(result.entities.map(\.id))")
|
||||||
|
|
||||||
// pull remote updates into local profiles (best-effort)
|
|
||||||
let profilesToImport = allRemoteProfiles.values
|
|
||||||
Task.detached { [weak self] in
|
Task.detached { [weak self] in
|
||||||
for remoteProfile in profilesToImport {
|
for remoteProfile in profilesToImport {
|
||||||
do {
|
do {
|
||||||
|
@ -294,14 +311,15 @@ private extension ProfileManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func performSearch(_ search: String) {
|
func performSearch(_ search: String) {
|
||||||
|
pp_log(.app, .notice, "Filter profiles with '\(search)'")
|
||||||
reloadFilteredProfiles(with: search)
|
reloadFilteredProfiles(with: search)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadFilteredProfiles(with search: String? = nil) {
|
func reloadFilteredProfiles(with search: String) {
|
||||||
profiles = allProfiles
|
profiles = allProfiles
|
||||||
.values
|
.values
|
||||||
.filter {
|
.filter {
|
||||||
if let search, !search.isEmpty {
|
if !search.isEmpty {
|
||||||
return $0.name.lowercased().contains(search.lowercased())
|
return $0.name.lowercased().contains(search.lowercased())
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -272,8 +272,7 @@ extension ProfileEditor {
|
||||||
func save(to profileManager: ProfileManager) async throws {
|
func save(to profileManager: ProfileManager) async throws {
|
||||||
do {
|
do {
|
||||||
let newProfile = try build()
|
let newProfile = try build()
|
||||||
try await profileManager.save(newProfile)
|
try await profileManager.save(newProfile, isShared: isShared)
|
||||||
try await profileManager.setRemotelyShared(isShared, profileWithId: newProfile.id)
|
|
||||||
} catch {
|
} catch {
|
||||||
pp_log(.app, .fault, "Unable to save edited profile: \(error)")
|
pp_log(.app, .fault, "Unable to save edited profile: \(error)")
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -86,7 +86,7 @@ private extension SettingsSectionGroup {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
pp_log(.app, .info, "Erase CloudKit profiles...")
|
pp_log(.app, .info, "Erase CloudKit profiles...")
|
||||||
try await profileManager.eraseRemoteProfiles()
|
try await profileManager.eraseRemotelySharedProfiles()
|
||||||
|
|
||||||
let containerId = BundleConfiguration.mainString(for: .cloudKitId)
|
let containerId = BundleConfiguration.mainString(for: .cloudKitId)
|
||||||
pp_log(.app, .info, "Erase CloudKit store with identifier \(containerId)...")
|
pp_log(.app, .info, "Erase CloudKit store with identifier \(containerId)...")
|
||||||
|
|
|
@ -21,12 +21,6 @@ logname = "CHANGELOG.txt"
|
||||||
|
|
||||||
desc "Bump version"
|
desc "Bump version"
|
||||||
lane :bump do |options|
|
lane :bump do |options|
|
||||||
version = options[:version]
|
|
||||||
build = options[:build]
|
|
||||||
increment_build_number(build_number: build)
|
|
||||||
unless version.nil? || version.empty?
|
|
||||||
increment_version_number_in_xcodeproj(version_number: version)
|
|
||||||
end
|
|
||||||
unless options[:only]
|
unless options[:only]
|
||||||
log = changelog_from_git_commits(
|
log = changelog_from_git_commits(
|
||||||
pretty: "* %h %s",
|
pretty: "* %h %s",
|
||||||
|
@ -36,7 +30,13 @@ lane :bump do |options|
|
||||||
File.open(path, "w") { |file|
|
File.open(path, "w") { |file|
|
||||||
file.write(log)
|
file.write(log)
|
||||||
}
|
}
|
||||||
system("vim", path)
|
system("vim", path) or UI.user_error!("CHANGELOG editor cancelled")
|
||||||
|
end
|
||||||
|
version = options[:version]
|
||||||
|
build = options[:build]
|
||||||
|
increment_build_number(build_number: build)
|
||||||
|
unless version.nil? || version.empty?
|
||||||
|
increment_version_number_in_xcodeproj(version_number: version)
|
||||||
end
|
end
|
||||||
commit_version_bump(
|
commit_version_bump(
|
||||||
message: "Bump version",
|
message: "Bump version",
|
||||||
|
|
Loading…
Reference in New Issue