|
18 | 18 | import Foundation |
19 | 19 | import PredixMobileSDK |
20 | 20 |
|
21 | | -internal class WebAppUpdater |
22 | | -{ |
| 21 | +internal class WebAppUpdater { |
23 | 22 | internal static let AppDocumentDirectoryFilesChangedNotification = "AppDocumentDirectoryFilesChangedNotification" |
24 | | - fileprivate var source : DispatchSource! |
25 | | - fileprivate var filesChangedObserver : NSObjectProtocol? |
26 | | - fileprivate var foregroundingObserver : NSObjectProtocol? |
27 | | - fileprivate var backgroundingObserver : NSObjectProtocol? |
| 23 | + fileprivate var source: DispatchSource! |
| 24 | + fileprivate var filesChangedObserver: NSObjectProtocol? |
| 25 | + fileprivate var foregroundingObserver: NSObjectProtocol? |
| 26 | + fileprivate var backgroundingObserver: NSObjectProtocol? |
28 | 27 |
|
29 | | - init() |
30 | | - { |
| 28 | + init() { |
31 | 29 | self.filesChangedObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: WebAppUpdater.AppDocumentDirectoryFilesChangedNotification), object: nil, queue: nil, using: {(_:Notification) -> Void in |
32 | 30 | // we use a utility service class queue here so the file copy operation, which runs on a higher service class queue completes first. |
33 | 31 | DispatchQueue.global(qos: DispatchQoS.QoSClass.utility).async(execute: {[unowned self] () -> Void in |
34 | 32 | self.documentsUpdated() |
35 | 33 | }) |
36 | 34 | }) |
37 | | - |
| 35 | + |
38 | 36 | self.foregroundingObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: nil, using: {(_:Notification) -> Void in |
39 | 37 | self.startWatcher() |
40 | 38 | }) |
41 | | - |
| 39 | + |
42 | 40 | self.backgroundingObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationDidEnterBackground, object: nil, queue: nil, using: {(_:Notification) -> Void in |
43 | 41 | self.stopWatcher() |
44 | 42 | }) |
45 | | - |
| 43 | + |
46 | 44 | startWatcher() |
47 | 45 | } |
48 | | - |
49 | | - deinit |
50 | | - { |
| 46 | + |
| 47 | + deinit { |
51 | 48 | // cleanup our observer and file watcher. |
52 | | - if let observer = self.filesChangedObserver |
53 | | - { |
| 49 | + if let observer = self.filesChangedObserver { |
54 | 50 | NotificationCenter.default.removeObserver(observer) |
55 | 51 | self.filesChangedObserver = nil |
56 | 52 | } |
57 | | - if let observer = self.foregroundingObserver |
58 | | - { |
| 53 | + if let observer = self.foregroundingObserver { |
59 | 54 | NotificationCenter.default.removeObserver(observer) |
60 | 55 | self.foregroundingObserver = nil |
61 | 56 | } |
62 | | - if let observer = self.backgroundingObserver |
63 | | - { |
| 57 | + if let observer = self.backgroundingObserver { |
64 | 58 | NotificationCenter.default.removeObserver(observer) |
65 | 59 | self.backgroundingObserver = nil |
66 | 60 | } |
67 | 61 | stopWatcher() |
68 | 62 | } |
69 | | - |
70 | | - func startWatcher() |
71 | | - { |
| 63 | + |
| 64 | + func startWatcher() { |
72 | 65 | Logger.info("Started watching documents directory for replacement WebApp files.") |
73 | | - if let documentsUrl = self.getDocumentsUrl() |
74 | | - { |
| 66 | + if let documentsUrl = self.getDocumentsUrl() { |
75 | 67 | let fileDescriptor = open((documentsUrl as NSURL).fileSystemRepresentation, O_EVTONLY) |
76 | 68 | let queue = DispatchQueue(label: "filewatcher_queue", attributes: []) |
77 | | - self.source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: queue) /*Migrator FIXME: Use DispatchSourceFileSystemObject to avoid the cast*/ as! DispatchSource; |
78 | | - |
| 69 | + self.source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor, eventMask: DispatchSource.FileSystemEvent.write, queue: queue) /*Migrator FIXME: Use DispatchSourceFileSystemObject to avoid the cast*/ as! DispatchSource |
| 70 | + |
79 | 71 | // call documentsUpdated if changes are detected. |
80 | 72 | // schedule documentsUpdate as next event on the queue to give a chance for |
81 | 73 | // any file IO to complete. |
82 | | - self.source.setEventHandler(handler: {()->() in |
| 74 | + self.source.setEventHandler(handler: {() -> Void in |
83 | 75 | NotificationCenter.default.post(name: Notification.Name(rawValue: WebAppUpdater.AppDocumentDirectoryFilesChangedNotification), object: nil) |
84 | 76 | }) |
85 | | - |
| 77 | + |
86 | 78 | // Cleanup when the source is canceled |
87 | 79 | self.source.setCancelHandler(handler: {() in |
88 | | - |
| 80 | + |
89 | 81 | close(fileDescriptor) |
90 | 82 | }) |
91 | | - |
| 83 | + |
92 | 84 | // everything is setup, start watching |
93 | | - self.source.resume(); |
| 85 | + self.source.resume() |
94 | 86 | } |
95 | 87 | } |
96 | | - |
97 | | - func stopWatcher() |
98 | | - { |
| 88 | + |
| 89 | + func stopWatcher() { |
99 | 90 | Logger.info("Stopped watching documents directory for replacement WebApp files.") |
100 | | - self.source.cancel(); |
| 91 | + self.source.cancel() |
101 | 92 | } |
102 | | - |
103 | | - func documentsUpdated() |
104 | | - { |
| 93 | + |
| 94 | + func documentsUpdated() { |
105 | 95 | Logger.info("Changes detected in the Documents directory") |
106 | | - |
107 | | - if let documentsURL = self.getDocumentsUrl() |
108 | | - { |
109 | | - for subDirectoryURL in self.getSubdirectories(documentsURL) |
110 | | - { |
| 96 | + |
| 97 | + if let documentsURL = self.getDocumentsUrl() { |
| 98 | + for subDirectoryURL in self.getSubdirectories(documentsURL) { |
111 | 99 | // match subdirectories in the Documents folder, with loaded webapp names. |
112 | | - if let webAppLocation = self.getWebAppURL(subDirectoryURL.lastPathComponent) |
113 | | - { |
| 100 | + if let webAppLocation = self.getWebAppURL(subDirectoryURL.lastPathComponent) { |
114 | 101 | self.copyFilesFromURL(subDirectoryURL, toURL: webAppLocation) |
115 | 102 | } |
116 | 103 | } |
117 | 104 | } |
118 | 105 | } |
119 | | - |
| 106 | + |
120 | 107 | // Helper function, enumerates a directory, calling onEachItem closure for each item in the directory. |
121 | 108 | // includingPropertiesForKeys can effect the metadata resources retreived: xcdoc://?url=developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFURLRef/index.html#//apple_ref/doc/constant_group/Common_File_System_Resource_Keys |
122 | 109 | // options can be used to specify what items are included: xcdoc://?url=developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/index.html#//apple_ref/c/tdef/NSDirectoryEnumerationOptions |
123 | 110 | // |
124 | | - func enumerateItemsInDirectoryURL(_ URL: Foundation.URL, includingPropertiesForKeys: [URLResourceKey]?, options: FileManager.DirectoryEnumerationOptions, onEachItem: (Foundation.URL)->()) |
125 | | - { |
126 | | - |
127 | | - if let urlEnumerator = FileManager.default.enumerator(at: URL, includingPropertiesForKeys: includingPropertiesForKeys, options: options, errorHandler: {(errURL: Foundation.URL, error: Error)->(Bool) in |
| 111 | + func enumerateItemsInDirectoryURL(_ URL: Foundation.URL, includingPropertiesForKeys: [URLResourceKey]?, options: FileManager.DirectoryEnumerationOptions, onEachItem: (Foundation.URL) -> Void) { |
| 112 | + |
| 113 | + if let urlEnumerator = FileManager.default.enumerator(at: URL, includingPropertiesForKeys: includingPropertiesForKeys, options: options, errorHandler: {(errURL: Foundation.URL, error: Error) -> (Bool) in |
128 | 114 | Logger.error("Error enumerating URL: \(errURL) : \(error)") |
129 | 115 | return true |
130 | | - }) |
131 | | - { |
132 | | - for case let subURL as Foundation.URL in urlEnumerator |
133 | | - { |
| 116 | + }) { |
| 117 | + for case let subURL as Foundation.URL in urlEnumerator { |
134 | 118 | onEachItem(subURL) |
135 | 119 | } |
136 | 120 | } |
137 | 121 | } |
138 | | - |
| 122 | + |
139 | 123 | // Helper function. Returns array of subdirectories in provided directory as NSURLs |
140 | | - func getSubdirectories(_ url: URL) -> ([URL]) |
141 | | - { |
142 | | - var subDirectories : [URL] = [] |
| 124 | + func getSubdirectories(_ url: URL) -> ([URL]) { |
| 125 | + var subDirectories: [URL] = [] |
143 | 126 | let keys = [URLResourceKey.isDirectoryKey] |
144 | 127 |
|
145 | 128 | // get a shallow enumerator for this directory, skipping hidden files |
146 | 129 |
|
147 | | - self.enumerateItemsInDirectoryURL(url, includingPropertiesForKeys: keys, options: [.skipsSubdirectoryDescendants,.skipsHiddenFiles]) { (itemURL: URL) -> () in |
148 | | - |
| 130 | + self.enumerateItemsInDirectoryURL(url, includingPropertiesForKeys: keys, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles]) { (itemURL: URL) -> Void in |
| 131 | + |
149 | 132 | // get the resource property for this item, and check if it's a directory |
150 | | - if let resourceValues = try? (itemURL as NSURL).resourceValues(forKeys: keys), let isDirectory = resourceValues[URLResourceKey.isDirectoryKey] as? Bool, isDirectory |
151 | | - { |
| 133 | + if let resourceValues = try? (itemURL as NSURL).resourceValues(forKeys: keys), let isDirectory = resourceValues[URLResourceKey.isDirectoryKey] as? Bool, isDirectory { |
152 | 134 | subDirectories.append(itemURL) |
153 | 135 | } |
154 | 136 | } |
155 | | - |
| 137 | + |
156 | 138 | return subDirectories |
157 | 139 | } |
158 | | - |
| 140 | + |
159 | 141 | // Performs the actual file copy. |
160 | | - func copyFilesFromURL(_ fromURL: URL, toURL: URL) |
161 | | - { |
| 142 | + func copyFilesFromURL(_ fromURL: URL, toURL: URL) { |
162 | 143 | // There are two ways to go with this. We could copy fromURL to toURL entirely. This would be a single quick operation, |
163 | 144 | // however, if would completely replace toURL. Any files under toURL not under fromURL, would be deleted. |
164 | 145 | // In this case we want to be "gentler". Matching files will be replaced, new files will be copied, but |
165 | 146 | // any missing files will not be deleted. This will allow the web developer to just drop in updates, rather than a complete |
166 | 147 | // replacement directory structure. |
167 | | - |
| 148 | + |
168 | 149 | print("\(#function): fromURL: \(fromURL.path) toURL: \(toURL.path)") |
169 | | - |
| 150 | + |
170 | 151 | let keys = [URLResourceKey.isDirectoryKey] |
171 | | - |
172 | | - self.enumerateItemsInDirectoryURL(fromURL, includingPropertiesForKeys: keys, options: [.skipsSubdirectoryDescendants,.skipsHiddenFiles]) { (itemURL:URL) -> () in |
| 152 | + |
| 153 | + self.enumerateItemsInDirectoryURL(fromURL, includingPropertiesForKeys: keys, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles]) { (itemURL: URL) -> Void in |
173 | 154 |
|
174 | 155 | Logger.trace("File: \(itemURL.path)") |
175 | | - |
| 156 | + |
176 | 157 | let targetURL = toURL.appendingPathComponent(itemURL.lastPathComponent) |
177 | 158 |
|
178 | 159 | let fileExists = FileManager.default.fileExists(atPath: targetURL.path) |
179 | | - |
| 160 | + |
180 | 161 | // inspect each URL to determine if it's a file or directory |
181 | | - if let resourceValues = try? (itemURL as NSURL).resourceValues(forKeys: keys), let isDirectory = resourceValues[URLResourceKey.isDirectoryKey] as? Bool, isDirectory |
182 | | - { |
| 162 | + if let resourceValues = try? (itemURL as NSURL).resourceValues(forKeys: keys), let isDirectory = resourceValues[URLResourceKey.isDirectoryKey] as? Bool, isDirectory { |
183 | 163 | // we have a directory.... |
184 | | - |
| 164 | + |
185 | 165 | if !fileExists // directory is new, need to create it. |
186 | 166 | { |
187 | | - do |
188 | | - { |
| 167 | + do { |
189 | 168 | Logger.trace("Creating directory: \(targetURL.path)") |
190 | 169 | try FileManager.default.createDirectory(at: targetURL, withIntermediateDirectories: true, attributes: nil) |
191 | | - } |
192 | | - catch let error |
193 | | - { |
| 170 | + } catch let error { |
194 | 171 | Logger.error("Error creating subdirectory: \(targetURL) : \(error)") |
195 | 172 | } |
196 | 173 | } |
197 | | - |
| 174 | + |
198 | 175 | // recurse this subdirectory |
199 | 176 | self.copyFilesFromURL(itemURL, toURL: targetURL) |
200 | | - |
201 | | - } |
202 | | - else |
203 | | - { |
| 177 | + |
| 178 | + } else { |
204 | 179 | // we have a file -- replace or copy it |
205 | | - if fileExists |
206 | | - { |
| 180 | + if fileExists { |
207 | 181 | Logger.trace("Replacing file: \(targetURL.path) with file: \(itemURL.path)") |
208 | | - do |
209 | | - { |
| 182 | + do { |
210 | 183 | try FileManager.default.replaceItem(at: targetURL, withItemAt: itemURL, backupItemName: nil, options: [.usingNewMetadataOnly], resultingItemURL: nil) |
211 | | - } |
212 | | - catch let error |
213 | | - { |
| 184 | + } catch let error { |
214 | 185 | Logger.error("Error replacing file: \(targetURL) : \(error)") |
215 | 186 | } |
216 | | - } |
217 | | - else |
218 | | - { |
| 187 | + } else { |
219 | 188 | Logger.trace("Copying file: \(itemURL.path) to file: \(targetURL.path)") |
220 | | - do |
221 | | - { |
| 189 | + do { |
222 | 190 | try FileManager.default.copyItem(at: itemURL, to: targetURL) |
223 | | - } |
224 | | - catch let error |
225 | | - { |
| 191 | + } catch let error { |
226 | 192 | Logger.error("Error copying file: \(targetURL) : \(error)") |
227 | 193 | } |
228 | 194 | } |
229 | | - |
| 195 | + |
230 | 196 | } |
231 | | - |
| 197 | + |
232 | 198 | } |
233 | 199 | } |
234 | | - |
235 | | - |
| 200 | + |
236 | 201 | // Helper function, gets the App's Documents directory, where iTunes file drops are stored. |
237 | | - func getDocumentsUrl()->URL? |
238 | | - { |
| 202 | + func getDocumentsUrl() -> URL? { |
239 | 203 | return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first |
240 | 204 | } |
241 | | - |
| 205 | + |
242 | 206 | // Helper function, returns the URL for the given webapp name, if it exists. |
243 | | - func getWebAppURL(_ webAppName : String?) -> (URL?) |
244 | | - { |
| 207 | + func getWebAppURL(_ webAppName: String?) -> (URL?) { |
245 | 208 | guard let webAppName = webAppName else {return nil} |
246 | | - |
247 | | - if let userURL = PredixMobilityConfiguration.userLocalStorageURL |
248 | | - { |
| 209 | + |
| 210 | + if let userURL = PredixMobilityConfiguration.userLocalStorageURL { |
249 | 211 | let webappLocation = userURL.appendingPathComponent("WebApps").appendingPathComponent(webAppName) |
250 | | - |
| 212 | + |
251 | 213 | return self.getSubdirectories(webappLocation).first |
252 | 214 | } |
253 | | - |
| 215 | + |
254 | 216 | return nil |
255 | 217 | } |
256 | 218 | } |
0 commit comments