|
5 | 5 | // Created by Krzysztof Łowiec on 12/09/2021.
|
6 | 6 | //
|
7 | 7 |
|
8 |
| -import Foundation |
| 8 | +import SwiftUI |
9 | 9 | import Combine
|
10 | 10 |
|
11 | 11 | protocol SnippetsParserService {
|
12 | 12 | func createSnippet(_ snippet: Snippet) -> AnyPublisher<SnippetPlist, SnippetsParserServiceError>
|
13 | 13 | func removeSnippet(_ snippet: Snippet) -> AnyPublisher<Void, SnippetsParserServiceError>
|
| 14 | + func writeSnippetsToPath( |
| 15 | + type: SnippetWriteType, |
| 16 | + snippets: [Snippet], |
| 17 | + uploadingStatus: Binding<UploadingStatus>, |
| 18 | + progressValue: Binding<CGFloat>, |
| 19 | + completion: @escaping () -> Void, |
| 20 | + onError: () -> Void |
| 21 | + ) |
14 | 22 | }
|
15 | 23 |
|
16 | 24 | final class SnippetsParserServiceImpl: SnippetsParserService {
|
@@ -93,4 +101,140 @@ final class SnippetsParserServiceImpl: SnippetsParserService {
|
93 | 101 | .eraseToAnyPublisher()
|
94 | 102 | }
|
95 | 103 |
|
| 104 | + // MARK: - Writing |
| 105 | + |
| 106 | + func writeSnippetsToPath( |
| 107 | + type: SnippetWriteType, |
| 108 | + snippets: [Snippet], |
| 109 | + uploadingStatus: Binding<UploadingStatus>, |
| 110 | + progressValue: Binding<CGFloat>, |
| 111 | + completion: @escaping () -> Void, |
| 112 | + onError: () -> Void |
| 113 | + ) { |
| 114 | + switch type { |
| 115 | + case .download: |
| 116 | + write( |
| 117 | + sourceDirectory: .downloadsDirectory, |
| 118 | + componentsSeparator: "Library/", |
| 119 | + snippets: snippets, |
| 120 | + uploadingStatus: uploadingStatus, |
| 121 | + progressValue: progressValue, |
| 122 | + message: "Where do you want to save the snippet?", |
| 123 | + directoryPathSuffix: "Downloads", |
| 124 | + completion: completion, |
| 125 | + onError: onError |
| 126 | + ) |
| 127 | + case .uploadToXcode: |
| 128 | + write( |
| 129 | + sourceDirectory: .libraryDirectory, |
| 130 | + componentsSeparator: "Containers/", |
| 131 | + snippets: snippets, |
| 132 | + uploadingStatus: uploadingStatus, |
| 133 | + progressValue: progressValue, |
| 134 | + message: "Confirm Xcode user snippets directory", |
| 135 | + directoryPathSuffix: "Developer/Xcode/UserData/CodeSnippets", |
| 136 | + completion: completion, |
| 137 | + onError: onError |
| 138 | + ) |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + private func write( |
| 143 | + sourceDirectory: FileManager.SearchPathDirectory, |
| 144 | + componentsSeparator: String, |
| 145 | + snippets: [Snippet], |
| 146 | + uploadingStatus: Binding<UploadingStatus>? = nil, |
| 147 | + progressValue: Binding<CGFloat>? = nil, |
| 148 | + message: String, |
| 149 | + directoryPathSuffix: String, |
| 150 | + completion: (() -> Void)? = nil, |
| 151 | + onError: () -> Void |
| 152 | + ) { |
| 153 | + let fileManager = FileManager.default |
| 154 | + let encoder = PropertyListEncoder() |
| 155 | + encoder.outputFormat = .xml |
| 156 | + |
| 157 | + let directoryURLs = fileManager.urls( |
| 158 | + for: sourceDirectory, |
| 159 | + in: .userDomainMask |
| 160 | + ) |
| 161 | + |
| 162 | + guard |
| 163 | + let containerDirectoryURL = directoryURLs.first, |
| 164 | + let sourceDirectory = containerDirectoryURL.absoluteString.components(separatedBy: componentsSeparator).first, |
| 165 | + let userSourceDirectory = snippetSourceDirectoryURL( |
| 166 | + withDirectory: sourceDirectory, |
| 167 | + message: message, |
| 168 | + directoryPathSuffix: directoryPathSuffix, |
| 169 | + uploadingStatus: uploadingStatus |
| 170 | + ) |
| 171 | + else { |
| 172 | + uploadingStatus?.wrappedValue = .error |
| 173 | + return |
| 174 | + } |
| 175 | + |
| 176 | + for (index, snippet) in snippets.enumerated() { |
| 177 | + DispatchQueue.main.async { |
| 178 | + uploadingStatus?.wrappedValue = .uploading |
| 179 | + progressValue?.wrappedValue = (CGFloat(index + 1) / CGFloat(snippets.count)) |
| 180 | + } |
| 181 | + do { |
| 182 | + let plistSnippet = SnippetPlist(from: snippet) |
| 183 | + let data = try encoder.encode(plistSnippet) |
| 184 | + let filePath = userSourceDirectory.appendingPathComponent("\(snippet.id).codesnippet") |
| 185 | + try data.write(to: filePath, options: []) |
| 186 | + } catch { |
| 187 | + onError() |
| 188 | + } |
| 189 | + } |
| 190 | + |
| 191 | + completion?() |
| 192 | + } |
| 193 | + |
| 194 | + private func snippetSourceDirectoryURL( |
| 195 | + withDirectory userDirectory: String, |
| 196 | + message: String, |
| 197 | + directoryPathSuffix: String, |
| 198 | + uploadingStatus: Binding<UploadingStatus>? = nil |
| 199 | + ) -> URL? { |
| 200 | + let openPanel = NSOpenPanel() |
| 201 | + openPanel.message = message |
| 202 | + openPanel.prompt = "Confirm" |
| 203 | + openPanel.allowedFileTypes = ["none"] |
| 204 | + openPanel.allowsOtherFileTypes = false |
| 205 | + openPanel.canChooseFiles = false |
| 206 | + openPanel.canChooseDirectories = true |
| 207 | + openPanel.directoryURL = URL(string: "\(userDirectory)\(directoryPathSuffix)") |
| 208 | + |
| 209 | + let response = openPanel.runModal() |
| 210 | + if response != .OK { |
| 211 | + uploadingStatus?.wrappedValue = .error |
| 212 | + return nil |
| 213 | + } else { |
| 214 | + return openPanel.urls.first |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | +} |
| 219 | + |
| 220 | +extension SnippetsParserService { |
| 221 | + |
| 222 | + func writeToPath( |
| 223 | + type: SnippetWriteType, |
| 224 | + snippets: [Snippet], |
| 225 | + uploadingStatus: Binding<UploadingStatus>? = nil, |
| 226 | + progressValue: Binding<CGFloat>? = nil, |
| 227 | + completion: (() -> Void)? = nil, |
| 228 | + onError: () -> Void |
| 229 | + ) { |
| 230 | + writeSnippetsToPath( |
| 231 | + type: type, |
| 232 | + snippets: snippets, |
| 233 | + uploadingStatus: uploadingStatus ?? .constant(.initializing), |
| 234 | + progressValue: progressValue ?? .constant(.zero), |
| 235 | + completion: completion ?? {}, |
| 236 | + onError: onError |
| 237 | + ) |
| 238 | + } |
| 239 | + |
96 | 240 | }
|
0 commit comments