pbind 0.4.2 → 0.5.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/command.rb +1 -0
- data/lib/pbind/command/serv.rb +175 -0
- data/lib/pbind/command/watch.rb +1 -1
- data/source/PBLiveLoader/NSInputStream+Reader.h +25 -0
- data/source/PBLiveLoader/NSInputStream+Reader.m +36 -0
- data/source/PBLiveLoader/PBLLInspector.h +23 -0
- data/source/PBLiveLoader/PBLLInspector.m +52 -0
- data/source/PBLiveLoader/PBLLInspectorController.h +19 -0
- data/source/PBLiveLoader/PBLLInspectorController.m +96 -0
- data/source/PBLiveLoader/PBLLRemoteWatcher.h +41 -0
- data/source/PBLiveLoader/PBLLRemoteWatcher.m +163 -0
- data/source/PBLiveLoader/PBLiveLoader.h +1 -3
- data/source/PBLiveLoader/PBLiveLoader.m +142 -47
- data/source/PBLiveLoader/PBSimulatorEnviroment.h +43 -0
- metadata +26 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d28ab25efd022a0182268eb102046401ddb3800
|
4
|
+
data.tar.gz: 112719797adf95c13e034efd3bd70eab5f511983
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d6ea7d843182955111dbab91c1fc64427ee05c49a10109669a4d81543e80dd69b2537f9d372e4a9a902cefe5431a8b5aec9811687d187b47f0d65ead3ee567a
|
7
|
+
data.tar.gz: fd041cc082b10871b081adf1d09d96fe6d1195d9690c4c6c53e54f372a05699b72b2d3a05afc2034a1750d1bd36958405546a4d47fb5fa4d9e7a2e141305cf4f
|
data/lib/pbind/command.rb
CHANGED
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'xcodeproj'
|
2
|
+
require 'pathname'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'listen'
|
5
|
+
|
6
|
+
require 'webrick'
|
7
|
+
include WEBrick
|
8
|
+
|
9
|
+
module Pbind
|
10
|
+
class Command
|
11
|
+
class Serv < Command
|
12
|
+
self.abstract_command = false
|
13
|
+
self.summary = 'Start a mock server for device.'
|
14
|
+
self.description = <<-DESC
|
15
|
+
This will start a HTTP server provides the APIs in PBLocalhost and
|
16
|
+
also a Socket server to send file changes to the device.
|
17
|
+
DESC
|
18
|
+
|
19
|
+
def validate!
|
20
|
+
verify_project_exists
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
@src_name = 'PBLiveLoader'
|
25
|
+
@api_name = 'PBLocalhost'
|
26
|
+
@src_key = 'PBResourcesPath'
|
27
|
+
@project_root = File.dirname(@project_path)
|
28
|
+
@src_install_dir = File.absolute_path(File.join(@project_root, @src_name))
|
29
|
+
@api_install_dir = File.absolute_path(File.join(@project_root, @api_name))
|
30
|
+
@project = Xcodeproj::Project.open(@project_path)
|
31
|
+
@changed = false
|
32
|
+
|
33
|
+
listen_file_changes
|
34
|
+
open_tcp_server
|
35
|
+
|
36
|
+
trap("SIGINT") {
|
37
|
+
@server.close
|
38
|
+
@listener.stop
|
39
|
+
exit
|
40
|
+
}
|
41
|
+
|
42
|
+
super
|
43
|
+
# sleep
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
#----------------------------------------#
|
49
|
+
|
50
|
+
# !@group Private helpers
|
51
|
+
|
52
|
+
# Create a HTTP server and open it
|
53
|
+
#
|
54
|
+
# @return [void]
|
55
|
+
#
|
56
|
+
def open_tcp_server
|
57
|
+
require 'socket'
|
58
|
+
|
59
|
+
server = TCPServer.new 8082 # Server bind to port
|
60
|
+
@server = server
|
61
|
+
@clients = []
|
62
|
+
|
63
|
+
addr = server.addr
|
64
|
+
addr.shift
|
65
|
+
puts "server is on #{addr.join(':')}"
|
66
|
+
|
67
|
+
loop do
|
68
|
+
Thread.start(server.accept) do |client|
|
69
|
+
@clients.push client
|
70
|
+
loop do
|
71
|
+
line = client.readpartial(1024)
|
72
|
+
if line != nil and line.end_with?('.json')
|
73
|
+
send_json(@clients, line)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
client.close
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# loop do
|
81
|
+
# client = server.accept # Wait for a client to connect
|
82
|
+
# @client = client
|
83
|
+
|
84
|
+
# line = client.readpartial(1024)
|
85
|
+
# puts "get #{line}"
|
86
|
+
# if line != nil and line.end_with?('.json')
|
87
|
+
# puts "send #{line}"
|
88
|
+
# send_json(client, line)
|
89
|
+
# end
|
90
|
+
|
91
|
+
# client.close
|
92
|
+
# end
|
93
|
+
end
|
94
|
+
|
95
|
+
def listen_file_changes
|
96
|
+
@listener = Listen.to(@project_root) do |modified, added, removed|
|
97
|
+
# puts "modified absolute path: #{modified}"
|
98
|
+
# puts "added absolute path: #{added}"
|
99
|
+
# puts "removed absolute path: #{removed}"
|
100
|
+
|
101
|
+
modified.each { |m|
|
102
|
+
if m.end_with?('.plist')
|
103
|
+
if @clients != nil
|
104
|
+
send_plist @clients, m
|
105
|
+
end
|
106
|
+
elsif m.end_with?('.json')
|
107
|
+
if @clients != nil
|
108
|
+
send_file_update @clients, m, nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
@listener.start # not blocking
|
115
|
+
end
|
116
|
+
|
117
|
+
def send_json(clients, json_file)
|
118
|
+
file = File.open(File.join(@api_install_dir, json_file), "r")
|
119
|
+
content = file.read
|
120
|
+
file.close
|
121
|
+
|
122
|
+
UI.section("Send API \"/#{File.basename(json_file, '.json')}\"") {
|
123
|
+
clients.each { |client|
|
124
|
+
write_byte client, 0xE0
|
125
|
+
write_string client, content
|
126
|
+
}
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def send_plist(clients, plist_path)
|
131
|
+
# Create a binary plist
|
132
|
+
require 'tempfile'
|
133
|
+
plist_name = File.basename(plist_path)
|
134
|
+
temp = Tempfile.new(plist_name)
|
135
|
+
`plutil -convert binary1 #{plist_path} -o #{temp.path}`
|
136
|
+
|
137
|
+
send_file_update clients, temp.path, plist_name
|
138
|
+
end
|
139
|
+
|
140
|
+
def send_file_update(clients, file_path, file_name)
|
141
|
+
File.open(file_path, "r") { |file|
|
142
|
+
file_content = file.read
|
143
|
+
if file_name == nil
|
144
|
+
file_name = File.basename(file_path)
|
145
|
+
end
|
146
|
+
UI.section("Update file \"#{file_name}\"") {
|
147
|
+
clients.each { |client|
|
148
|
+
write_byte(client, 0xF1)
|
149
|
+
write_string(client, file_name)
|
150
|
+
write_string(client, file_content)
|
151
|
+
}
|
152
|
+
}
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def write_byte(client, b)
|
157
|
+
write_any client, [b].pack("C")
|
158
|
+
end
|
159
|
+
|
160
|
+
def write_string(client, text)
|
161
|
+
write_any client, [text.bytesize].pack("N")
|
162
|
+
write_any client, text
|
163
|
+
end
|
164
|
+
|
165
|
+
def write_any(client, obj)
|
166
|
+
begin
|
167
|
+
client.write obj
|
168
|
+
rescue Exception => e
|
169
|
+
@clients.delete client
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
data/lib/pbind/command/watch.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
//
|
2
|
+
// NSInputStream+Reader.h
|
3
|
+
// Pchat
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
// Copyright © 2017 galen. 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 NSInputStream (Reader)
|
16
|
+
|
17
|
+
- (int)readInt;
|
18
|
+
|
19
|
+
- (NSString *)readString;
|
20
|
+
|
21
|
+
- (NSData *)readData;
|
22
|
+
|
23
|
+
@end
|
24
|
+
|
25
|
+
#endif
|
@@ -0,0 +1,36 @@
|
|
1
|
+
//
|
2
|
+
// NSInputStream+Reader.m
|
3
|
+
// Pchat
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
// Copyright © 2017 galen. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#import "NSInputStream+Reader.h"
|
10
|
+
|
11
|
+
#if (DEBUG && !(TARGET_IPHONE_SIMULATOR))
|
12
|
+
|
13
|
+
@implementation NSInputStream (Reader)
|
14
|
+
|
15
|
+
- (int)readInt {
|
16
|
+
uint8_t bytes[4];
|
17
|
+
[self read:bytes maxLength:4];
|
18
|
+
int intData = *((int *)bytes);
|
19
|
+
return NSSwapInt(intData);
|
20
|
+
}
|
21
|
+
|
22
|
+
- (NSData *)readData {
|
23
|
+
int length = [self readInt];
|
24
|
+
uint8_t *bytes = malloc(length);
|
25
|
+
NSInteger len = [self read:bytes maxLength:length];
|
26
|
+
return [NSData dataWithBytes:bytes length:len];
|
27
|
+
}
|
28
|
+
|
29
|
+
- (NSString *)readString {
|
30
|
+
NSData *data = [self readData];
|
31
|
+
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
32
|
+
}
|
33
|
+
|
34
|
+
@end
|
35
|
+
|
36
|
+
#endif
|
@@ -0,0 +1,23 @@
|
|
1
|
+
//
|
2
|
+
// PBLLInspector.h
|
3
|
+
// Pchat
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
// Copyright © 2017 galen. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#include <targetconditionals.h>
|
10
|
+
|
11
|
+
#if (DEBUG && !(TARGET_IPHONE_SIMULATOR))
|
12
|
+
|
13
|
+
#import <UIKit/UIKit.h>
|
14
|
+
|
15
|
+
@interface PBLLInspector : UIButton
|
16
|
+
|
17
|
+
+ (instancetype)sharedInspector;
|
18
|
+
|
19
|
+
+ (void)addToWindow;
|
20
|
+
|
21
|
+
@end
|
22
|
+
|
23
|
+
#endif
|
@@ -0,0 +1,52 @@
|
|
1
|
+
//
|
2
|
+
// PBLLInspector.m
|
3
|
+
// Pbind
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
//
|
7
|
+
|
8
|
+
#import "PBLLInspector.h"
|
9
|
+
|
10
|
+
#if (DEBUG && !(TARGET_IPHONE_SIMULATOR))
|
11
|
+
|
12
|
+
#import "PBLLInspectorController.h"
|
13
|
+
#import <Pbind/Pbind.h>
|
14
|
+
|
15
|
+
@interface PBLLInspector () <UISearchBarDelegate>
|
16
|
+
|
17
|
+
@end
|
18
|
+
|
19
|
+
@implementation PBLLInspector
|
20
|
+
|
21
|
+
+ (instancetype)sharedInspector {
|
22
|
+
static PBLLInspector *o;
|
23
|
+
static dispatch_once_t onceToken;
|
24
|
+
dispatch_once(&onceToken, ^{
|
25
|
+
o = [PBLLInspector buttonWithType:UIButtonTypeCustom];
|
26
|
+
});
|
27
|
+
return o;
|
28
|
+
}
|
29
|
+
|
30
|
+
+ (void)addToWindow {
|
31
|
+
PBLLInspector *inspector = [self sharedInspector];
|
32
|
+
CGRect frame = [UIScreen mainScreen].bounds;
|
33
|
+
frame.origin.x = frame.size.width - 68.f;
|
34
|
+
frame.origin.y = frame.size.height - 100.f;
|
35
|
+
frame.size.width = 60.f;
|
36
|
+
frame.size.height = 44.f;
|
37
|
+
inspector.frame = frame;
|
38
|
+
inspector.backgroundColor = [UIColor greenColor];
|
39
|
+
[inspector setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
40
|
+
[inspector setTitle:@"Pbind" forState:UIControlStateNormal];
|
41
|
+
[inspector addTarget:inspector action:@selector(didClick:) forControlEvents:UIControlEventTouchUpInside];
|
42
|
+
[[[UIApplication sharedApplication].delegate window] addSubview:inspector];
|
43
|
+
}
|
44
|
+
|
45
|
+
- (void)didClick:(id)sender {
|
46
|
+
PBLLInspectorController *controller = [[PBLLInspectorController alloc] init];
|
47
|
+
[PBTopController().navigationController pushViewController:controller animated:YES];
|
48
|
+
}
|
49
|
+
|
50
|
+
@end
|
51
|
+
|
52
|
+
#endif
|
@@ -0,0 +1,19 @@
|
|
1
|
+
//
|
2
|
+
// PBLLInspectorController.h
|
3
|
+
// Pchat
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
// Copyright © 2017 galen. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#include <targetconditionals.h>
|
10
|
+
|
11
|
+
#if (DEBUG && !(TARGET_IPHONE_SIMULATOR))
|
12
|
+
|
13
|
+
#import <UIKit/UIKit.h>
|
14
|
+
|
15
|
+
@interface PBLLInspectorController : UIViewController
|
16
|
+
|
17
|
+
@end
|
18
|
+
|
19
|
+
#endif
|
@@ -0,0 +1,96 @@
|
|
1
|
+
//
|
2
|
+
// PBLLInspectorController.m
|
3
|
+
// Pchat
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
//
|
7
|
+
|
8
|
+
#import "PBLLInspectorController.h"
|
9
|
+
|
10
|
+
#if (DEBUG && !(TARGET_IPHONE_SIMULATOR))
|
11
|
+
|
12
|
+
#import "PBLLInspector.h"
|
13
|
+
#import "PBLLRemoteWatcher.h"
|
14
|
+
#import <Pbind/Pbind.h>
|
15
|
+
|
16
|
+
@interface PBLLInspectorController () <UISearchBarDelegate>
|
17
|
+
{
|
18
|
+
UISearchBar *_searchBar;
|
19
|
+
}
|
20
|
+
|
21
|
+
@end
|
22
|
+
|
23
|
+
@implementation PBLLInspectorController
|
24
|
+
|
25
|
+
- (void)viewDidLoad {
|
26
|
+
[super viewDidLoad];
|
27
|
+
// Do any additional setup after loading the view.
|
28
|
+
self.edgesForExtendedLayout = UIRectEdgeNone;
|
29
|
+
self.view.backgroundColor = [UIColor whiteColor];
|
30
|
+
CGRect frame = self.view.bounds;
|
31
|
+
frame.size.height = 44.f;
|
32
|
+
frame.origin.y = 16.f;
|
33
|
+
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:frame];
|
34
|
+
searchBar.text = [self defaultIP];
|
35
|
+
searchBar.delegate = self;
|
36
|
+
searchBar.returnKeyType = UIReturnKeyJoin;
|
37
|
+
[self.view addSubview:searchBar];
|
38
|
+
_searchBar = searchBar;
|
39
|
+
|
40
|
+
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTap:)];
|
41
|
+
[self.view addGestureRecognizer:tap];
|
42
|
+
}
|
43
|
+
|
44
|
+
- (void)viewWillAppear:(BOOL)animated {
|
45
|
+
[super viewWillAppear:animated];
|
46
|
+
[PBLLInspector sharedInspector].hidden = YES;
|
47
|
+
}
|
48
|
+
|
49
|
+
- (void)viewDidDisappear:(BOOL)animated {
|
50
|
+
[super viewDidDisappear:animated];
|
51
|
+
[PBLLInspector sharedInspector].hidden = NO;
|
52
|
+
}
|
53
|
+
|
54
|
+
- (void)didReceiveMemoryWarning {
|
55
|
+
[super didReceiveMemoryWarning];
|
56
|
+
// Dispose of any resources that can be recreated.
|
57
|
+
}
|
58
|
+
|
59
|
+
- (NSString *)defaultIP {
|
60
|
+
NSString *ip = [[NSUserDefaults standardUserDefaults] objectForKey:@"pbind.server.ip"];
|
61
|
+
if (ip == nil) {
|
62
|
+
ip = @"192.168.1.10";
|
63
|
+
}
|
64
|
+
return ip;
|
65
|
+
}
|
66
|
+
|
67
|
+
- (void)setDefaultIP:(NSString *)ip {
|
68
|
+
[[NSUserDefaults standardUserDefaults] setObject:ip forKey:@"pbind.server.ip"];
|
69
|
+
[[NSUserDefaults standardUserDefaults] synchronize];
|
70
|
+
}
|
71
|
+
|
72
|
+
#pragma mark - UISearchBarDelegate
|
73
|
+
|
74
|
+
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
|
75
|
+
NSString *ip = searchBar.text;
|
76
|
+
if (ip.length == 0) {
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
80
|
+
[self setDefaultIP:ip];
|
81
|
+
[[PBLLRemoteWatcher globalWatcher] connect:ip];
|
82
|
+
[self.navigationController popViewControllerAnimated:YES];
|
83
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
84
|
+
[PBTopController().view pb_reloadClient];
|
85
|
+
});
|
86
|
+
}
|
87
|
+
|
88
|
+
#pragma mark - Gesture
|
89
|
+
|
90
|
+
- (void)didTap:(id)sender {
|
91
|
+
[_searchBar resignFirstResponder];
|
92
|
+
}
|
93
|
+
|
94
|
+
@end
|
95
|
+
|
96
|
+
#endif
|
@@ -0,0 +1,41 @@
|
|
1
|
+
//
|
2
|
+
// PBLLRemoteWatcher.h
|
3
|
+
// Pchat
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
// Copyright © 2017 galen. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
#include <targetconditionals.h>
|
10
|
+
|
11
|
+
#if (DEBUG && !(TARGET_IPHONE_SIMULATOR))
|
12
|
+
|
13
|
+
#import <Foundation/Foundation.h>
|
14
|
+
|
15
|
+
@class PBLLRemoteWatcher;
|
16
|
+
|
17
|
+
@protocol PBLLRemoteWatcherDelegate <NSObject>
|
18
|
+
|
19
|
+
- (void)remoteWatcher:(PBLLRemoteWatcher *)watcher didUpdateFile:(NSString *)fileName withData:(NSData *)data;
|
20
|
+
|
21
|
+
@optional
|
22
|
+
|
23
|
+
- (void)remoteWatcher:(PBLLRemoteWatcher *)watcher didCreateFile:(NSString *)fileName;
|
24
|
+
- (void)remoteWatcher:(PBLLRemoteWatcher *)watcher didDeleteFile:(NSString *)fileName;
|
25
|
+
|
26
|
+
@end
|
27
|
+
|
28
|
+
@interface PBLLRemoteWatcher : NSObject
|
29
|
+
|
30
|
+
+ (instancetype)globalWatcher;
|
31
|
+
|
32
|
+
- (void)connect:(NSString *)ip;
|
33
|
+
- (void)connectDefaultIP;
|
34
|
+
|
35
|
+
- (void)requestAPI:(NSString *)api success:(void (^)(NSData *))success failure:(void (^)(NSError *))failure;
|
36
|
+
|
37
|
+
@property (nonatomic, assign) id<PBLLRemoteWatcherDelegate> delegate;
|
38
|
+
|
39
|
+
@end
|
40
|
+
|
41
|
+
#endif
|
@@ -0,0 +1,163 @@
|
|
1
|
+
//
|
2
|
+
// PBLLRemoteWatcher.m
|
3
|
+
// Pbind
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 15/03/2017.
|
6
|
+
//
|
7
|
+
|
8
|
+
#import "PBLLRemoteWatcher.h"
|
9
|
+
|
10
|
+
#if (DEBUG && !(TARGET_IPHONE_SIMULATOR))
|
11
|
+
|
12
|
+
#import "NSInputStream+Reader.h"
|
13
|
+
|
14
|
+
@interface PBLLRemoteWatcher () <NSStreamDelegate>
|
15
|
+
{
|
16
|
+
NSInputStream *inputStream;
|
17
|
+
NSOutputStream *outputStream;
|
18
|
+
void (^responseBlock)(NSData *);
|
19
|
+
BOOL streamOpened;
|
20
|
+
NSData *apiReqData;
|
21
|
+
NSString *tempResourcesPath;
|
22
|
+
NSString *connectedIP;
|
23
|
+
}
|
24
|
+
|
25
|
+
@end
|
26
|
+
|
27
|
+
@implementation PBLLRemoteWatcher
|
28
|
+
|
29
|
+
+ (instancetype)globalWatcher {
|
30
|
+
static PBLLRemoteWatcher *o;
|
31
|
+
static dispatch_once_t onceToken;
|
32
|
+
dispatch_once(&onceToken, ^{
|
33
|
+
o = [[PBLLRemoteWatcher alloc] init];
|
34
|
+
});
|
35
|
+
return o;
|
36
|
+
}
|
37
|
+
|
38
|
+
- (void)connect:(NSString *)ip {
|
39
|
+
[self close];
|
40
|
+
|
41
|
+
connectedIP = ip;
|
42
|
+
CFReadStreamRef readStream;
|
43
|
+
CFWriteStreamRef writeStream;
|
44
|
+
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)ip, 8082, &readStream, &writeStream);
|
45
|
+
inputStream = (__bridge NSInputStream *)readStream;
|
46
|
+
outputStream = (__bridge NSOutputStream *)writeStream;
|
47
|
+
inputStream.delegate = self;
|
48
|
+
outputStream.delegate = self;
|
49
|
+
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
50
|
+
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
51
|
+
[inputStream open];
|
52
|
+
[outputStream open];
|
53
|
+
}
|
54
|
+
|
55
|
+
- (void)connectDefaultIP {
|
56
|
+
NSString *ip = [[NSUserDefaults standardUserDefaults] objectForKey:@"pbind.server.ip"];
|
57
|
+
if (ip == nil) {
|
58
|
+
ip = @"192.168.1.2";
|
59
|
+
}
|
60
|
+
[self connect:ip];
|
61
|
+
}
|
62
|
+
|
63
|
+
- (void)requestAPI:(NSString *)api success:(void (^)(NSData *))success failure:(void (^)(NSError *))failure {
|
64
|
+
if (inputStream == nil) {
|
65
|
+
failure([NSError errorWithDomain:@"PBLLRemoteWatcher"
|
66
|
+
code:1
|
67
|
+
userInfo:@{NSLocalizedDescriptionKey: @"Watcher wasn't online!"}]);
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
responseBlock = success;
|
72
|
+
NSData *data = [api dataUsingEncoding:NSUTF8StringEncoding];
|
73
|
+
if (streamOpened) {
|
74
|
+
[outputStream write:[data bytes] maxLength:[data length]];
|
75
|
+
} else {
|
76
|
+
apiReqData = data;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
- (void)close {
|
81
|
+
if (inputStream != nil) {
|
82
|
+
[inputStream close];
|
83
|
+
inputStream = nil;
|
84
|
+
}
|
85
|
+
if (outputStream != nil) {
|
86
|
+
[outputStream close];
|
87
|
+
outputStream = nil;
|
88
|
+
}
|
89
|
+
streamOpened = NO;
|
90
|
+
}
|
91
|
+
|
92
|
+
#pragma mark - NSStreamDelegate
|
93
|
+
|
94
|
+
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
|
95
|
+
NSLog(@"socket: %i, %@", (int)eventCode, aStream);
|
96
|
+
switch (eventCode) {
|
97
|
+
case NSStreamEventOpenCompleted:
|
98
|
+
|
99
|
+
break;
|
100
|
+
case NSStreamEventHasSpaceAvailable:
|
101
|
+
if (aStream == outputStream) {
|
102
|
+
streamOpened = YES;
|
103
|
+
if (apiReqData != nil) {
|
104
|
+
[outputStream write:[apiReqData bytes] maxLength:[apiReqData length]];
|
105
|
+
apiReqData = nil;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
break;
|
109
|
+
case NSStreamEventHasBytesAvailable:
|
110
|
+
if (aStream == inputStream) {
|
111
|
+
NSLog(@"inputStream is ready.");
|
112
|
+
|
113
|
+
uint8_t type[1];
|
114
|
+
[inputStream read:type maxLength:1];
|
115
|
+
switch (*type) {
|
116
|
+
case 0xE0: { // Got json
|
117
|
+
NSData *jsonData = [inputStream readData];
|
118
|
+
if (responseBlock != nil) {
|
119
|
+
responseBlock(jsonData);
|
120
|
+
}
|
121
|
+
break;
|
122
|
+
}
|
123
|
+
case 0xF0: { // Create file
|
124
|
+
NSString *fileName = [inputStream readString];
|
125
|
+
[self.delegate remoteWatcher:self didCreateFile:fileName];
|
126
|
+
break;
|
127
|
+
}
|
128
|
+
case 0xF1: { // Update file
|
129
|
+
NSString *fileName = [inputStream readString];
|
130
|
+
NSData *fileData = [inputStream readData];
|
131
|
+
[self.delegate remoteWatcher:self didUpdateFile:fileName withData:fileData];
|
132
|
+
break;
|
133
|
+
}
|
134
|
+
case 0xF2: { // Delete file
|
135
|
+
NSString *fileName = [inputStream readString];
|
136
|
+
[self.delegate remoteWatcher:self didDeleteFile:fileName];
|
137
|
+
break;
|
138
|
+
}
|
139
|
+
default:
|
140
|
+
break;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
break;
|
144
|
+
case NSStreamEventErrorOccurred:
|
145
|
+
if (aStream == outputStream) {
|
146
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
147
|
+
[self connect:connectedIP];
|
148
|
+
});
|
149
|
+
}
|
150
|
+
break;
|
151
|
+
case NSStreamEventEndEncountered:
|
152
|
+
if (aStream == inputStream) {
|
153
|
+
[self close];
|
154
|
+
}
|
155
|
+
break;
|
156
|
+
default:
|
157
|
+
break;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
@end
|
162
|
+
|
163
|
+
#endif
|
@@ -6,12 +6,32 @@
|
|
6
6
|
//
|
7
7
|
|
8
8
|
#import "PBLiveLoader.h"
|
9
|
+
#include <targetconditionals.h>
|
9
10
|
|
10
|
-
#if (DEBUG
|
11
|
+
#if (DEBUG)
|
11
12
|
|
12
13
|
#import "PBDirectoryWatcher.h"
|
13
14
|
#import <Pbind/Pbind.h>
|
14
15
|
|
16
|
+
#import "PBSimulatorEnviroment.h"
|
17
|
+
|
18
|
+
#if !(TARGET_IPHONE_SIMULATOR)
|
19
|
+
|
20
|
+
#import "PBLLRemoteWatcher.h"
|
21
|
+
#import "PBLLInspector.h"
|
22
|
+
|
23
|
+
@interface PBLiveLoader () <PBLLRemoteWatcherDelegate>
|
24
|
+
{
|
25
|
+
void (^apiComplection)(PBResponse *);
|
26
|
+
NSData *apiReqData;
|
27
|
+
NSString *tempResourcesPath;
|
28
|
+
NSMutableDictionary *cacheResponseData;
|
29
|
+
}
|
30
|
+
|
31
|
+
@end
|
32
|
+
|
33
|
+
#endif
|
34
|
+
|
15
35
|
@implementation PBLiveLoader
|
16
36
|
|
17
37
|
static NSString *const kPlistSuffix = @".plist";
|
@@ -22,8 +42,11 @@ static NSString *const kDebugJSONRedirectKey = @"$redirect";
|
|
22
42
|
static NSString *const kDebugJSONStatusKey = @"$status";
|
23
43
|
|
24
44
|
static NSArray<NSString *> *kIgnoreAPIs;
|
45
|
+
|
46
|
+
#if (TARGET_IPHONE_SIMULATOR)
|
25
47
|
static PBDirectoryWatcher *kResWatcher;
|
26
48
|
static PBDirectoryWatcher *kAPIWatcher;
|
49
|
+
#endif
|
27
50
|
|
28
51
|
static BOOL HasSuffix(NSString *src, NSString *tail)
|
29
52
|
{
|
@@ -42,13 +65,9 @@ static BOOL HasSuffix(NSString *src, NSString *tail)
|
|
42
65
|
}
|
43
66
|
|
44
67
|
+ (void)watchPlist {
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
return;
|
49
|
-
}
|
50
|
-
|
51
|
-
if (![[NSFileManager defaultManager] fileExistsAtPath:resPath]) {
|
68
|
+
#if (TARGET_IPHONE_SIMULATOR)
|
69
|
+
NSString *resPath = PBLLMainBundlePath();
|
70
|
+
if (resPath == nil || ![[NSFileManager defaultManager] fileExistsAtPath:resPath]) {
|
52
71
|
NSLog(@"PBLiveLoader: PBResourcesPath is not exists! (%@)", resPath);
|
53
72
|
return;
|
54
73
|
}
|
@@ -79,15 +98,21 @@ static BOOL HasSuffix(NSString *src, NSString *tail)
|
|
79
98
|
break;
|
80
99
|
}
|
81
100
|
}];
|
101
|
+
#else
|
102
|
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
103
|
+
NSString *documentPath = paths.firstObject;
|
104
|
+
NSString *tempBundlePath = [documentPath stringByAppendingPathComponent:@".pb_liveload"];
|
105
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:tempBundlePath]) {
|
106
|
+
[[NSFileManager defaultManager] createDirectoryAtPath:tempBundlePath withIntermediateDirectories:NO attributes:nil error:nil];
|
107
|
+
}
|
108
|
+
[self defaultLoader]->tempResourcesPath = tempBundlePath;
|
109
|
+
[Pbind addResourcesBundle:[NSBundle bundleWithPath:tempBundlePath]];
|
110
|
+
#endif
|
82
111
|
}
|
83
112
|
|
84
113
|
+ (void)watchAPI {
|
85
|
-
|
86
|
-
|
87
|
-
NSLog(@"PBLiveLoader: Please define PBLocalhost in Info.plist with value '$(SRCROOT)/[path-to-api]'!");
|
88
|
-
return;
|
89
|
-
}
|
90
|
-
|
114
|
+
#if (TARGET_IPHONE_SIMULATOR)
|
115
|
+
NSString *serverPath = PBLLMockingAPIPath();
|
91
116
|
if (![[NSFileManager defaultManager] fileExistsAtPath:serverPath]) {
|
92
117
|
NSLog(@"PBLiveLoader: PBLocalhost is not exists! (%@)", serverPath);
|
93
118
|
return;
|
@@ -122,8 +147,10 @@ static BOOL HasSuffix(NSString *src, NSString *tail)
|
|
122
147
|
break;
|
123
148
|
}
|
124
149
|
}];
|
150
|
+
#endif
|
125
151
|
|
126
|
-
[PBClient registerDebugServer:^
|
152
|
+
[PBClient registerDebugServer:^(PBClient *client, PBRequest *request, void (^complection)(PBResponse *response)) {
|
153
|
+
|
127
154
|
NSString *action = request.action;
|
128
155
|
if ([action characterAtIndex:0] == '/') {
|
129
156
|
action = [action substringFromIndex:1]; // bypass '/'
|
@@ -135,49 +162,60 @@ static BOOL HasSuffix(NSString *src, NSString *tail)
|
|
135
162
|
}
|
136
163
|
action = [action stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
|
137
164
|
if (kIgnoreAPIs != nil && [kIgnoreAPIs containsObject:action]) {
|
138
|
-
|
165
|
+
complection(nil);
|
166
|
+
return;
|
139
167
|
}
|
140
168
|
|
141
169
|
NSString *jsonName = [NSString stringWithFormat:@"%@/%@.json", [[client class] description], action];
|
170
|
+
#if (TARGET_IPHONE_SIMULATOR)
|
142
171
|
NSString *jsonPath = [serverPath stringByAppendingPathComponent:jsonName];
|
143
172
|
if (![[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) {
|
144
173
|
NSLog(@"PBLiveLoader: Missing '%@', ignores!", jsonName);
|
145
|
-
|
174
|
+
complection(nil);
|
175
|
+
return;
|
146
176
|
}
|
147
177
|
NSData *jsonData = [NSData dataWithContentsOfFile:jsonPath];
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
178
|
+
[self receiveJsonData:jsonData withFile:jsonName complection:complection];
|
179
|
+
#else
|
180
|
+
[[self defaultLoader] requestAPI:jsonName complection:complection];
|
181
|
+
#endif
|
182
|
+
}];
|
183
|
+
}
|
184
|
+
|
185
|
+
+ (void)receiveJsonData:(NSData *)jsonData withFile:(NSString *)jsonName complection:(void (^)(PBResponse *))complection {
|
186
|
+
NSError *error = nil;
|
187
|
+
id data = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
|
188
|
+
if (data == nil) {
|
189
|
+
NSLog(@"PBLiveLoader: Invalid '%@', ignores! The file format should be pure JSON style.", jsonName);
|
190
|
+
complection(nil);
|
191
|
+
return;
|
192
|
+
}
|
193
|
+
|
194
|
+
PBResponse *response = [[PBResponse alloc] init];
|
195
|
+
if ([data isKindOfClass:[NSDictionary class]]) {
|
196
|
+
NSString *redirect = [data objectForKey:kDebugJSONRedirectKey];
|
197
|
+
if (redirect != nil) {
|
198
|
+
PBExpression *expression = [PBExpression expressionWithString:redirect];
|
199
|
+
if (expression != nil) {
|
200
|
+
data = [expression valueWithData:nil];
|
201
|
+
}
|
202
|
+
} else {
|
203
|
+
NSString *statusString = [data objectForKey:kDebugJSONStatusKey];
|
204
|
+
if (statusString != nil) {
|
205
|
+
response.status = [statusString intValue];
|
206
|
+
}
|
207
|
+
NSMutableDictionary *filteredDict = [NSMutableDictionary dictionaryWithDictionary:data];
|
208
|
+
[filteredDict removeObjectForKey:kDebugJSONStatusKey];
|
209
|
+
if (filteredDict.count == 0) {
|
210
|
+
data = nil;
|
164
211
|
} else {
|
165
|
-
|
166
|
-
if (statusString != nil) {
|
167
|
-
response.status = [statusString intValue];
|
168
|
-
}
|
169
|
-
NSMutableDictionary *filteredDict = [NSMutableDictionary dictionaryWithDictionary:response.data];
|
170
|
-
[filteredDict removeObjectForKey:kDebugJSONStatusKey];
|
171
|
-
if (filteredDict.count == 0) {
|
172
|
-
response.data = nil;
|
173
|
-
} else {
|
174
|
-
response.data = filteredDict;
|
175
|
-
}
|
212
|
+
data = filteredDict;
|
176
213
|
}
|
177
214
|
}
|
178
|
-
|
179
|
-
|
180
|
-
|
215
|
+
}
|
216
|
+
|
217
|
+
response.data = data;
|
218
|
+
complection(response);
|
181
219
|
}
|
182
220
|
|
183
221
|
+ (NSArray *)ignoreAPIsWithContentsOfFile:(NSString *)path {
|
@@ -225,6 +263,63 @@ static BOOL HasSuffix(NSString *src, NSString *tail)
|
|
225
263
|
}
|
226
264
|
}
|
227
265
|
|
266
|
+
#if !(TARGET_IPHONE_SIMULATOR)
|
267
|
+
|
268
|
+
+ (PBLiveLoader *)defaultLoader {
|
269
|
+
static PBLiveLoader *loader = nil;
|
270
|
+
static dispatch_once_t onceToken;
|
271
|
+
dispatch_once(&onceToken, ^{
|
272
|
+
loader = [[self alloc] init];
|
273
|
+
[PBLLRemoteWatcher globalWatcher].delegate = loader;
|
274
|
+
});
|
275
|
+
return loader;
|
276
|
+
}
|
277
|
+
|
278
|
+
- (void)requestAPI:(NSString *)api complection:(void (^)(PBResponse *))complection {
|
279
|
+
static dispatch_once_t onceToken;
|
280
|
+
dispatch_once(&onceToken, ^{
|
281
|
+
[PBLLInspector addToWindow];
|
282
|
+
[[PBLLRemoteWatcher globalWatcher] connectDefaultIP];
|
283
|
+
});
|
284
|
+
|
285
|
+
NSString *key = [api lastPathComponent];
|
286
|
+
NSData *cacheData = cacheResponseData[key];
|
287
|
+
if (cacheData != nil) {
|
288
|
+
[[self class] receiveJsonData:cacheData withFile:nil complection:complection];
|
289
|
+
[cacheResponseData removeObjectForKey:key];
|
290
|
+
return;
|
291
|
+
}
|
292
|
+
|
293
|
+
[[PBLLRemoteWatcher globalWatcher] requestAPI:api success:^(NSData *data) {
|
294
|
+
[[self class] receiveJsonData:data withFile:nil complection:complection];
|
295
|
+
} failure:^(NSError *error) {
|
296
|
+
complection(nil);
|
297
|
+
}];
|
298
|
+
}
|
299
|
+
|
300
|
+
#pragma mark - PBLLRemoteWatcherDelegate
|
301
|
+
|
302
|
+
- (void)remoteWatcher:(PBLLRemoteWatcher *)watcher didReceiveResponse:(NSData *)jsonData {
|
303
|
+
[[self class] receiveJsonData:jsonData withFile:nil complection:apiComplection];
|
304
|
+
}
|
305
|
+
|
306
|
+
- (void)remoteWatcher:(PBLLRemoteWatcher *)watcher didUpdateFile:(NSString *)fileName withData:(NSData *)data {
|
307
|
+
if (HasSuffix(fileName, @".plist")) {
|
308
|
+
NSString *plist = [tempResourcesPath stringByAppendingPathComponent:fileName];
|
309
|
+
[data writeToFile:plist atomically:NO];
|
310
|
+
|
311
|
+
[Pbind reloadViewsOnPlistUpdate:fileName];
|
312
|
+
} else if (HasSuffix(fileName, @".json")) {
|
313
|
+
if (cacheResponseData == nil) {
|
314
|
+
cacheResponseData = [[NSMutableDictionary alloc] init];
|
315
|
+
}
|
316
|
+
cacheResponseData[fileName] = data;
|
317
|
+
[Pbind reloadViewsOnAPIUpdate:fileName];
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
#endif
|
322
|
+
|
228
323
|
@end
|
229
324
|
|
230
325
|
#endif
|
@@ -0,0 +1,43 @@
|
|
1
|
+
//
|
2
|
+
// PBSimulatorEnviroment.h
|
3
|
+
// Pbind
|
4
|
+
//
|
5
|
+
// Created by Galen Lin on 13/03/2017.
|
6
|
+
//
|
7
|
+
|
8
|
+
#if (DEBUG)
|
9
|
+
|
10
|
+
#include <targetconditionals.h>
|
11
|
+
#import <Foundation/Foundation.h>
|
12
|
+
|
13
|
+
#if (TARGET_IPHONE_SIMULATOR)
|
14
|
+
|
15
|
+
FOUNDATION_STATIC_INLINE NSString *PBLLProjectPath() {
|
16
|
+
return [[@(__FILE__) stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
|
17
|
+
}
|
18
|
+
|
19
|
+
FOUNDATION_STATIC_INLINE NSString *PBLLMainBundlePath() {
|
20
|
+
NSString *projectPath = PBLLProjectPath();
|
21
|
+
NSString *bundlePath = [projectPath stringByAppendingPathComponent:[projectPath lastPathComponent]];
|
22
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
|
23
|
+
return bundlePath;
|
24
|
+
}
|
25
|
+
|
26
|
+
NSArray *subdirs = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:projectPath error:nil];
|
27
|
+
for (NSString *subdir in subdirs) {
|
28
|
+
bundlePath = [projectPath stringByAppendingPathComponent:subdir];
|
29
|
+
NSString *mainFile = [bundlePath stringByAppendingPathComponent:@"main.m"];
|
30
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:mainFile]) {
|
31
|
+
return bundlePath;
|
32
|
+
}
|
33
|
+
}
|
34
|
+
return nil;
|
35
|
+
}
|
36
|
+
|
37
|
+
FOUNDATION_STATIC_INLINE NSString *PBLLMockingAPIPath() {
|
38
|
+
return [PBLLProjectPath() stringByAppendingPathComponent:@"PBLocalhost"];
|
39
|
+
}
|
40
|
+
|
41
|
+
#endif
|
42
|
+
|
43
|
+
#endif
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pbind
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Galen Lin
|
@@ -50,6 +50,20 @@ dependencies:
|
|
50
50
|
- - "<"
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '2.0'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: listen
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - '='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 3.1.5
|
60
|
+
type: :runtime
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - '='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 3.1.5
|
53
67
|
description: A toolkit to create a xcode project with Pbind.
|
54
68
|
email: oolgloo.2012@gmail.com
|
55
69
|
executables:
|
@@ -61,13 +75,23 @@ files:
|
|
61
75
|
- lib/pbind.rb
|
62
76
|
- lib/pbind/command.rb
|
63
77
|
- lib/pbind/command/mock.rb
|
78
|
+
- lib/pbind/command/serv.rb
|
64
79
|
- lib/pbind/command/watch.rb
|
65
80
|
- lib/pbind/gem_version.rb
|
66
81
|
- lib/pbind/user_interface.rb
|
82
|
+
- source/PBLiveLoader/NSInputStream+Reader.h
|
83
|
+
- source/PBLiveLoader/NSInputStream+Reader.m
|
67
84
|
- source/PBLiveLoader/PBDirectoryWatcher.h
|
68
85
|
- source/PBLiveLoader/PBDirectoryWatcher.m
|
86
|
+
- source/PBLiveLoader/PBLLInspector.h
|
87
|
+
- source/PBLiveLoader/PBLLInspector.m
|
88
|
+
- source/PBLiveLoader/PBLLInspectorController.h
|
89
|
+
- source/PBLiveLoader/PBLLInspectorController.m
|
90
|
+
- source/PBLiveLoader/PBLLRemoteWatcher.h
|
91
|
+
- source/PBLiveLoader/PBLLRemoteWatcher.m
|
69
92
|
- source/PBLiveLoader/PBLiveLoader.h
|
70
93
|
- source/PBLiveLoader/PBLiveLoader.m
|
94
|
+
- source/PBLiveLoader/PBSimulatorEnviroment.h
|
71
95
|
- source/PBLocalhost/ignore.h
|
72
96
|
homepage: http://rubygems.org/gems/pbind
|
73
97
|
licenses:
|
@@ -89,9 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
113
|
version: '0'
|
90
114
|
requirements: []
|
91
115
|
rubyforge_project:
|
92
|
-
rubygems_version: 2.6.
|
116
|
+
rubygems_version: 2.6.8
|
93
117
|
signing_key:
|
94
118
|
specification_version: 4
|
95
119
|
summary: Pbind xcodeproj helper
|
96
120
|
test_files: []
|
97
|
-
has_rdoc:
|