pbind 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|