pbind 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1e4fc51921052509464b90b73e739278ea397f5
4
- data.tar.gz: 870f80bf09baa2fd496be63832384670021cdb6f
3
+ metadata.gz: 81e1f081ae44630a0c6fc01ddd9875ac5bc751ef
4
+ data.tar.gz: d12dfa76614ead7d6e29d8b90d1ff13f4d7382d3
5
5
  SHA512:
6
- metadata.gz: c6def51b03be6d197676e9f95e8e23f85c35eb70af527eb217c77a53500103cf80f922978ad4693d64823b732d57d0f89068e7cdbdd8f3c063d9df991e057c84
7
- data.tar.gz: 90b2e4bc7751110ccedad5bcf935cf01e59769984d9d87d5741c02831729f626130a4cf7f951f60571a54b0e92268030e99387775c96531eb2eaab6b61e597cd
6
+ metadata.gz: be3540ef369e882c103a469e87e76ba3463e4428c2371926e728683a0825308c562d9d54b9a57411ee390061ca060d8c70444d1535119088cc5a908ed4a00aa9
7
+ data.tar.gz: 62dd698fe2c428e4dfc06eddb24dc1791a476a4a1620ff898e45445a975a3c441dd11b191b46573d8da899f85adfed7bb945f868ffddf586ca9a9f78c867ab65
@@ -3,91 +3,4 @@ require 'xcodeproj'
3
3
 
4
4
  module Pbind
5
5
  require_relative 'pbind/command'
6
- end
7
-
8
- # class Pbind
9
-
10
- # install_dir = 'PBPlayground'
11
-
12
- # # project.targets.each do |target|
13
- # # puts target.name
14
- # # end
15
-
16
- # # target = project.targets.first
17
- # # files = target.source_build_phase.files.to_a.map do |pbx_build_file|
18
- # # pbx_build_file.file_ref.real_path.to_s
19
-
20
- # # end.select do |path|
21
- # # path.end_with?(".m", ".mm", ".swift")
22
-
23
- # # end.select do |path|
24
- # # File.exists?(path)
25
- # # end
26
-
27
- # # puts files
28
-
29
- # define_method :downloadSource do
30
- # if File.exists?(install_dir)
31
- # return true
32
- # end
33
-
34
- # `curl -OL https://github.com/wequick/Pbind/releases/download/0.6.0/PBPlayground.zip && tar -xvf PBPlayground.zip && rm PBPlayground.zip`
35
- # return File.exists?(install_dir)
36
- # end
37
-
38
- # define_method :addReferences do |project_path|
39
- # project = Xcodeproj::Project.open(project_path)
40
- # target = project.targets.first
41
-
42
- # group = project.main_group.find_subpath(install_dir, true)
43
- # group.set_source_tree('SOURCE_ROOT')
44
- # group.clear
45
-
46
- # file_refs = Array.new
47
- # Dir.foreach(install_dir) do |file|
48
- # if !File.directory?(file)
49
- # file_refs << group.new_reference(File.join(install_dir, file))
50
- # end
51
- # end
52
-
53
- # target.add_file_references(file_refs)
54
- # project.save
55
- # end
56
-
57
- # def logSuccess
58
- # puts "[ \e[32mOK\e[0m ]"
59
- # end
60
-
61
- # def logFailed
62
- # puts "[\e[31mFAILED\e[0m]"
63
- # end
64
-
65
- # define_method :install do
66
- # project_paths = Dir.glob("*.xcodeproj")
67
- # if project_paths.empty?
68
- # puts "Failed to find any xcodeproj!"
69
- # return
70
- # end
71
-
72
- # project_path = project_paths[0]
73
- # print '%-64s' % 'Downloading PBPlayground source...'
74
- # ret = downloadSource
75
- # if !ret
76
- # logFailed
77
- # return
78
- # end
79
- # logSuccess
80
-
81
- # print '%-64s' % 'Add PBPlayground reference to project...'
82
- # addReferences project_path
83
- # logSuccess
84
- # end
85
-
86
- # def self.hi
87
- # puts "Hello world!"
88
- # end
89
-
90
- # def self.exec argv
91
- # puts "Hello #{argv[0]}"
92
- # end
93
- # end
6
+ end
@@ -5,11 +5,12 @@ module Pbind
5
5
  class Command < CLAide::Command
6
6
 
7
7
  require_relative 'command/watch'
8
+ require_relative 'command/mock'
8
9
 
9
10
  self.abstract_command = true
10
11
  self.command = 'pbind'
11
12
  self.version = version
12
- self.description = 'Pbind, the Pbind xcode project helper.'
13
+ self.description = 'Pbind, the Pbind XcodeProject Helper.'
13
14
  self.plugin_prefixes = %w(claide pbind)
14
15
 
15
16
  def self.report_error(exception)
@@ -22,7 +23,7 @@ module Pbind
22
23
  raise
23
24
  else
24
25
  # if ENV['PBIND_ENV'] != 'development'
25
- # puts UI::ErrorReport.report(exception)
26
+ # puts UI::ErrorReport.report(exception)
26
27
  # UI::ErrorReport.search_for_exceptions(exception)
27
28
  # exit 1
28
29
  # else
@@ -31,5 +32,34 @@ module Pbind
31
32
  end
32
33
  end
33
34
 
35
+ def self.options
36
+ [
37
+ ['--project=path/to/Project.xcodeproj', 'The path of the XcodeProject.']
38
+ ].concat(super)
39
+ end
40
+
41
+ def initialize(argv)
42
+ super
43
+
44
+ @project_path = argv.option('project')
45
+ end
46
+
47
+ def verify_project_exists
48
+ if @project_path == nil
49
+ projects = Dir.glob("*.xcodeproj")
50
+ num_project = projects.length
51
+
52
+ help! 'No `*.xcodeproj\' found in the project directory.' if num_project == 0
53
+ help! "Could not automatically select an Xcode project. Specify one in your arguments like so:\
54
+ \n\n --project=path/to/Project.xcodeproj" unless num_project == 1
55
+
56
+ @project_path = projects[0]
57
+ else
58
+ help! 'The Xcode project should ends with `*.xcodeproj`.' unless @project_path.end_with?('.xcodeproj')
59
+ absolute_path = File.absolute_path(@project_path)
60
+ help! "Unable to find the Xcode project `#{absolute_path}`." unless File.exists?(absolute_path)
61
+ end
62
+ end
63
+
34
64
  end
35
65
  end
@@ -0,0 +1,109 @@
1
+ require 'xcodeproj'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+
5
+ module Pbind
6
+ class Command
7
+ class Mock < Command
8
+ self.summary = 'Enable JSON mocking feature for Pbind.'
9
+ self.description = <<-DESC
10
+ Create `CLIENT`/`ACTION`.json under PBLocalhost directory for the XCODEPROJ.
11
+ DESC
12
+
13
+ self.arguments = [
14
+ CLAide::Argument.new(%(CLIENT ACTION), true),
15
+ ]
16
+
17
+ def initialize(argv)
18
+ super
19
+ @client = argv.shift_argument
20
+ @action = argv.shift_argument
21
+ end
22
+
23
+ def validate!
24
+ verify_project_exists
25
+ help! 'The client is required.' unless @client
26
+ help! 'The action is required.' unless @action
27
+ end
28
+
29
+ def run
30
+ @api_name = 'PBLocalhost'
31
+ @project_root = File.dirname(@project_path)
32
+ @api_install_dir = File.absolute_path(File.join(@project_root, @api_name))
33
+ @project = Xcodeproj::Project.open(@project_path)
34
+ @changed = false
35
+
36
+ add_mock_json
37
+
38
+ if !@changed
39
+ puts 'All are UP-TO-DATE.'
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ #----------------------------------------#
46
+
47
+ # !@group Private helpers
48
+
49
+ # Create [CLIENT]/[ACTION].json under PBLocalhost directory
50
+ #
51
+ # @return [void]
52
+ #
53
+ def add_mock_json
54
+ project = @project
55
+ target = project.targets.first
56
+ changed = false
57
+
58
+ # Add PBLocalhost group
59
+ group = project.main_group.find_subpath(@api_name, true)
60
+ if group.empty?
61
+ group.clear
62
+ file_refs = Array.new
63
+ Dir.foreach(@api_install_dir) do |file|
64
+ if !File.directory?(file)
65
+ file_refs << group.new_reference(File.join(@api_install_dir, file))
66
+ end
67
+ end
68
+ target.add_file_references(file_refs)
69
+ changed = true
70
+ end
71
+
72
+ # Create directory
73
+ client_dir = File.join(@api_install_dir, @client)
74
+ if !File.exists?(client_dir)
75
+ Dir.mkdir client_dir
76
+ changed = true
77
+ end
78
+
79
+ # Create json file
80
+ json_path = File.join(client_dir, "#{@action}.json")
81
+ if !File.exists?(json_path)
82
+ json_file = File.new(json_path, 'w')
83
+ json_file.puts("{\n \n}")
84
+ changed = true
85
+ end
86
+
87
+ # Add json file reference
88
+ group = group.find_subpath(@client, true)
89
+ if group.empty?
90
+ group.clear
91
+ file_refs = Array.new
92
+ file_refs << group.new_reference(json_path)
93
+ target.add_file_references(file_refs)
94
+ changed = true
95
+ end
96
+
97
+ # Save
98
+ if !changed
99
+ return
100
+ end
101
+
102
+ UI.section("Creating PBLocalhost/#{@client}/#{@action}.json.") do
103
+ project.save
104
+ @changed = true
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -5,33 +5,28 @@ require 'fileutils'
5
5
  module Pbind
6
6
  class Command
7
7
  class Watch < Command
8
- self.summary = 'Enable Pbind can watch sources and apply instant changes at runtime.'
8
+ self.abstract_command = false
9
+ self.summary = 'Enable the live load feature for Pbind.'
9
10
  self.description = <<-DESC
10
- 1. Download `PBPlayground` sources from GitHub, create a new group named `PBPlayground` and add it to the current project.
11
+ Downloads all dependencies of `PBLiveLoader` for the Xcode project.
11
12
 
12
- 2. Modify the project `Info.plist` and set `PBResourcesPath` to `$(SRCROOT)/[project_name]`, `PBLocalhost` to `$(SRCROOT)/PBLocalhost`.
13
+ The Xcode project file should be specified in `--project` like this:
13
14
 
14
- 3. At runtime: `[PBPlayground load]` observe the application lifecycle and watch the above paths to apply instant changes.
15
- DESC
15
+ --project=path/to/Project.xcodeproj
16
16
 
17
- self.arguments = [
18
- CLAide::Argument.new('XCODEPROJ', true),
19
- ]
17
+ If no project is specified, then a search for an Xcode project will be made. If
18
+ more than one Xcode project is found, the command will raise an error.
20
19
 
21
- def initialize(argv)
22
- @project_path = argv.shift_argument
23
- end
20
+ This will configure the project to reference the Pbind LiveLoader library
21
+ and add a PBLocalhost directory for later JSON mocking.
22
+ DESC
24
23
 
25
24
  def validate!
26
- # super
27
- help! 'A path for the xcodeproj is required.' unless @project_path
28
- @project_path = @project_path.chomp('/')
29
- help! 'The xcodeproj path should ends with ".xcodeproj"' unless @project_path.end_with?('.xcodeproj')
30
- help! "The xcodeproj path '#{@project_path}' is not exists." unless File.exists?(@project_path)
25
+ verify_project_exists
31
26
  end
32
27
 
33
28
  def run
34
- @src_name = 'PBPlayground'
29
+ @src_name = 'PBLiveLoader'
35
30
  @api_name = 'PBLocalhost'
36
31
  @src_key = 'PBResourcesPath'
37
32
  @project_root = File.dirname(@project_path)
@@ -55,7 +50,7 @@ module Pbind
55
50
 
56
51
  # !@group Private helpers
57
52
 
58
- # Install the `PBPlayground`, `PBLocalhost` sources
53
+ # Install the `PBLiveLoader`, `PBLocalhost` sources
59
54
  #
60
55
  # @return [void]
61
56
  #
@@ -65,7 +60,7 @@ module Pbind
65
60
  end
66
61
 
67
62
  source_dir = ENV['PBIND_SOURCE']
68
- UI.section("Copying `#{source_dir}` into `#{@project_root}`.") do
63
+ UI.section("Copying [`#{@src_name}`, `#{@api_name}`] into `#{@project_root}`.") do
69
64
  FileUtils.cp_r File.join(source_dir, @src_name), @project_root
70
65
  FileUtils.cp_r File.join(source_dir, @api_name), @project_root
71
66
  @changed = true
@@ -113,7 +108,7 @@ module Pbind
113
108
  end
114
109
  end
115
110
 
116
- # Add `PBPlayground`, `PBLocalhost` group references to the project
111
+ # Add `PBLiveLoader`, `PBLocalhost` group references to the project
117
112
  #
118
113
  # @return [Bool] something changed
119
114
  #
@@ -122,7 +117,7 @@ module Pbind
122
117
  target = project.targets.first
123
118
  changed = false
124
119
 
125
- # Add PBPlayground group
120
+ # Add PBLiveLoader group
126
121
  group = project.main_group.find_subpath(@src_name, true)
127
122
  if group.empty?
128
123
  group.set_source_tree('SOURCE_ROOT')
@@ -0,0 +1,38 @@
1
+ //
2
+ // PBDirectoryWatcher.h
3
+ // Pbind
4
+ //
5
+ // Created by Galen Lin on 2016/12/9.
6
+ //
7
+
8
+ #include <targetconditionals.h>
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #import <Foundation/Foundation.h>
13
+
14
+ typedef enum : NSUInteger {
15
+ PBDirEventNewFolder,
16
+ PBDirEventNewFile,
17
+ PBDirEventDeleteFolder,
18
+ PBDirEventDeleteFile,
19
+ PBDirEventModifyFile
20
+ } PBDirEvent;
21
+
22
+ /**
23
+ A watcher that recursively watch directories|files create|modify|delete events.
24
+ */
25
+ @interface PBDirectoryWatcher : NSObject
26
+
27
+ /**
28
+ Start watching the directory.
29
+
30
+ @param dir the watching directory root.
31
+ @param handler the handler to handle all the events.
32
+ */
33
+ - (void)watchDir:(NSString *)dir
34
+ handler:(void (^)(NSString *path, BOOL initial, PBDirEvent event))handler;
35
+
36
+ @end
37
+
38
+ #endif
@@ -0,0 +1,466 @@
1
+ //
2
+ // PBDirectoryWatcher.m
3
+ // Pbind
4
+ //
5
+ // Created by Galen Lin on 2016/12/9.
6
+ //
7
+
8
+ #import "PBDirectoryWatcher.h"
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #include <stdio.h>
13
+ #include <string.h>
14
+ #include <sys/types.h>
15
+ #include <dirent.h>
16
+ #include <sys/stat.h>
17
+
18
+ #include <fcntl.h>
19
+ #include <unistd.h>
20
+ #include <sys/event.h>
21
+
22
+ typedef void(^file_handler)(const char *dir, const char *file, const char *name, BOOL isDir);
23
+ static int walk_dir(const char *path, int depth_limit, file_handler handler);
24
+
25
+ //
26
+
27
+ @interface _PBMonitorFile : NSObject
28
+
29
+ @property (nonatomic, strong) NSString *path;
30
+ @property (nonatomic, assign) NSTimeInterval lastModified;
31
+
32
+ - (instancetype)initWithPath:(NSString *)path;
33
+ - (BOOL)updateLastModified;
34
+
35
+ @end
36
+
37
+ //
38
+
39
+ @class _PBDirWatcher;
40
+ @protocol _PBDirWatcherDelegate <NSObject>
41
+
42
+ - (void)directoryDidChange:(_PBDirWatcher *)watcher;
43
+
44
+ @end
45
+
46
+ //
47
+
48
+ @interface _PBDirWatcher : NSObject
49
+
50
+ @property (nonatomic, readonly) NSString *path;
51
+ @property (copy, nonatomic) void (^update)(void);
52
+
53
+ - (instancetype)initWithDir:(NSString *)dir file:(NSString *)file delegate:(id<_PBDirWatcherDelegate>)delegate;
54
+
55
+ - (void)addSubdir:(NSString *)dir;
56
+ - (void)addFile:(NSString *)file;
57
+ - (_PBMonitorFile *)fileForPath:(NSString *)path;
58
+ - (NSArray<NSString *> *)updateSubdirs:(NSArray<NSString *> *)subdirs;
59
+ - (NSArray<NSString *> *)updateFiles:(NSArray<NSString *> *)subdirs;
60
+
61
+ - (void)invalidate;
62
+
63
+ @end
64
+
65
+
66
+ @interface PBDirectoryWatcher () <_PBDirWatcherDelegate>
67
+ {
68
+ NSMutableArray<_PBDirWatcher *> *_dirWatchers;
69
+ NSArray<NSString *> *_extensions;
70
+ void (^_handler)(NSString *path, BOOL initial, PBDirEvent event);
71
+ }
72
+
73
+ @end
74
+
75
+ @implementation PBDirectoryWatcher
76
+
77
+ - (void)watchDir:(NSString *)dir
78
+ handler:(void (^)(NSString *path, BOOL initial, PBDirEvent event))handler
79
+ {
80
+ _dirWatchers = [[NSMutableArray alloc] init];
81
+ _handler = handler;
82
+ [self scan:dir watcher:nil];
83
+ }
84
+
85
+ - (void)scan:(NSString *)path watcher:(_PBDirWatcher *)watcher
86
+ {
87
+ BOOL initial = (watcher == nil);
88
+ int walk_depth = initial ? 0 : 1;
89
+
90
+ NSMutableArray *subdirs;
91
+ NSMutableArray *files;
92
+ if (!initial) {
93
+ subdirs = [[NSMutableArray alloc] init];
94
+ files = [[NSMutableArray alloc] init];
95
+ }
96
+
97
+ walk_dir([path UTF8String], walk_depth, ^(const char *dir, const char *file, const char *name, BOOL isDir) {
98
+ if (isDir) {
99
+ NSString *resDir = [[NSString alloc] initWithUTF8String:file];
100
+ NSString *parentDir = [[NSString alloc] initWithUTF8String:dir];
101
+ NSArray *filteredWatchers = [_dirWatchers filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"path == %@", parentDir]];
102
+ if (filteredWatchers.count == 1) {
103
+ _PBDirWatcher *parentWatcher = filteredWatchers[0];
104
+ [parentWatcher addSubdir:resDir];
105
+ }
106
+
107
+ filteredWatchers = [_dirWatchers filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"path == %@", resDir]];
108
+ if (filteredWatchers.count == 0) {
109
+ // New dir
110
+ _PBDirWatcher *dirMonitor = [[_PBDirWatcher alloc] initWithDir:resDir file:nil delegate:self];
111
+ [_dirWatchers addObject:dirMonitor];
112
+ _handler(resDir, initial, PBDirEventNewFolder);
113
+ }
114
+
115
+ if (!initial) {
116
+ [subdirs addObject:resDir];
117
+ }
118
+
119
+ return;
120
+ }
121
+
122
+ NSString *resDir = [[NSString alloc] initWithUTF8String:dir];
123
+ NSString *resFilePath = [[NSString alloc] initWithUTF8String:file];
124
+ NSArray *filteredWatchers = [_dirWatchers filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"path == %@", resDir]];
125
+ _PBDirWatcher *dirWatcher;
126
+ if (filteredWatchers.count == 0) {
127
+ // New dir to watch
128
+ dirWatcher = [[_PBDirWatcher alloc] initWithDir:resDir file:resFilePath delegate:self];
129
+ [_dirWatchers addObject:dirWatcher];
130
+ _handler(resDir, initial, PBDirEventNewFolder);
131
+ _handler(resFilePath, initial, PBDirEventNewFile); // notify change
132
+ } else {
133
+ // Old dir had watch
134
+ dirWatcher = filteredWatchers[0];
135
+ _PBMonitorFile *resFile = [dirWatcher fileForPath:resFilePath];
136
+ if (resFile == nil) {
137
+ // New file
138
+ [dirWatcher addFile:resFilePath];
139
+
140
+ _handler(resFilePath, initial, PBDirEventNewFile); // notify change
141
+ } else {
142
+ // Old file
143
+ if ([resFile updateLastModified]) {
144
+ // Modified
145
+
146
+ _handler(resFilePath, initial, PBDirEventModifyFile); // notify change
147
+ }
148
+ }
149
+ }
150
+
151
+ if (!initial) {
152
+ [files addObject:resFilePath];
153
+ }
154
+ });
155
+
156
+ if (!initial) {
157
+ // Check if any directory be deleted.
158
+ NSArray *deletedDirs = [watcher updateSubdirs:subdirs];
159
+ if (deletedDirs != nil) {
160
+ // Unwatch
161
+ NSArray *filters = [_dirWatchers filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"path in %@", deletedDirs]];
162
+ for (_PBDirWatcher *watcher in filters) {
163
+ [watcher invalidate];
164
+ }
165
+ [_dirWatchers removeObjectsInArray:filters];
166
+
167
+ // Notify change
168
+ for (NSString *dir in deletedDirs) {
169
+ _handler(dir, initial, PBDirEventDeleteFolder);
170
+ }
171
+ }
172
+
173
+ // Check if any file be deleted.
174
+ NSArray *deletedFiles = [watcher updateFiles:files];
175
+ if (deletedFiles != nil) {
176
+ for (NSString *file in deletedFiles) {
177
+ _handler(file, initial, PBDirEventDeleteFile);
178
+ }
179
+ }
180
+ }
181
+ }
182
+
183
+ - (void)directoryDidChange:(_PBDirWatcher *)watcher {
184
+ [self scan:watcher.path watcher:watcher];
185
+ }
186
+
187
+ - (void)dealloc
188
+ {
189
+ _dirWatchers = nil;
190
+ _handler = nil;
191
+ }
192
+
193
+ @end
194
+
195
+ //
196
+
197
+ @implementation _PBMonitorFile
198
+
199
+ - (instancetype)initWithPath:(NSString *)path
200
+ {
201
+ if (self = [super init]) {
202
+ _path = path;
203
+ _lastModified = [self currentLastModified];
204
+ }
205
+ return self;
206
+ }
207
+
208
+ - (BOOL)updateLastModified
209
+ {
210
+ NSTimeInterval currentLastModified = [self currentLastModified];
211
+ if (_lastModified != currentLastModified) {
212
+ _lastModified = currentLastModified;
213
+ return YES;
214
+ }
215
+ return NO;
216
+ }
217
+
218
+ - (NSTimeInterval)currentLastModified
219
+ {
220
+ NSError *error = nil;
221
+ NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:_path error:&error];
222
+ NSTimeInterval lastModified = 0;
223
+ if (error == nil) {
224
+ lastModified = [[attrs fileModificationDate] timeIntervalSince1970];
225
+ }
226
+ return lastModified;
227
+ }
228
+
229
+ @end
230
+
231
+ // Directory watcher
232
+ // @see https://developer.apple.com/library/content/samplecode/DocInteraction/Listings/Classes_DirectoryWatcher_m.html
233
+
234
+ @implementation _PBDirWatcher {
235
+ int kq;
236
+ int dirFD;
237
+ CFFileDescriptorRef dirKQRef;
238
+ __weak id<_PBDirWatcherDelegate> delegate;
239
+ NSMutableArray<NSString *> *subdirs;
240
+ NSMutableArray<_PBMonitorFile *> *files;
241
+ }
242
+
243
+ static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info) {
244
+ _PBDirWatcher* obj = (__bridge _PBDirWatcher*) info;
245
+
246
+ assert([obj isKindOfClass:[_PBDirWatcher class]]);
247
+ assert(kqRef == obj->dirKQRef);
248
+ assert(callBackTypes == kCFFileDescriptorReadCallBack);
249
+
250
+ [obj kqueueFired];
251
+ }
252
+
253
+ - (instancetype)initWithDir:(NSString *)dir file:(NSString *)file delegate:(id<_PBDirWatcherDelegate>)aDelegate
254
+ {
255
+ if (self = [super init]) {
256
+ kq = -1;
257
+ dirFD = -1;
258
+ delegate = aDelegate;
259
+
260
+ _path = dir;
261
+ files = [[NSMutableArray alloc] init];
262
+ if (file != nil) {
263
+ _PBMonitorFile *resFile = [[_PBMonitorFile alloc] initWithPath:file];
264
+ [files addObject:resFile];
265
+ }
266
+ [self start];
267
+ }
268
+ return self;
269
+ }
270
+
271
+ - (void)addSubdir:(NSString *)dir
272
+ {
273
+ if (subdirs == nil) {
274
+ subdirs = [[NSMutableArray alloc] init];
275
+ }
276
+ [subdirs addObject:dir];
277
+ }
278
+
279
+ - (void)addFile:(NSString *)file
280
+ {
281
+ _PBMonitorFile *resFile = [[_PBMonitorFile alloc] initWithPath:file];
282
+ [files addObject:resFile];
283
+ }
284
+
285
+ - (_PBMonitorFile *)fileForPath:(NSString *)path
286
+ {
287
+ NSArray *filteredFiles = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"path==%@", path]];
288
+ if (filteredFiles.count == 0) {
289
+ return nil;
290
+ }
291
+ return filteredFiles[0];
292
+ }
293
+
294
+ - (NSArray<NSString *> *)updateSubdirs:(NSArray<NSString *> *)the_subdirs
295
+ {
296
+ if (subdirs == nil) return nil;
297
+
298
+ NSArray *filters = [subdirs filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT SELF IN %@", the_subdirs]];
299
+ if (filters.count > 0) {
300
+ [subdirs removeObjectsInArray:filters];
301
+ return filters;
302
+ }
303
+
304
+ return nil;
305
+ }
306
+
307
+ - (NSArray<NSString *> *)updateFiles:(NSArray<NSString *> *)the_files
308
+ {
309
+ if (files == nil) return nil;
310
+
311
+ NSArray *filters = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT path IN %@", the_files]];
312
+ if (filters.count > 0) {
313
+ [files removeObjectsInArray:filters];
314
+ return [filters valueForKey:@"path"];
315
+ }
316
+
317
+ return nil;
318
+ }
319
+
320
+ - (void)dealloc {
321
+ [self invalidate];
322
+ }
323
+
324
+ - (void)kqueueFired {
325
+ assert(kq >= 0);
326
+
327
+ struct kevent event;
328
+ struct timespec timeout = {0, 0};
329
+ int eventCount;
330
+
331
+ eventCount = kevent(kq, NULL, 0, &event, 1, &timeout);
332
+ assert((eventCount >= 0) && (eventCount < 2));
333
+
334
+ // call our delegate of the directory change
335
+ [delegate directoryDidChange:self];
336
+
337
+ CFFileDescriptorEnableCallBacks(dirKQRef, kCFFileDescriptorReadCallBack);
338
+ }
339
+
340
+ - (void)start {
341
+ // Double initializing is not going to work...
342
+ if (dirKQRef != NULL) return;
343
+ if (dirFD != -1) return;
344
+ if (kq != -1) return;
345
+
346
+ if (_path == nil) return;
347
+
348
+ // Open the directory we're going to watch
349
+ dirFD = open([_path fileSystemRepresentation], O_EVTONLY);
350
+ if (dirFD < 0) return;
351
+
352
+ // Create a kqueue for our event messages...
353
+ kq = kqueue();
354
+ if (kq < 0) {
355
+ close(dirFD);
356
+ return;
357
+ }
358
+
359
+ struct kevent eventToAdd;
360
+ eventToAdd.ident = dirFD;
361
+ eventToAdd.filter = EVFILT_VNODE;
362
+ eventToAdd.flags = EV_ADD | EV_CLEAR;
363
+ eventToAdd.fflags = NOTE_WRITE;
364
+ eventToAdd.data = 0;
365
+ eventToAdd.udata = NULL;
366
+
367
+ int errNum = kevent(kq, &eventToAdd, 1, NULL, 0, NULL);
368
+ if (errNum != 0) {
369
+ close(kq);
370
+ close(dirFD);
371
+ return;
372
+ }
373
+
374
+ // Passing true in the third argument so CFFileDescriptorInvalidate will close kq.
375
+ CFFileDescriptorContext context = { 0, (__bridge void *)(self), NULL, NULL, NULL };
376
+ dirKQRef = CFFileDescriptorCreate(NULL, kq, true, KQCallback, &context);
377
+ if (dirKQRef == NULL) {
378
+ close(kq);
379
+ close(dirFD);
380
+ return;
381
+ }
382
+
383
+ // Spin out a pluggable run loop source from the CFFileDescriptorRef
384
+ // Add it to the current run loop, then release it
385
+ CFRunLoopSourceRef rls = CFFileDescriptorCreateRunLoopSource(NULL, dirKQRef, 0);
386
+ if (rls == NULL) {
387
+ CFRelease(dirKQRef);
388
+ close(kq);
389
+ close(dirFD);
390
+ dirKQRef = NULL;
391
+ return;
392
+ }
393
+
394
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
395
+ CFRelease(rls);
396
+ CFFileDescriptorEnableCallBacks(dirKQRef, kCFFileDescriptorReadCallBack);
397
+ }
398
+
399
+ - (void)invalidate
400
+ {
401
+ if (dirKQRef != NULL) {
402
+ CFFileDescriptorInvalidate(dirKQRef);
403
+ CFRelease(dirKQRef);
404
+ dirKQRef = NULL;
405
+ // We don't need to close the kq, CFFileDescriptorInvalidate closed it instead.
406
+ // Change the value so no one thinks it's still live.
407
+ kq = -1;
408
+ }
409
+
410
+ if (dirFD != -1) {
411
+ close(dirFD);
412
+ dirFD = -1;
413
+ }
414
+ }
415
+
416
+ @end
417
+
418
+ #pragma mark -
419
+ #pragma mark - Directory walker with pure C.
420
+
421
+ static int walk_dir_sub(const char *path, int depth_limit, int depth, file_handler handler) {
422
+ DIR *d;
423
+ struct dirent *file;
424
+ struct stat sb;
425
+ char current[256];
426
+ int next_depth = depth + 1;
427
+
428
+ if (!(d = opendir(path))) {
429
+ NSLog(@"error opendir %s!", path);
430
+ return -1;
431
+ }
432
+
433
+ while ((file = readdir(d)) != NULL) {
434
+ const char *name = file->d_name;
435
+ if (*name == '.') {
436
+ continue;
437
+ }
438
+
439
+ sprintf(current, "%s/%s", path, name);
440
+ if (stat(current, &sb) < 0) {
441
+ continue;
442
+ }
443
+
444
+ if (S_ISDIR(sb.st_mode)) {
445
+ handler(path, current, name, true);
446
+ if (depth_limit == next_depth) {
447
+ continue;
448
+ }
449
+
450
+ walk_dir_sub(current, depth_limit, next_depth, handler);
451
+ continue;
452
+ }
453
+
454
+ handler(path, current, name, false);
455
+ }
456
+
457
+ closedir(d);
458
+
459
+ return 0;
460
+ }
461
+
462
+ static int walk_dir(const char *path, int depth_limit, file_handler handler) {
463
+ return walk_dir_sub(path, depth_limit, 0, handler);
464
+ }
465
+
466
+ #endif