Skip to content

Commit f4e044f

Browse files
Merge pull request #8 from bow-swift/propertywrapper_storage
Create Bookmark type using property wrapped
2 parents fc3f3f0 + 1f975e9 commit f4e044f

File tree

6 files changed

+211
-71
lines changed

6 files changed

+211
-71
lines changed

Diff for: nef-plugin.xcodeproj/project.pbxproj

+54-12
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,14 @@
3333
8BB2895822CB6F81006E4CCB /* SourceEditorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB2895722CB6F81006E4CCB /* SourceEditorExtension.swift */; };
3434
8BB2895A22CB6F81006E4CCB /* NefEditorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BB2895922CB6F81006E4CCB /* NefEditorCommand.swift */; };
3535
8BB2895F22CB6F81006E4CCB /* nef-plugin.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8BB2895222CB6F81006E4CCB /* nef-plugin.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
36-
8BBDE53C22DC86BB00598565 /* nef in Frameworks */ = {isa = PBXBuildFile; productRef = 8BBDE53B22DC86BB00598565 /* nef */; };
36+
8BEAFB12235C96F700118F31 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEAFB11235C96F700118F31 /* Bookmark.swift */; };
3737
8BEFF92B2301A86B009AA042 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8BEFF92D2301A86B009AA042 /* Localizable.strings */; };
3838
8BEFF9342301A91B009AA042 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8BEFF92D2301A86B009AA042 /* Localizable.strings */; };
3939
8BEFF9372301AEDB009AA042 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 8BEFF9392301AEDB009AA042 /* Localizable.stringsdict */; };
40+
8BF53F4D235E0A1E0081C27C /* BowOptics in Frameworks */ = {isa = PBXBuildFile; productRef = 8BF53F4C235E0A1E0081C27C /* BowOptics */; };
41+
8BF53F4F235E0A1E0081C27C /* BowEffects in Frameworks */ = {isa = PBXBuildFile; productRef = 8BF53F4E235E0A1E0081C27C /* BowEffects */; };
42+
8BF53F51235E0A1E0081C27C /* Bow in Frameworks */ = {isa = PBXBuildFile; productRef = 8BF53F50235E0A1E0081C27C /* Bow */; };
43+
8BF53F59235F0D020081C27C /* nef in Frameworks */ = {isa = PBXBuildFile; productRef = 8BF53F58235F0D020081C27C /* nef */; };
4044
8BF68F9F22DDD802009E57A9 /* PreferencesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF68F9E22DDD802009E57A9 /* PreferencesDataSource.swift */; };
4145
8BF68FA122DDD8BD009E57A9 /* Assembler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF68FA022DDD8BD009E57A9 /* Assembler.swift */; };
4246
8BF68FA322DDE268009E57A9 /* ActionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF68FA222DDE268009E57A9 /* ActionViewModel.swift */; };
@@ -102,6 +106,7 @@
102106
8BB2895922CB6F81006E4CCB /* NefEditorCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NefEditorCommand.swift; sourceTree = "<group>"; };
103107
8BB2895B22CB6F81006E4CCB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
104108
8BB2895C22CB6F81006E4CCB /* nef_plugin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = nef_plugin.entitlements; sourceTree = "<group>"; };
109+
8BEAFB11235C96F700118F31 /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = "<group>"; };
105110
8BEFF92C2301A86B009AA042 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
106111
8BEFF92F2301A8B9009AA042 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
107112
8BEFF9312301A8CE009AA042 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = "<group>"; };
@@ -121,7 +126,10 @@
121126
isa = PBXFrameworksBuildPhase;
122127
buildActionMask = 2147483647;
123128
files = (
124-
8BBDE53C22DC86BB00598565 /* nef in Frameworks */,
129+
8BF53F59235F0D020081C27C /* nef in Frameworks */,
130+
8BF53F4D235E0A1E0081C27C /* BowOptics in Frameworks */,
131+
8BF53F4F235E0A1E0081C27C /* BowEffects in Frameworks */,
132+
8BF53F51235E0A1E0081C27C /* Bow in Frameworks */,
125133
);
126134
runOnlyForDeploymentPostprocessing = 0;
127135
};
@@ -158,6 +166,7 @@
158166
children = (
159167
8B3B21A922F8329900E92BC9 /* Browser.swift */,
160168
8BF9220523546D8D00C58AA0 /* OpenPanel.swift */,
169+
8BEAFB11235C96F700118F31 /* Bookmark.swift */,
161170
);
162171
path = Utils;
163172
sourceTree = "<group>";
@@ -328,7 +337,10 @@
328337
);
329338
name = nef;
330339
packageProductDependencies = (
331-
8BBDE53B22DC86BB00598565 /* nef */,
340+
8BF53F4C235E0A1E0081C27C /* BowOptics */,
341+
8BF53F4E235E0A1E0081C27C /* BowEffects */,
342+
8BF53F50235E0A1E0081C27C /* Bow */,
343+
8BF53F58235F0D020081C27C /* nef */,
332344
);
333345
productName = nef;
334346
productReference = 8BB2892A22CB6EFB006E4CCB /* nef.app */;
@@ -360,7 +372,7 @@
360372
isa = PBXProject;
361373
attributes = {
362374
LastSwiftUpdateCheck = 1100;
363-
LastUpgradeCheck = 1100;
375+
LastUpgradeCheck = 1120;
364376
ORGANIZATIONNAME = "47 Degrees";
365377
TargetAttributes = {
366378
8BB2892922CB6EFB006E4CCB = {
@@ -382,7 +394,8 @@
382394
);
383395
mainGroup = 8BB2892122CB6EFB006E4CCB;
384396
packageReferences = (
385-
8BBDE53A22DC86BB00598565 /* XCRemoteSwiftPackageReference "nef" */,
397+
8BF53F4B235E0A1E0081C27C /* XCRemoteSwiftPackageReference "bow" */,
398+
8BF53F57235F0D020081C27C /* XCRemoteSwiftPackageReference "nef" */,
386399
);
387400
productRefGroup = 8BB2892B22CB6EFB006E4CCB /* Products */;
388401
projectDirPath = "";
@@ -440,6 +453,7 @@
440453
8BF68FB522DE2403009E57A9 /* CarbonStyle+Model.swift in Sources */,
441454
8BAAB73D22D4DB2A00FD3636 /* Date+Extension.swift in Sources */,
442455
8B9F3CEF22EF2D48001EE2DF /* CarbonWebView.swift in Sources */,
456+
8BEAFB12235C96F700118F31 /* Bookmark.swift in Sources */,
443457
8B9F3CF122F036BD001EE2DF /* CarbonViewer.swift in Sources */,
444458
8B41588822D8E43A00588921 /* CarbonStyle+Color.swift in Sources */,
445459
8B3B21B022F86C5600E92BC9 /* SeparatorView.swift in Sources */,
@@ -556,6 +570,7 @@
556570
MACOSX_DEPLOYMENT_TARGET = 10.15;
557571
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
558572
MTL_FAST_MATH = YES;
573+
ONLY_ACTIVE_ARCH = YES;
559574
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
560575
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
561576
};
@@ -623,8 +638,9 @@
623638
CODE_SIGN_IDENTITY = "-";
624639
CODE_SIGN_STYLE = Manual;
625640
COMBINE_HIDPI_IMAGES = YES;
626-
CURRENT_PROJECT_VERSION = 47;
641+
CURRENT_PROJECT_VERSION = 100;
627642
DEVELOPMENT_TEAM = "";
643+
ENABLE_HARDENED_RUNTIME = NO;
628644
INFOPLIST_FILE = "nef/Support Files/Info.plist";
629645
LD_RUNPATH_SEARCH_PATHS = (
630646
"$(inherited)",
@@ -648,8 +664,9 @@
648664
CODE_SIGN_IDENTITY = "-";
649665
CODE_SIGN_STYLE = Manual;
650666
COMBINE_HIDPI_IMAGES = YES;
651-
CURRENT_PROJECT_VERSION = 47;
667+
CURRENT_PROJECT_VERSION = 100;
652668
DEVELOPMENT_TEAM = "";
669+
ENABLE_HARDENED_RUNTIME = NO;
653670
INFOPLIST_FILE = "nef/Support Files/Info.plist";
654671
LD_RUNPATH_SEARCH_PATHS = (
655672
"$(inherited)",
@@ -671,8 +688,9 @@
671688
CODE_SIGN_IDENTITY = "-";
672689
CODE_SIGN_STYLE = Manual;
673690
COMBINE_HIDPI_IMAGES = YES;
674-
CURRENT_PROJECT_VERSION = 47;
691+
CURRENT_PROJECT_VERSION = 100;
675692
DEVELOPMENT_TEAM = "";
693+
ENABLE_HARDENED_RUNTIME = NO;
676694
INFOPLIST_FILE = "nef-plugin/Support Files/Info.plist";
677695
LD_RUNPATH_SEARCH_PATHS = (
678696
"$(inherited)",
@@ -695,8 +713,9 @@
695713
CODE_SIGN_IDENTITY = "-";
696714
CODE_SIGN_STYLE = Manual;
697715
COMBINE_HIDPI_IMAGES = YES;
698-
CURRENT_PROJECT_VERSION = 47;
716+
CURRENT_PROJECT_VERSION = 100;
699717
DEVELOPMENT_TEAM = "";
718+
ENABLE_HARDENED_RUNTIME = NO;
700719
INFOPLIST_FILE = "nef-plugin/Support Files/Info.plist";
701720
LD_RUNPATH_SEARCH_PATHS = (
702721
"$(inherited)",
@@ -745,7 +764,15 @@
745764
/* End XCConfigurationList section */
746765

747766
/* Begin XCRemoteSwiftPackageReference section */
748-
8BBDE53A22DC86BB00598565 /* XCRemoteSwiftPackageReference "nef" */ = {
767+
8BF53F4B235E0A1E0081C27C /* XCRemoteSwiftPackageReference "bow" */ = {
768+
isa = XCRemoteSwiftPackageReference;
769+
repositoryURL = "http://github.com/bow-swift/bow.git";
770+
requirement = {
771+
branch = master;
772+
kind = branch;
773+
};
774+
};
775+
8BF53F57235F0D020081C27C /* XCRemoteSwiftPackageReference "nef" */ = {
749776
isa = XCRemoteSwiftPackageReference;
750777
repositoryURL = "https://github.com/bow-swift/nef.git";
751778
requirement = {
@@ -756,9 +783,24 @@
756783
/* End XCRemoteSwiftPackageReference section */
757784

758785
/* Begin XCSwiftPackageProductDependency section */
759-
8BBDE53B22DC86BB00598565 /* nef */ = {
786+
8BF53F4C235E0A1E0081C27C /* BowOptics */ = {
787+
isa = XCSwiftPackageProductDependency;
788+
package = 8BF53F4B235E0A1E0081C27C /* XCRemoteSwiftPackageReference "bow" */;
789+
productName = BowOptics;
790+
};
791+
8BF53F4E235E0A1E0081C27C /* BowEffects */ = {
792+
isa = XCSwiftPackageProductDependency;
793+
package = 8BF53F4B235E0A1E0081C27C /* XCRemoteSwiftPackageReference "bow" */;
794+
productName = BowEffects;
795+
};
796+
8BF53F50235E0A1E0081C27C /* Bow */ = {
797+
isa = XCSwiftPackageProductDependency;
798+
package = 8BF53F4B235E0A1E0081C27C /* XCRemoteSwiftPackageReference "bow" */;
799+
productName = Bow;
800+
};
801+
8BF53F58235F0D020081C27C /* nef */ = {
760802
isa = XCSwiftPackageProductDependency;
761-
package = 8BBDE53A22DC86BB00598565 /* XCRemoteSwiftPackageReference "nef" */;
803+
package = 8BF53F57235F0D020081C27C /* XCRemoteSwiftPackageReference "nef" */;
762804
productName = nef;
763805
};
764806
/* End XCSwiftPackageProductDependency section */

Diff for: nef/AppDelegate.swift

+36-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright © 2019 The nef Authors.
22

33
import SwiftUI
4+
import Bow
5+
import BowEffects
46

57
@NSApplicationMain
68
class AppDelegate: NSObject, NSApplicationDelegate {
@@ -73,27 +75,44 @@ class AppDelegate: NSObject, NSApplicationDelegate {
7375

7476
private func carbonDidFinishLaunching(code: String) {
7577
guard !code.isEmpty else { terminate(); return }
76-
guard let _ = carbonWindow(code: code) else { terminate(); return }
7778

7879
window = NSWindow.empty
7980
window.makeKeyAndOrderFront(nil)
81+
82+
carbonIO(code: code).unsafeRunAsync(on: .global(qos: .userInitiated)) { _ in self.terminate() }
8083
}
8184

8285
// MARK: private methods
83-
private func carbonWindow(code: String) -> NSWindow? {
84-
guard let writableFolder = assembler.resolveOpenPanel().writableFolder(create: true) else { return nil }
86+
private func carbonIO(code: String) -> IO<AppDelegateError, ()> {
8587

86-
let filename = "nef \(Date.now.human)"
87-
let outputPath = writableFolder.appendingPathComponent(filename).path
88+
func runCarbon(code: String, outputPath: String) -> IO<OpenPanelError, ()> {
89+
IO.async { callback in
90+
self.assembler.carbon(code: code, outputPath: outputPath) { status in
91+
if status {
92+
let file = URL(fileURLWithPath: "\(outputPath).png")
93+
self.showFile(file)
94+
callback(.right(()))
95+
} else {
96+
callback(.left(.denied))
97+
}
98+
}
99+
}^
100+
}
88101

89-
return assembler.resolveCarbonWindow(code: code, outputPath: outputPath) { status in
90-
if status {
91-
let file = URL(fileURLWithPath: "\(outputPath).png")
92-
self.showFile(file)
93-
}
94-
95-
self.terminate()
102+
func outputURL(inFolder url: URL) -> IO<OpenPanelError, URL> {
103+
let filename = "nef \(Date.now.human)"
104+
return IO.pure(url.appendingPathComponent(filename))^
96105
}
106+
107+
return assembler.resolveOpenPanel().writableFolder(create: true).use { folder in
108+
let file = IO<OpenPanelError, URL>.var()
109+
110+
return binding(
111+
file <- outputURL(inFolder: folder),
112+
continueOn(.main),
113+
|<-runCarbon(code: code, outputPath: file.get.path),
114+
yield: ())
115+
}^.mapLeft { _ in AppDelegateError.carbon }
97116
}
98117

99118
private func showFile(_ file: URL) {
@@ -147,4 +166,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
147166
static let preferencesTitle = NSLocalizedString("preferences", comment: "")
148167
static let aboutTitle = NSLocalizedString("about", comment: "")
149168
}
169+
170+
// MARK: Errors
171+
enum AppDelegateError: Error {
172+
case carbon
173+
}
150174
}

Diff for: nef/Assembler.swift

+11-7
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ class Assembler {
1818
return PreferencesView(viewModel: resolvePreferencesViewModel())
1919
}
2020

21-
func resolveCarbonWindow(code: String, outputPath: String, completion: @escaping (_ status: Bool) -> Void) -> NSWindow {
22-
nef.carbon(code: code,
23-
style: preferencesDataSource.state.style,
24-
outputPath: outputPath,
25-
success: { completion(true) }, failure: { _ in completion(false) })
26-
}
27-
2821
// MARK: - utils
2922
func resolveOpenPanel() -> OpenPanel { OpenPanel() }
3023

@@ -41,3 +34,14 @@ class Assembler {
4134
return PreferencesDataSource(fileManager: .default)
4235
}
4336
}
37+
38+
39+
extension Assembler {
40+
41+
func carbon(code: String, outputPath: String, completion: @escaping (_ status: Bool) -> Void) {
42+
nef.carbon(code: code,
43+
style: preferencesDataSource.state.style,
44+
outputPath: outputPath,
45+
success: { completion(true) }, failure: { _ in completion(false) })
46+
}
47+
}

Diff for: nef/Preferences/Views/OutputFolderView.swift

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright © 2019 The nef Authors.
22

33
import SwiftUI
4+
import BowEffects
5+
46

57
struct OutputFolderView: View {
68

@@ -18,23 +20,32 @@ struct OutputFolderView: View {
1820
.lineLimit(1)
1921
.ligthGray
2022

21-
Text("􀉓")
23+
Text("")
2224
.font(.system(.caption)).fontWeight(.light)
2325
.regularGray
2426
}.frame(width: PreferencesView.Layout.rightPanel+Constant.rightPanelExtraWidth, alignment: .leading)
25-
.onTapGesture { self.selectWritableFolder() }
27+
.onTapGesture { try? self.selectWritableFolder().unsafeRunSync() }
2628

2729
}.offset(x: Constant.rightPanelExtraWidth/2)
2830
.onAppear(perform: onAppear)
2931
}
3032

3133
private func onAppear() {
32-
outputPath = openPanel.writableFolder(create: false)?.path ?? ""
34+
let updateOutputPathIO: IO<OpenPanelError, ()> = openPanel.writableFolder(create: false).use { url in
35+
IO.invoke {
36+
self.outputPath = url.path
37+
}
38+
}^
39+
40+
try? updateOutputPathIO.unsafeRunSync()
3341
}
3442

35-
private func selectWritableFolder() {
36-
guard let url = openPanel.selectWritableFolder() else { return }
37-
outputPath = url.path
43+
private func selectWritableFolder() -> IO<OpenPanelError, ()> {
44+
openPanel.selectWritableFolder().use { url in
45+
IO.invoke {
46+
self.outputPath = url.path
47+
}
48+
}^
3849
}
3950

4051
// MARK: - Constants

Diff for: nef/Utils/Bookmark.swift

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright © 2019 The nef Authors.
2+
3+
import Foundation
4+
5+
@propertyWrapper
6+
struct Bookmark {
7+
private let key: String
8+
private let storage: UserDefaults
9+
10+
init(key: String, storage: UserDefaults = UserDefaults.standard) {
11+
self.key = key
12+
self.storage = storage
13+
}
14+
15+
var wrappedValue: URL? {
16+
get {
17+
guard let url = retrieveBookmark(), existItem(at: url) else { return nil }
18+
return url
19+
}
20+
set {
21+
guard let url = newValue else { return }
22+
persistBookmark(url: url)
23+
}
24+
}
25+
26+
// MARK: private methods <storage>
27+
private func persistBookmark(url: URL) {
28+
guard let data = try? url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) else { return }
29+
storage.setValue(data, forKey: key)
30+
}
31+
32+
private func retrieveBookmark() -> URL? {
33+
guard let data = storage.data(forKey: key) else { return nil }
34+
var bookmarkDataIsStale: Bool = true
35+
return try? URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &bookmarkDataIsStale)
36+
}
37+
38+
// MARK: helpers
39+
private func existItem(at url: URL) -> Bool {
40+
FileManager.default.fileExists(atPath: url.path)
41+
}
42+
}
43+
44+
45+
extension URL {
46+
func openAccessingResource() {
47+
_ = self.startAccessingSecurityScopedResource()
48+
}
49+
50+
func closeAccessingResource() {
51+
self.stopAccessingSecurityScopedResource()
52+
}
53+
}

0 commit comments

Comments
 (0)