pbind 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b1e4fc51921052509464b90b73e739278ea397f5
4
+ data.tar.gz: 870f80bf09baa2fd496be63832384670021cdb6f
5
+ SHA512:
6
+ metadata.gz: c6def51b03be6d197676e9f95e8e23f85c35eb70af527eb217c77a53500103cf80f922978ad4693d64823b732d57d0f89068e7cdbdd8f3c063d9df991e057c84
7
+ data.tar.gz: 90b2e4bc7751110ccedad5bcf935cf01e59769984d9d87d5741c02831729f626130a4cf7f951f60571a54b0e92268030e99387775c96531eb2eaab6b61e597cd
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5
+
6
+ require 'pbind'
7
+
8
+ ENV['PBIND_SOURCE'] = File.expand_path('../../source', __FILE__)
9
+
10
+ if profile_filename = ENV['PROFILE']
11
+ require 'ruby-prof'
12
+ reporter =
13
+ case (profile_extname = File.extname(profile_filename))
14
+ when '.txt'
15
+ RubyProf::FlatPrinterWithLineNumbers
16
+ when '.html'
17
+ RubyProf::GraphHtmlPrinter
18
+ when '.callgrind'
19
+ RubyProf::CallTreePrinter
20
+ else
21
+ raise "Unknown profiler format indicated by extension: #{profile_extname}"
22
+ end
23
+ File.open(profile_filename, 'w') do |io|
24
+ reporter.new(RubyProf.profile { Pbind::Command.run(ARGV) }).print(io)
25
+ end
26
+ else
27
+ Pbind::Command.run(ARGV)
28
+ end
@@ -0,0 +1,93 @@
1
+ require 'rubygems'
2
+ require 'xcodeproj'
3
+
4
+ module Pbind
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
@@ -0,0 +1,35 @@
1
+ require 'colored'
2
+ require 'claide'
3
+
4
+ module Pbind
5
+ class Command < CLAide::Command
6
+
7
+ require_relative 'command/watch'
8
+
9
+ self.abstract_command = true
10
+ self.command = 'pbind'
11
+ self.version = version
12
+ self.description = 'Pbind, the Pbind xcode project helper.'
13
+ self.plugin_prefixes = %w(claide pbind)
14
+
15
+ def self.report_error(exception)
16
+ case exception
17
+ when Interrupt
18
+ puts ''
19
+ puts '[!] Cancelled'.red
20
+ # Config.instance.verbose? ? raise : exit(1)
21
+ when SystemExit
22
+ raise
23
+ else
24
+ # if ENV['PBIND_ENV'] != 'development'
25
+ # puts UI::ErrorReport.report(exception)
26
+ # UI::ErrorReport.search_for_exceptions(exception)
27
+ # exit 1
28
+ # else
29
+ raise exception
30
+ # end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,165 @@
1
+ require 'xcodeproj'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+
5
+ module Pbind
6
+ class Command
7
+ class Watch < Command
8
+ self.summary = 'Enable Pbind can watch sources and apply instant changes at runtime.'
9
+ self.description = <<-DESC
10
+ 1. Download `PBPlayground` sources from GitHub, create a new group named `PBPlayground` and add it to the current project.
11
+
12
+ 2. Modify the project `Info.plist` and set `PBResourcesPath` to `$(SRCROOT)/[project_name]`, `PBLocalhost` to `$(SRCROOT)/PBLocalhost`.
13
+
14
+ 3. At runtime: `[PBPlayground load]` observe the application lifecycle and watch the above paths to apply instant changes.
15
+ DESC
16
+
17
+ self.arguments = [
18
+ CLAide::Argument.new('XCODEPROJ', true),
19
+ ]
20
+
21
+ def initialize(argv)
22
+ @project_path = argv.shift_argument
23
+ end
24
+
25
+ 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)
31
+ end
32
+
33
+ def run
34
+ @src_name = 'PBPlayground'
35
+ @api_name = 'PBLocalhost'
36
+ @src_key = 'PBResourcesPath'
37
+ @project_root = File.dirname(@project_path)
38
+ @src_install_dir = File.absolute_path(File.join(@project_root, @src_name))
39
+ @api_install_dir = File.absolute_path(File.join(@project_root, @api_name))
40
+ @project = Xcodeproj::Project.open(@project_path)
41
+ @changed = false
42
+
43
+ install_sources
44
+ add_plist_entries
45
+ add_group_references
46
+
47
+ if !@changed
48
+ puts 'All are UP-TO-DATE.'
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ #----------------------------------------#
55
+
56
+ # !@group Private helpers
57
+
58
+ # Install the `PBPlayground`, `PBLocalhost` sources
59
+ #
60
+ # @return [void]
61
+ #
62
+ def install_sources
63
+ if File.exists?(@src_install_dir)
64
+ return
65
+ end
66
+
67
+ source_dir = ENV['PBIND_SOURCE']
68
+ UI.section("Copying `#{source_dir}` into `#{@project_root}`.") do
69
+ FileUtils.cp_r File.join(source_dir, @src_name), @project_root
70
+ FileUtils.cp_r File.join(source_dir, @api_name), @project_root
71
+ @changed = true
72
+ end
73
+ end
74
+
75
+ # Add the source path to `Info.plist`
76
+ #
77
+ # @return [Bool] something changed
78
+ #
79
+ def add_plist_entries
80
+ project = @project
81
+ target = project.targets.first
82
+
83
+ source_root = File.join('$(SRCROOT)', target.name)
84
+ api_root = File.join('$(SRCROOT)', @api_name)
85
+
86
+ debug_cfg = target.build_configurations.detect { |e| e.name == 'Debug' }
87
+ info_plist = debug_cfg.build_settings['INFOPLIST_FILE']
88
+
89
+ info_plist_path = File.join(@project_root, info_plist)
90
+ if !File.exists?(info_plist_path)
91
+ puts "Failed to find `#{info_plist_path}`".red
92
+ return
93
+ end
94
+
95
+ changed = false
96
+ plist = Xcodeproj::Plist.read_from_path(info_plist_path)
97
+ if (plist[@src_key] != source_root)
98
+ plist[@src_key] = source_root
99
+ changed = true
100
+ end
101
+ if (plist[@api_name] != api_root)
102
+ plist[@api_name] = api_root
103
+ changed = true
104
+ end
105
+
106
+ if !changed
107
+ return
108
+ end
109
+
110
+ UI.section("Add plist entires to `#{@project_path}`.") do
111
+ Xcodeproj::Plist.write_to_path(plist, info_plist_path)
112
+ @changed = true
113
+ end
114
+ end
115
+
116
+ # Add `PBPlayground`, `PBLocalhost` group references to the project
117
+ #
118
+ # @return [Bool] something changed
119
+ #
120
+ def add_group_references
121
+ project = @project
122
+ target = project.targets.first
123
+ changed = false
124
+
125
+ # Add PBPlayground group
126
+ group = project.main_group.find_subpath(@src_name, true)
127
+ if group.empty?
128
+ group.set_source_tree('SOURCE_ROOT')
129
+ file_refs = Array.new
130
+ Dir.foreach(@src_install_dir) do |file|
131
+ if !File.directory?(file)
132
+ file_refs << group.new_reference(File.join(@src_install_dir, file))
133
+ end
134
+ end
135
+ target.add_file_references(file_refs)
136
+ changed = true
137
+ end
138
+
139
+ # Add PBLocalhost group
140
+ group = project.main_group.find_subpath(@api_name, true)
141
+ if group.empty?
142
+ group.clear
143
+ file_refs = Array.new
144
+ Dir.foreach(@api_install_dir) do |file|
145
+ if !File.directory?(file)
146
+ file_refs << group.new_reference(File.join(@api_install_dir, file))
147
+ end
148
+ end
149
+ target.add_file_references(file_refs)
150
+ changed = true
151
+ end
152
+
153
+ # Save
154
+ if !changed
155
+ return
156
+ end
157
+
158
+ UI.section("Add group [`#{@src_name}`, `#{@api_name}`] to `#{@project_path}`.") do
159
+ project.save
160
+ @changed = true
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,2 @@
1
+ // Add API actions at each line to prevent it from local testing.
2
+ // Comment|uncomment(`[Cmd]+/`) to switch whether ignores it or not.
@@ -0,0 +1,19 @@
1
+ //
2
+ // PBPlayground.h
3
+ // Pbind
4
+ //
5
+ // Created by Galen Lin on 16/9/22.
6
+ // Copyright © 2016年 galenlin. All rights reserved.
7
+ //
8
+
9
+ #include <targetconditionals.h>
10
+
11
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
12
+
13
+ #import <Foundation/Foundation.h>
14
+
15
+ @interface PBPlayground : NSObject
16
+
17
+ @end
18
+
19
+ #endif
@@ -0,0 +1,237 @@
1
+ //
2
+ // PBPlayground.m
3
+ // Pbind
4
+ //
5
+ // Created by Galen Lin on 16/9/22.
6
+ // Copyright © 2016年 galenlin. All rights reserved.
7
+ //
8
+
9
+ #import "PBPlayground.h"
10
+
11
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
12
+
13
+ #import "SGDirWatchdog.h"
14
+ #import <Pbind/Pbind.h>
15
+
16
+ #include <stdio.h>
17
+ #include <string.h>
18
+ #include <sys/types.h>
19
+ #include <dirent.h>
20
+ #include <sys/stat.h>
21
+
22
+ typedef void(^file_handler)(const char *parent, const char *current, const char *name);
23
+
24
+ int walk_dir(const char *path, int depth, file_handler handler) {
25
+ DIR *d;
26
+ struct dirent *file;
27
+ struct stat sb;
28
+ char subdir[256];
29
+
30
+ if (!(d = opendir(path))) {
31
+ NSLog(@"error opendir %s!", path);
32
+ return -1;
33
+ }
34
+
35
+ while ((file = readdir(d)) != NULL) {
36
+ const char *name = file->d_name;
37
+ if (*name == '.') {
38
+ continue;
39
+ }
40
+
41
+ sprintf(subdir, "%s/%s", path, name);
42
+ if (stat(subdir, &sb) < 0) {
43
+ continue;
44
+ }
45
+
46
+ if (S_ISDIR(sb.st_mode)) {
47
+ walk_dir(subdir, depth + 1, handler);
48
+ continue;
49
+ }
50
+
51
+ handler(path, subdir, name);
52
+ }
53
+
54
+ closedir(d);
55
+
56
+ return 0;
57
+ }
58
+
59
+ UIViewController *topcontroller(UIViewController *controller)
60
+ {
61
+ UIViewController *presentedController = [controller presentedViewController];
62
+ if (presentedController != nil) {
63
+ return topcontroller(controller);
64
+ }
65
+
66
+ if ([controller isKindOfClass:[UINavigationController class]]) {
67
+ return topcontroller([(id)controller topViewController]);
68
+ }
69
+
70
+ if ([controller isKindOfClass:[UITabBarController class]]) {
71
+ return topcontroller([(id)controller selectedViewController]);
72
+ }
73
+
74
+ return controller;
75
+ }
76
+
77
+ @implementation PBPlayground
78
+
79
+ static NSMutableDictionary<NSString *, SGDirWatchdog *> *kPlistWatchdogs;
80
+ static NSMutableDictionary<NSString *, SGDirWatchdog *> *kJsonWatchdogs;
81
+
82
+ static SGDirWatchdog *kIgnoreAPIWatchdog;
83
+ static NSArray *kIgnoreAPIs;
84
+ static NSString *kIgnoresFile;
85
+
86
+ static dispatch_block_t onPlistUpdate = ^{
87
+ UIViewController *controller = [[[UIApplication sharedApplication].delegate window] rootViewController];
88
+ controller = topcontroller(controller);
89
+ [controller.view pb_reloadPlist];
90
+ };
91
+
92
+ static dispatch_block_t onJsonUpdate = ^{
93
+ UIViewController *controller = [[[UIApplication sharedApplication].delegate window] rootViewController];
94
+ controller = topcontroller(controller);
95
+ [controller.view pb_reloadClient];
96
+ };
97
+
98
+ static dispatch_block_t onIgnoresUpdate = ^{
99
+ if (![[NSFileManager defaultManager] fileExistsAtPath:kIgnoresFile]) {
100
+ return;
101
+ }
102
+
103
+ NSString *content = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:kIgnoresFile] encoding:NSUTF8StringEncoding];
104
+ kIgnoreAPIs = [[content componentsSeparatedByString:@"\n"] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT (SELF BEGINSWITH '//')"]];
105
+ onJsonUpdate();
106
+ };
107
+
108
+
109
+ + (void)load {
110
+ [super load];
111
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:UIApplicationDidFinishLaunchingNotification object:nil];
112
+ }
113
+
114
+ + (void)applicationDidFinishLaunching:(id)note {
115
+ [self watchPlist];
116
+ [self watchAPI];
117
+ }
118
+
119
+ + (void)watchPlist {
120
+ NSString *resPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBResourcesPath"];
121
+ if (resPath == nil) {
122
+ NSLog(@"PBPlayground: Please define PBResourcesPath in Info.plist with value '$(SRCROOT)/[path-to-resources]'!");
123
+ return;
124
+ }
125
+
126
+ if (![[NSFileManager defaultManager] fileExistsAtPath:resPath]) {
127
+ NSLog(@"PBPlayground: PBResourcesPath is not exists! (%@)", resPath);
128
+ return;
129
+ }
130
+
131
+ NSMutableArray *resPaths = [[NSMutableArray alloc] init];
132
+
133
+ walk_dir([resPath UTF8String], 0, ^(const char *parent, const char *current, const char *name) {
134
+ size_t len = strlen(name);
135
+ if (len < 7) return;
136
+
137
+ char *p = (char *)name + len - 6;
138
+ if (strcmp(p, ".plist") != 0) return;
139
+
140
+ NSString *parentPath = [[NSString alloc] initWithUTF8String:parent];
141
+ if (![resPaths containsObject:parentPath]) {
142
+ [resPaths addObject:parentPath];
143
+ }
144
+ });
145
+
146
+ if (resPaths.count == 0) {
147
+ NSLog(@"PBPlayground: Could not found any *.plist!");
148
+ return;
149
+ }
150
+
151
+ kPlistWatchdogs = [NSMutableDictionary dictionaryWithCapacity:resPaths.count];
152
+
153
+ for (NSString *path in resPaths) {
154
+ [Pbind addResourcesBundle:[NSBundle bundleWithPath:path]];
155
+
156
+ SGDirWatchdog *watchdog = [[SGDirWatchdog alloc] initWithPath:path update:onPlistUpdate];
157
+ [kPlistWatchdogs setValue:watchdog forKey:path];
158
+ [watchdog start];
159
+ }
160
+ }
161
+
162
+ + (void)watchAPI {
163
+ NSString *serverPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBLocalhost"];
164
+ if (serverPath == nil) {
165
+ NSLog(@"PBPlayground: Please define PBLocalhost in Info.plist with value '$(SRCROOT)/[path-to-api]'!");
166
+ return;
167
+ }
168
+
169
+ if (![[NSFileManager defaultManager] fileExistsAtPath:serverPath]) {
170
+ NSLog(@"PBPlayground: PBLocalhost is not exists! (%@)", serverPath);
171
+ return;
172
+ }
173
+
174
+ kIgnoreAPIWatchdog = [[SGDirWatchdog alloc] initWithPath:serverPath update:onIgnoresUpdate];
175
+ kIgnoresFile = [serverPath stringByAppendingPathComponent:@"ignore.h"];
176
+ onIgnoresUpdate();
177
+ [kIgnoreAPIWatchdog start];
178
+
179
+ NSMutableArray *jsonPaths = [[NSMutableArray alloc] init];
180
+
181
+ walk_dir([serverPath UTF8String], 0, ^(const char *parent, const char *current, const char *name) {
182
+ size_t len = strlen(name);
183
+ if (len < 6) return;
184
+
185
+ char *p = (char *)name + len - 5;
186
+ if (strcmp(p, ".json") != 0) return;
187
+
188
+ NSString *parentPath = [[NSString alloc] initWithUTF8String:parent];
189
+ if (![jsonPaths containsObject:parentPath]) {
190
+ [jsonPaths addObject:parentPath];
191
+ }
192
+ });
193
+
194
+ if (jsonPaths.count == 0) {
195
+ NSLog(@"PBPlayground: Could not found any *.json!");
196
+ return;
197
+ }
198
+
199
+ kJsonWatchdogs = [NSMutableDictionary dictionaryWithCapacity:jsonPaths.count];
200
+
201
+ for (NSString *path in jsonPaths) {
202
+ SGDirWatchdog *watchdog = [[SGDirWatchdog alloc] initWithPath:path update:onJsonUpdate];
203
+ [kPlistWatchdogs setValue:watchdog forKey:path];
204
+ [watchdog start];
205
+ }
206
+
207
+ [PBClient registerDebugServer:^id(PBClient *client, PBRequest *request) {
208
+ NSString *action = request.action;
209
+ if ([action characterAtIndex:0] == '/') {
210
+ action = [action substringFromIndex:1]; // bypass '/'
211
+ }
212
+
213
+ if (kIgnoreAPIs != nil && [kIgnoreAPIs containsObject:action]) {
214
+ return nil;
215
+ }
216
+
217
+ NSString *jsonName = [NSString stringWithFormat:@"%@/%@.json", [[client class] description], action];
218
+ NSString *jsonPath = [serverPath stringByAppendingPathComponent:jsonName];
219
+ if (![[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) {
220
+ NSLog(@"PBPlayground: Missing '%@', ignores!", jsonName);
221
+ return nil;
222
+ }
223
+ NSData *jsonData = [NSData dataWithContentsOfFile:jsonPath];
224
+ NSError *error = nil;
225
+ id response = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
226
+ if (error != nil) {
227
+ NSLog(@"PBPlayground: Invalid '%@', ignores!", jsonName);
228
+ return nil;
229
+ }
230
+
231
+ return response;
232
+ }];
233
+ }
234
+
235
+ @end
236
+
237
+ #endif
@@ -0,0 +1,29 @@
1
+ //
2
+ // SGDirObserver.h
3
+ // DirectoryObserver
4
+ //
5
+ // Copyright (c) 2011 Simon Grätzer.
6
+ //
7
+
8
+ #include <targetconditionals.h>
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #import <Foundation/Foundation.h>
13
+
14
+ @interface SGDirWatchdog : NSObject
15
+
16
+ @property (readonly, nonatomic) NSString *path;
17
+ @property (copy, nonatomic) void (^update)(void);
18
+
19
+ + (NSString *)documentsPath;
20
+ + (id)watchtdogOnDocumentsDir:(void (^)(void))update;
21
+
22
+ - (id)initWithPath:(NSString *)path update:(void (^)(void))update;
23
+
24
+ - (void)start;
25
+ - (void)stop;
26
+
27
+ @end
28
+
29
+ #endif
@@ -0,0 +1,163 @@
1
+ //
2
+ // SGDirObserver.m
3
+ // DirectoryObserver
4
+ //
5
+ // Copyright (c) 2011 Simon Grätzer.
6
+ //
7
+
8
+ #import "SGDirWatchdog.h"
9
+
10
+ #if (DEBUG && TARGET_IPHONE_SIMULATOR)
11
+
12
+ #import <fcntl.h>
13
+ #import <unistd.h>
14
+ #import <sys/event.h>
15
+
16
+ @interface SGDirWatchdog ()
17
+ @property (nonatomic, readonly) CFFileDescriptorRef kqRef;
18
+ - (void)kqueueFired;
19
+ @end
20
+
21
+
22
+ static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info) {
23
+ // Pick up the object passed in the "info" member of the CFFileDescriptorContext passed to CFFileDescriptorCreate
24
+ SGDirWatchdog* obj = (__bridge SGDirWatchdog*) info;
25
+
26
+ if ([obj isKindOfClass:[SGDirWatchdog class]] && // If we can call back to the proper sort of object ...
27
+ (kqRef == obj.kqRef) && // and the FD that issued the CB is the expected one ...
28
+ (callBackTypes == kCFFileDescriptorReadCallBack) ) // and we're processing the proper sort of CB ...
29
+ {
30
+ [obj kqueueFired]; // Invoke the instance's CB handler
31
+ }
32
+ }
33
+
34
+ @implementation SGDirWatchdog {
35
+ int _dirFD;
36
+ CFFileDescriptorRef _kqRef;
37
+ }
38
+
39
+ + (NSString *)documentsPath {
40
+ NSArray *documentsPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
41
+
42
+ return documentsPaths[0]; // Path to the application's "Documents" directory
43
+ }
44
+
45
+ + (id)watchtdogOnDocumentsDir:(void (^)(void))update; {
46
+ return [[SGDirWatchdog alloc]initWithPath:[self documentsPath] update:update];
47
+ }
48
+
49
+
50
+ - (id)initWithPath:(NSString *)path update:(void (^)(void))update; {
51
+ if ((self = [super init])) {
52
+ _path = path;
53
+ _update = [update copy];
54
+ }
55
+ return self;
56
+ }
57
+
58
+ - (void)dealloc {
59
+ [self stop];
60
+
61
+
62
+ }
63
+
64
+ #pragma mark -
65
+ #pragma mark Extension methods
66
+
67
+ - (void)kqueueFired {
68
+ // Pull the native FD around which the CFFileDescriptor was wrapped
69
+ int kq = CFFileDescriptorGetNativeDescriptor(_kqRef);
70
+ if (kq < 0) return;
71
+
72
+ // If we pull a single available event out of the queue, assume the directory was updated
73
+ struct kevent event;
74
+ struct timespec timeout = {0, 0};
75
+ if (kevent(kq, NULL, 0, &event, 1, &timeout) == 1 && _update) {
76
+ _update();
77
+ }
78
+
79
+ // (Re-)Enable a one-shot (the only kind) callback
80
+ CFFileDescriptorEnableCallBacks(_kqRef, kCFFileDescriptorReadCallBack);
81
+ }
82
+
83
+
84
+ - (void)start {
85
+ // One ping only
86
+ if (_kqRef != NULL) return;
87
+
88
+ // Fetch pathname of the directory to monitor
89
+ NSString* docPath = self.path;
90
+ if (!docPath) return;
91
+
92
+ // Open an event-only file descriptor associated with the directory
93
+ int dirFD = open([docPath fileSystemRepresentation], O_EVTONLY);
94
+ if (dirFD < 0) return;
95
+
96
+ // Create a new kernel event queue
97
+ int kq = kqueue();
98
+ if (kq < 0)
99
+ {
100
+ close(dirFD);
101
+ return;
102
+ }
103
+
104
+ // Set up a kevent to monitor
105
+ struct kevent eventToAdd; // Register an (ident, filter) pair with the kqueue
106
+ eventToAdd.ident = dirFD; // The object to watch (the directory FD)
107
+ eventToAdd.filter = EVFILT_VNODE; // Watch for certain events on the VNODE spec'd by ident
108
+ eventToAdd.flags = EV_ADD | EV_CLEAR; // Add a resetting kevent
109
+ eventToAdd.fflags = NOTE_WRITE; // The events to watch for on the VNODE spec'd by ident (writes)
110
+ eventToAdd.data = 0; // No filter-specific data
111
+ eventToAdd.udata = NULL; // No user data
112
+
113
+ // Add a kevent to monitor
114
+ if (kevent(kq, &eventToAdd, 1, NULL, 0, NULL)) {
115
+ close(kq);
116
+ close(dirFD);
117
+ return;
118
+ }
119
+
120
+ // Wrap a CFFileDescriptor around a native FD
121
+ CFFileDescriptorContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
122
+ _kqRef = CFFileDescriptorCreate(NULL, // Use the default allocator
123
+ kq, // Wrap the kqueue
124
+ true, // Close the CFFileDescriptor if kq is invalidated
125
+ KQCallback, // Fxn to call on activity
126
+ &context); // Supply a context to set the callback's "info" argument
127
+ if (_kqRef == NULL) {
128
+ close(kq);
129
+ close(dirFD);
130
+ return;
131
+ }
132
+
133
+ // Spin out a pluggable run loop source from the CFFileDescriptorRef
134
+ // Add it to the current run loop, then release it
135
+ CFRunLoopSourceRef rls = CFFileDescriptorCreateRunLoopSource(NULL, _kqRef, 0);
136
+ if (rls == NULL) {
137
+ CFRelease(_kqRef); _kqRef = NULL;
138
+ close(kq);
139
+ close(dirFD);
140
+ return;
141
+ }
142
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
143
+ CFRelease(rls);
144
+
145
+ // Store the directory FD for later closing
146
+ _dirFD = dirFD;
147
+
148
+ // Enable a one-shot (the only kind) callback
149
+ CFFileDescriptorEnableCallBacks(_kqRef, kCFFileDescriptorReadCallBack);
150
+ }
151
+
152
+ - (void)stop {
153
+ if (_kqRef) {
154
+ close(_dirFD);
155
+ CFFileDescriptorInvalidate(_kqRef);
156
+ CFRelease(_kqRef);
157
+ _kqRef = NULL;
158
+ }
159
+ }
160
+
161
+ @end
162
+
163
+ #endif
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pbind
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Galen Lin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: xcodeproj
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.3.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: claide
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.0.1
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.0'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.0.1
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '2.0'
53
+ description: A toolkit to create a xcode project with Pbind.
54
+ email: oolgloo.2012@gmail.com
55
+ executables:
56
+ - pbind
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - bin/pbind
61
+ - lib/pbind.rb
62
+ - lib/pbind/command.rb
63
+ - lib/pbind/command/watch.rb
64
+ - source/PBLocalhost/ignore.h
65
+ - source/PBPlayground/PBPlayground.h
66
+ - source/PBPlayground/PBPlayground.m
67
+ - source/PBPlayground/SGDirWatchdog.h
68
+ - source/PBPlayground/SGDirWatchdog.m
69
+ homepage: http://rubygems.org/gems/pbind
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.6.6
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Pbind xcodeproj helper
93
+ test_files: []
94
+ has_rdoc: