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 +4 -4
- data/lib/pbind.rb +1 -88
- data/lib/pbind/command.rb +32 -2
- data/lib/pbind/command/mock.rb +109 -0
- data/lib/pbind/command/watch.rb +16 -21
- data/source/PBLiveLoader/PBDirectoryWatcher.h +38 -0
- data/source/PBLiveLoader/PBDirectoryWatcher.m +466 -0
- data/source/PBLiveLoader/PBLiveLoader.h +25 -0
- data/source/PBLiveLoader/PBLiveLoader.m +203 -0
- data/source/PBLocalhost/ignore.h +2 -2
- metadata +7 -6
- data/source/PBPlayground/PBPlayground.h +0 -19
- data/source/PBPlayground/PBPlayground.m +0 -237
- data/source/PBPlayground/SGDirWatchdog.h +0 -29
- data/source/PBPlayground/SGDirWatchdog.m +0 -163
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81e1f081ae44630a0c6fc01ddd9875ac5bc751ef
|
4
|
+
data.tar.gz: d12dfa76614ead7d6e29d8b90d1ff13f4d7382d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be3540ef369e882c103a469e87e76ba3463e4428c2371926e728683a0825308c562d9d54b9a57411ee390061ca060d8c70444d1535119088cc5a908ed4a00aa9
|
7
|
+
data.tar.gz: 62dd698fe2c428e4dfc06eddb24dc1791a476a4a1620ff898e45445a975a3c441dd11b191b46573d8da899f85adfed7bb945f868ffddf586ca9a9f78c867ab65
|
data/lib/pbind.rb
CHANGED
@@ -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
|
data/lib/pbind/command.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
data/lib/pbind/command/watch.rb
CHANGED
@@ -5,33 +5,28 @@ require 'fileutils'
|
|
5
5
|
module Pbind
|
6
6
|
class Command
|
7
7
|
class Watch < Command
|
8
|
-
self.
|
8
|
+
self.abstract_command = false
|
9
|
+
self.summary = 'Enable the live load feature for Pbind.'
|
9
10
|
self.description = <<-DESC
|
10
|
-
|
11
|
+
Downloads all dependencies of `PBLiveLoader` for the Xcode project.
|
11
12
|
|
12
|
-
|
13
|
+
The Xcode project file should be specified in `--project` like this:
|
13
14
|
|
14
|
-
|
15
|
-
DESC
|
15
|
+
--project=path/to/Project.xcodeproj
|
16
16
|
|
17
|
-
|
18
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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 = '
|
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 `
|
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 `#{
|
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 `
|
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
|
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
|