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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c07d34ce4ff3b24370c314f9d036a3a7cec91b0f
4
- data.tar.gz: 04e7b6199d0c44ef14f99927325355f517a67eb7
3
+ metadata.gz: 2d28ab25efd022a0182268eb102046401ddb3800
4
+ data.tar.gz: 112719797adf95c13e034efd3bd70eab5f511983
5
5
  SHA512:
6
- metadata.gz: b8010b2ebce635a3c9927a2f557729eed3b2c2db0b4d43857c3f77cb0cfabb6c0bc586220c32973efdf90ad475fee4fedf019c040c614ed4c09d41948b8ac746
7
- data.tar.gz: c6bd623fd5a67ae102f68dfe2eb2bb75ef2aa006b298143345deffd0db3a6b7ae4d01c44a4764893512612d8c399564f91bdf66c3d59a823d027e759391a42a0
6
+ metadata.gz: 0d6ea7d843182955111dbab91c1fc64427ee05c49a10109669a4d81543e80dd69b2537f9d372e4a9a902cefe5431a8b5aec9811687d187b47f0d65ead3ee567a
7
+ data.tar.gz: fd041cc082b10871b081adf1d09d96fe6d1195d9690c4c6c53e54f372a05699b72b2d3a05afc2034a1750d1bd36958405546a4d47fb5fa4d9e7a2e141305cf4f
@@ -9,6 +9,7 @@ module Pbind
9
9
 
10
10
  require_relative 'command/watch'
11
11
  require_relative 'command/mock'
12
+ require_relative 'command/serv'
12
13
 
13
14
  self.abstract_command = true
14
15
  self.command = 'pbind'
@@ -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
@@ -36,7 +36,7 @@ module Pbind
36
36
  @changed = false
37
37
 
38
38
  install_sources
39
- add_plist_entries
39
+ # add_plist_entries
40
40
  add_group_references
41
41
 
42
42
  super
@@ -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
@@ -5,9 +5,7 @@
5
5
  // Created by Galen Lin on 2016/12/9.
6
6
  //
7
7
 
8
- #include <targetconditionals.h>
9
-
10
- #if (DEBUG && TARGET_IPHONE_SIMULATOR)
8
+ #if (DEBUG)
11
9
 
12
10
  #import <Foundation/Foundation.h>
13
11
 
@@ -6,12 +6,32 @@
6
6
  //
7
7
 
8
8
  #import "PBLiveLoader.h"
9
+ #include <targetconditionals.h>
9
10
 
10
- #if (DEBUG && TARGET_IPHONE_SIMULATOR)
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
- NSString *resPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBResourcesPath"];
46
- if (resPath == nil) {
47
- NSLog(@"PBLiveLoader: Please define PBResourcesPath in Info.plist with value '$(SRCROOT)/[path-to-resources]'!");
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
- NSString *serverPath = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"PBLocalhost"];
86
- if (serverPath == nil) {
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:^id(PBClient *client, PBRequest *request) {
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
- return nil;
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
- return nil;
174
+ complection(nil);
175
+ return;
146
176
  }
147
177
  NSData *jsonData = [NSData dataWithContentsOfFile:jsonPath];
148
- NSError *error = nil;
149
-
150
- PBResponse *response = [[PBResponse alloc] init];
151
- response.data = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
152
- if (error != nil) {
153
- NSLog(@"PBLiveLoader: Invalid '%@', ignores! The file format should be pure JSON style.", jsonName);
154
- return nil;
155
- }
156
-
157
- if ([response.data isKindOfClass:[NSDictionary class]]) {
158
- NSString *redirect = [response.data objectForKey:kDebugJSONRedirectKey];
159
- if (redirect != nil) {
160
- PBExpression *expression = [PBExpression expressionWithString:redirect];
161
- if (expression != nil) {
162
- response.data = [expression valueWithData:nil];
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
- NSString *statusString = [response.data objectForKey:kDebugJSONStatusKey];
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
- return response;
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.2
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.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: