rhoconnect 3.0.5 → 3.0.6

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.
Files changed (73) hide show
  1. data/CHANGELOG.md +7 -1
  2. data/Gemfile.lock +9 -9
  3. data/README.md +4 -4
  4. data/Rakefile +41 -28
  5. data/bench/bench_runner.rb +4 -5
  6. data/bench/benchapp/Gemfile +1 -1
  7. data/bench/benchapp/Gemfile.lock +26 -27
  8. data/bench/benchapp/config.ru +4 -0
  9. data/bench/benchapp/log/passenger.3000.log +1 -0
  10. data/bench/benchapp/log/passenger.9292.log +59 -0
  11. data/bench/benchapp/settings/settings.yml +2 -0
  12. data/bench/benchapp/tmp/pids/passenger.3000.pid.lock +0 -0
  13. data/bench/benchapp/tmp/pids/passenger.9292.pid.lock +0 -0
  14. data/bench/distr_bench/distr_bench +7 -0
  15. data/bench/distr_bench/distr_bench_main +99 -0
  16. data/bench/distr_bench/run_distr_client.sh +12 -0
  17. data/bench/distr_bench/run_test_query_script.sh +50 -0
  18. data/bench/lib/bench/bench_result_processor.rb +90 -0
  19. data/bench/lib/bench/cli.rb +35 -2
  20. data/bench/lib/bench/logging.rb +2 -0
  21. data/bench/lib/bench/runner.rb +4 -2
  22. data/bench/lib/bench/session.rb +3 -1
  23. data/bench/lib/bench/statistics.rb +41 -1
  24. data/bench/lib/bench/timer.rb +7 -0
  25. data/bench/lib/bench/utils.rb +8 -0
  26. data/bench/lib/bench.rb +35 -9
  27. data/bench/lib/testdata/0-data.txt +0 -0
  28. data/bench/lib/testdata/1-data.txt +0 -0
  29. data/bench/lib/testdata/10-data.txt +15 -0
  30. data/bench/lib/testdata/2-data.txt +3 -0
  31. data/bench/lib/testdata/25-data.txt +39 -0
  32. data/bench/lib/testdata/250-data.txt +353 -0
  33. data/bench/lib/testdata/3-data.txt +4 -0
  34. data/bench/lib/testdata/5-data.txt +7 -8
  35. data/bench/lib/testdata/50-data.txt +70 -0
  36. data/bench/lib/testdata/500-data.txt +711 -0
  37. data/bench/prepare_bench +45 -0
  38. data/bench/run_bench.sh +3 -3
  39. data/bench/run_blob_script.sh +1 -1
  40. data/bench/run_cud_script.sh +1 -1
  41. data/bench/run_query_md_script.sh +1 -1
  42. data/bench/run_query_only_script.sh +1 -1
  43. data/bench/run_query_script.sh +1 -1
  44. data/bench/run_test_query_script.sh +30 -0
  45. data/bench/run_test_source_script.sh +21 -0
  46. data/bench/scripts/test_query_script.rb +64 -0
  47. data/bench/scripts/test_source_script.rb +27 -0
  48. data/bin/rhoconnect-benchmark +13 -0
  49. data/doc/client-objc.txt +236 -1
  50. data/doc/client.txt +132 -0
  51. data/doc/extending-rhoconnect-server.txt +79 -0
  52. data/doc/install.txt +1 -1
  53. data/doc/java-plugin.txt +291 -0
  54. data/doc/rest-api.txt +3 -1
  55. data/installer/unix-like/create_texts.rb +73 -16
  56. data/installer/unix-like/pre_install.sh +1 -1
  57. data/installer/unix-like/rho_connect_install_constants.rb +1 -1
  58. data/installer/utils/constants.rb +2 -2
  59. data/installer/utils/nix_install_test.rb +85 -75
  60. data/installer/utils/package_upload/repos.rake +82 -2
  61. data/installer/windows/rhosync.nsi +5 -5
  62. data/lib/rhoconnect/api/application/queue_updates.rb +14 -0
  63. data/lib/rhoconnect/api/source/set_db_doc.rb +3 -1
  64. data/lib/rhoconnect/server.rb +9 -17
  65. data/lib/rhoconnect/version.rb +1 -1
  66. data/rhoconnect.gemspec +1 -1
  67. data/spec/api/admin/get_api_token_spec.rb +6 -0
  68. data/spec/api/source/set_db_doc_spec.rb +13 -0
  69. data/spec/server/server_spec.rb +27 -1
  70. data/tasks/redis.rake +2 -2
  71. metadata +73 -48
  72. data/examples/simple/dump.rdb +0 -0
  73. data/installer/utils/package_upload/repos.rb +0 -83
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
5
+ require 'bench'
6
+
7
+ def create_subdir(dir)
8
+ begin
9
+ Dir.mkdir(dir)
10
+ rescue
11
+ end
12
+ end
13
+
14
+ # 1) Extract Bench title
15
+ title = ARGV[0]
16
+
17
+
18
+ # 2) create result directory structure
19
+ ['bench_results', ARGV[1]].each do |dir|
20
+ next if dir.nil?
21
+ create_subdir dir
22
+ Dir.chdir dir
23
+ end
24
+ result_dir = Dir.pwd
25
+
26
+ ['raw_data', 'images'].each do |dir|
27
+ create_subdir dir
28
+ end
29
+
30
+ # 3) create meta.yml file
31
+ meta_hash = {:x_keys => {}, :metrics => {}, :label => title}
32
+ x_keys = ARGV[2].split() if $ARGV[1]
33
+ x_keys ||= []
34
+ counter = 0
35
+ x_keys = x_keys.sort_by(&Bench.sort_natural_order)
36
+ x_keys.each do |x_key|
37
+ meta_hash[:x_keys][x_key] = counter
38
+ counter += 1
39
+ end
40
+
41
+ meta_hash[:metrics] = {'Throughput' => 0, 'Av.Time' => 1}
42
+
43
+ File.open(File.join(result_dir,'raw_data','meta.yml'), 'w') do |file|
44
+ file.write meta_hash.to_yaml unless meta_hash.empty?
45
+ end
data/bench/run_bench.sh CHANGED
@@ -20,8 +20,8 @@ else
20
20
  exit
21
21
  fi
22
22
 
23
- RHOCONNECT_HOME="$HOME/workspace/rhoconnect"
24
- cd $RHOCONNECT_HOME
23
+ cd '../'
24
+ RHOCONNECT_HOME=`pwd`
25
25
 
26
26
  echo "Pull rhoconnect code from remote repository ..." | tee -a /tmp/bench.log
27
27
  git reset --hard HEAD | tee -a /tmp/bench.log 2>&1
@@ -36,7 +36,7 @@ do
36
36
  ruby_version=$(rvm current)
37
37
  echo "Running benchmarks under $ruby_version ..." | tee -a /tmp/bench.log
38
38
  echo "" | tee -a /tmp/bench.log
39
- ruby bench/bench_runner.rb $RHOCONNECT_HOME 2>&1 | tee -a /tmp/bench.log
39
+ ruby ./bench/bench_runner.rb $RHOCONNECT_HOME 2>&1 | tee -a /tmp/bench.log
40
40
  done
41
41
 
42
42
 
@@ -1,3 +1,3 @@
1
1
  #! /bin/sh
2
2
 
3
- ruby bench start 'scripts/blob_cud_script.rb' 'rhoadmin' ''
3
+ ruby bench start 'scripts/blob_cud_script.rb' 'rhoadmin' '' $*
@@ -1,3 +1,3 @@
1
1
  #! /bin/sh
2
2
 
3
- ruby bench start 'scripts/cud_script.rb' 'rhoadmin' ''
3
+ ruby bench start 'scripts/cud_script.rb' 'rhoadmin' '' $*
@@ -1,3 +1,3 @@
1
1
  #! /bin/sh
2
2
 
3
- ruby bench start 'scripts/query_md_script.rb' 'rhoadmin' ''
3
+ ruby bench start 'scripts/query_md_script.rb' 'rhoadmin' '' $*
@@ -1,3 +1,3 @@
1
1
  #! /bin/sh
2
2
 
3
- ruby bench start 'scripts/query_only_script.rb' 'rhoadmin' ''
3
+ ruby bench start 'scripts/query_only_script.rb' 'rhoadmin' '' $*
@@ -1,3 +1,3 @@
1
1
  #! /bin/sh
2
2
 
3
- ruby bench start 'scripts/query_script.rb' 'rhoadmin' ''
3
+ ruby bench start 'scripts/query_script.rb' 'rhoadmin' '' $*
@@ -0,0 +1,30 @@
1
+ #! /bin/sh
2
+
3
+ timestamp_postfix=$(date +%Y_%m_%d_%H%M%S)
4
+ test_label=query_bench_$timestamp_postfix
5
+ if [ $# -gt 0 ] ; then
6
+ test_label=$1
7
+ fi
8
+ server='default'
9
+ if [ $# -gt 1 ] ; then
10
+ server=$2
11
+ fi
12
+
13
+ n_iterations=10
14
+
15
+ # setup the benchmark directory structure
16
+ ruby prepare_bench $test_label query_bench_$timestamp_postfix "1 2 3 5 10"
17
+
18
+ # simulate various number of simultaneous clients
19
+ for n_threads in 1 2 3 5 10
20
+ do
21
+ # simulate variaous number of data records
22
+ for payload in 1 5 10 50 100 250 500
23
+ do
24
+ result_filename=./bench_results/query_bench_$timestamp_postfix/raw_data/query_bench_result.$payload
25
+ ruby bench start 'scripts/test_query_script.rb' 'rhoadmin' '' $server $result_filename $n_threads $n_iterations $payload
26
+ done
27
+ done
28
+
29
+ # once benchmark is finished - process the results
30
+ ruby ./lib/bench/bench_result_processor.rb ./bench_results/query_bench_$timestamp_postfix/raw_data ./bench_results/query_bench_$timestamp_postfix/images
@@ -0,0 +1,21 @@
1
+ #! /bin/sh
2
+
3
+ timestamp_postfix=$(date +%Y_%m_%d_%H%M%S)
4
+ n_iterations=100
5
+ #server='http://rhohub-mzverev-c7ca8de3.rhosync.com/api/application'
6
+ server='default'
7
+
8
+ # setup the benchmark directory structure
9
+ ruby prepare_bench 'Rhoconnect SOURCE API benchmark' source_bench_$timestamp_postfix "1 2 5 10 15 20"
10
+
11
+ # simulate various number of simultaneous clients
12
+ for n_threads in 1 2 5 10 15 20
13
+ do
14
+ result_filename=./bench_results/source_bench_$timestamp_postfix/raw_data/source_bench_result.test
15
+ ruby bench start 'scripts/test_source_script.rb' 'rhoadmin' '' $server $result_filename $n_threads $n_iterations
16
+ done
17
+
18
+ # once benchmark is finished - process the results
19
+ ruby ./lib/bench/bench_result_processor.rb ./bench_results/source_bench_$timestamp_postfix/raw_data ./bench_results/source_bench_$timestamp_postfix/images
20
+
21
+
@@ -0,0 +1,64 @@
1
+ include BenchHelpers
2
+ bench_log "Runs simple login,clientcreate,clientregister,sync session and validates response"
3
+
4
+ Bench.config do |config|
5
+ config.concurrency ||= 10
6
+ config.iterations ||= 10
7
+ config.datasize ||= 100
8
+ config.main_marker = 'get-cud'
9
+ config.user_name = "benchuser"
10
+ config.password = "password"
11
+ config.get_test_server
12
+ @datasize = Bench.datasize
13
+ @expected = Bench.get_test_data(@datasize)
14
+ @all_objects = "[{\"version\":3},{\"token\":\"%s\"},{\"count\":%i},{\"progress_count\":0},{\"total_count\":%i},{\"insert\":""}]"
15
+ @ack_token = "[{\"version\":3},{\"token\":\"\"},{\"count\":0},{\"progress_count\":%i},{\"total_count\":%i},{}]"
16
+ @api_token = Bench.get_token
17
+ config.request_logging = false
18
+
19
+ # if this is not a distributed run - reset the app
20
+ if not Bench.sync_key
21
+ config.reset_app
22
+ config.set_server_state("test_db_storage:application:#{config.user_name}",@expected)
23
+ config.reset_refresh_time('MockAdapter')
24
+ end
25
+ end
26
+
27
+ Bench.synchronize do |config|
28
+ break unless Bench.sync_key
29
+ while true
30
+ cur_time = Time.now.to_f
31
+ sync_time = Bench.get_server_value(Bench.sync_key).to_f
32
+ if sync_time > 0.0 and cur_time >= sync_time
33
+ break
34
+ end
35
+ sleep(0.010)
36
+ end
37
+ puts " we have here #{cur_time}, #{sync_time}"
38
+ end
39
+
40
+ Bench.test do |config,session|
41
+ session.post "clientlogin", "#{config.base_url}/clientlogin", :content_type => :json do
42
+ {:login => config.user_name, :password => config.password}.to_json
43
+ end
44
+ session.get "clientcreate", "#{config.base_url}/clientcreate"
45
+ client_id = JSON.parse(session.last_result.body)['client']['client_id']
46
+ session.client_id = client_id
47
+ session.post "clientregister", "#{config.base_url}/clientregister", :content_type => :json do
48
+ {:device_type => "Apple", :device_pin => 'somepin123', :device_port => "device_port_111", :phone_id => 'unique_phone_id',
49
+ :client_id => client_id}.to_json
50
+ end
51
+
52
+ session.get "get-cud", "#{config.base_url}/query" do
53
+ {'source_name' => 'MockAdapter', 'client_id' => session.client_id, 'p_size' => @datasize}
54
+ end
55
+ token = JSON.parse(session.last_result.body)[1]['token']
56
+ session.get "ack-cud", "#{config.base_url}/query" do
57
+ { 'source_name' => 'MockAdapter',
58
+ 'client_id' => session.client_id,
59
+ 'token' => token}
60
+ end
61
+ session.last_result.verify_code(200)
62
+ session.last_result.verify_body([{:version => 3},{:token => ''},{:count => 0},
63
+ {:progress_count => @datasize},{:total_count => @datasize},{}].to_json)
64
+ end
@@ -0,0 +1,27 @@
1
+ include BenchHelpers
2
+ bench_log "Runs admin source methods in batch"
3
+
4
+ Bench.config do |config|
5
+ config.concurrency ||= 10
6
+ config.iterations ||= 100
7
+ config.main_marker = 'get_adapter'
8
+ config.user_name = "benchuser"
9
+ config.password = "password"
10
+ config.get_test_server
11
+ config.reset_app
12
+
13
+ @token = config.get_token
14
+ @save_adapter_url = "my/dynamic/adapter/url"
15
+ config.reset_refresh_time('MockAdapter')
16
+ config.request_logging = false
17
+ end
18
+
19
+
20
+ Bench.test do |config,session|
21
+ session.post "save_adapter", "#{config.host}/api/source/save_adapter", :content_type => :json do
22
+ {:api_token => @token, :attributes => {:adapter_url => @save_adapter_url}}.to_json
23
+ end
24
+ session.post "get_adapter", "#{config.host}/api/source/get_adapter", :content_type => :json do
25
+ {:api_token => @token}.to_json
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $:.unshift File.join(File.dirname(__FILE__),'..','bench','lib')
5
+ $:.unshift File.join(File.dirname(__FILE__),'..','bench')
6
+ require 'bench'
7
+
8
+ $ARGV.unshift ''
9
+ $ARGV.unshift 'rhoadmin'
10
+ $ARGV.unshift 'scripts/test_comm_script.rb'
11
+ $ARGV.unshift 'start'
12
+
13
+ Bench::Cli.start
data/doc/client-objc.txt CHANGED
@@ -73,6 +73,8 @@ Do the following steps to add schema files to your Copy Bundle Resources build p
73
73
  CREATE TABLE product (
74
74
  "client_id" VARCHAR(255) default NULL);
75
75
 
76
+ ** NOTE: Do not use NOT NULL statement in column definitions. RhoConnect client delete object attributes by setting value to null, when all object attributes become nulls, object deleted. So objects with NOT NULL columns will not be deleted. **
77
+
76
78
  ## Coding a RhoConnect Client
77
79
 
78
80
  This section discusses the general steps you perform to code a RhoConnect client in Objective C, using the store example. It is not a tutorial for coding a complete Xcode application, but instead discusses the RhoConnect code that you need to create.
@@ -180,6 +182,7 @@ Here is the Store code for the login process, contained in LoginViewController.m
180
182
  [ [RhoConnectEngine sharedInstance].syncClient loginWithUser:txtLogin.text pwd:txtPassword.text callback:@selector(loginComplete:) target:self];
181
183
  }
182
184
 
185
+
183
186
  ### Synchronize Data
184
187
 
185
188
  Call the RhoConnectClient setNotification method to call a callback method that notifies about the state of the synchronization.
@@ -210,6 +213,31 @@ Here is the Store code for the sync process, contained in WaitLoginController.m.
210
213
  [ [RhoConnectEngine sharedInstance].syncClient clearNotification];
211
214
  }else if ([notify.status compare:@"error"] == 0)
212
215
  {
216
+ if([notify.error_message caseInsensitiveCompare:@"unknown client"] == 0) {
217
+ [[RhoConnectEngine sharedInstance].syncClient database_client_reset];
218
+ [[RhoConnectEngine sharedInstance].syncClient setNotification: @selector(syncAllCalback:) target:self];
219
+ [[RhoConnectEngine sharedInstance].syncClient syncAll];
220
+ } else if( err_code == RHO_ERR_CLIENTISNOTLOGGEDIN
221
+ || err_code == RHO_ERR_UNATHORIZED) {
222
+
223
+ NSLog(@"GO TO LOGIN PAGE!");
224
+ // real code to trigger view transition goes here..
225
+ }else
226
+ {
227
+ //This is mandatory:
228
+ if([notify hasCreateErrors]) {
229
+ [[RhoConnectEngine sharedInstance].syncClient onCreateError: notify @"delete"];
230
+ }
231
+ //These are optional:
232
+ /*
233
+ if([notify hasUpdateErrors]) {
234
+ [[RhoConnectEngine sharedInstance].syncClient onUpdateError: notify @"rollback"];
235
+ }
236
+ if([notify hasDeleteErrors]) {
237
+ [[RhoConnectEngine sharedInstance].syncClient onDeleteError: notify @"retry"];
238
+ }
239
+ */
240
+ }
213
241
  }
214
242
  }
215
243
 
@@ -227,6 +255,114 @@ Here is the Store code for the sync process, contained in WaitLoginController.m.
227
255
  }
228
256
  }
229
257
 
258
+ ### Processing unknown-client error
259
+ Unknown client error return by server after resetting server database, removing particular client id from database or any other cases when server cannot find client id(sync server unique id of device).
260
+ Note that login session may still exist on server, so in this case client does not have to login again, just create new client id.
261
+ Processing of this error contain 2 steps:
262
+
263
+ * When unknown client error is come from server, client should call database_client_reset and start new sync, to register new client id.
264
+
265
+ * If login session also deleted or expired on the server, then customer has to login again.
266
+
267
+ Example:
268
+ :::cplusplus
269
+
270
+ - (void)syncAllComplete:(RhoConnectNotify*) notify
271
+ {
272
+ NSString* status = notify.status;
273
+ NSString* error = notify.error_message;
274
+ int err_code = notify.error_code;
275
+
276
+ NSLog(@"syncAll DONE, status: '%s' , error_msg: '%s' , error_code: %d",
277
+ [status cStringUsingEncoding: NSUTF8StringEncoding],
278
+ [error cStringUsingEncoding: NSUTF8StringEncoding],
279
+ err_code
280
+ );
281
+
282
+ if ( [notify.status compare:@"in_progress"] == 0) {
283
+
284
+ } else if ([notify.status compare:@"complete"] == 0) {
285
+
286
+ [[RhoConnectEngine sharedInstance].syncClient clearNotification];
287
+
288
+ } else if ([notify.status compare:@"error"] == 0) {
289
+
290
+ if([notify.error_message caseInsensitiveCompare:@"unknown client"] == 0) {
291
+ [[RhoConnectEngine sharedInstance].syncClient database_client_reset];
292
+ [[RhoConnectEngine sharedInstance].syncClient setNotification: @selector(syncAllCalback:) target:self];
293
+ [[RhoConnectEngine sharedInstance].syncClient syncAll];
294
+ } else if( err_code == RHO_ERR_CLIENTISNOTLOGGEDIN
295
+ || err_code == RHO_ERR_UNATHORIZED) {
296
+
297
+ NSLog(@"GO TO LOGIN PAGE!");
298
+ // real code to trigger view transition goes here..
299
+ }
300
+ }
301
+ }
302
+
303
+ ### Processing server errors
304
+ * create-error:
305
+ has to be handled in sync callback. Otherwise sync will stop on this model. To fix create errors you should call Model.on_sync_create_error or SyncEngine.on_sync_create_error:
306
+
307
+ * update-error:
308
+ If not handled, local modifications, which were failing on server, will never sync to server again.
309
+ So sync will work fine, but nobody will know about these changes.
310
+
311
+ * delete-error:
312
+ If not handled, local modifications, which were failing on server, will never sync to server again.
313
+ So sync will work fine, but nobody will know about these changes.
314
+
315
+ Example:
316
+ :::cplusplus
317
+
318
+ - (void)syncAllCalback:(RhoConnectNotify*) notify
319
+ {
320
+ NSString* status = notify.status;
321
+ NSString* error = notify.error_message;
322
+ int err_code = notify.error_code;
323
+
324
+ NSLog(@"syncAll DONE, status: '%s' , error_msg: '%s' , error_code: %d",
325
+ [status cStringUsingEncoding: NSUTF8StringEncoding],
326
+ [error cStringUsingEncoding: NSUTF8StringEncoding],
327
+ err_code
328
+ );
329
+
330
+ if ( [notify.status compare:@"in_progress"] == 0) {
331
+
332
+ } else if ([notify.status compare:@"complete"] == 0) {
333
+
334
+ [[RhoConnectEngine sharedInstance].syncClient clearNotification];
335
+
336
+ } else if ([notify.status compare:@"error"] == 0) {
337
+
338
+ if([notify isUnknownClientError]) {
339
+ [[RhoConnectEngine sharedInstance].syncClient database_client_reset];
340
+ [[RhoConnectEngine sharedInstance].syncClient setNotification: @selector(syncAllCalback:) target:self];
341
+ [[RhoConnectEngine sharedInstance].syncClient syncAll];
342
+ } else if( err_code == RHO_ERR_CLIENTISNOTLOGGEDIN
343
+ || err_code == RHO_ERR_UNATHORIZED) {
344
+
345
+ NSLog(@"GO TO LOGIN PAGE!");
346
+ // real code to trigger view transition goes here..
347
+ }else
348
+ {
349
+ //This is mandatory:
350
+ if([notify hasCreateErrors]) {
351
+ [[RhoConnectEngine sharedInstance].syncClient onCreateError: notify action:@"delete"];
352
+ }
353
+ //These are optional:
354
+ /*
355
+ if([notify hasUpdateErrors]) {
356
+ [[RhoConnectEngine sharedInstance].syncClient onUpdateError: notify action:@"rollback"];
357
+ }
358
+ if([notify hasDeleteErrors]) {
359
+ [[RhoConnectEngine sharedInstance].syncClient onDeleteError: notify action:@"retry"];
360
+ }
361
+ */
362
+ }
363
+ }
364
+ }
365
+
230
366
  ### Search the Client Database for Models
231
367
 
232
368
  Call a RhomModel find method (find, find\_first, find\_all) to find model objects in the client database. This code sample for the find\_all method fetches all the models in the database; you can also search for models containing certain attribute data.
@@ -271,6 +407,9 @@ The RhoConnectClient class contains the following properties and methods to buil
271
407
  * <a href="#setObjectNotification">setObjectNotification</a>
272
408
  * <a href="#clearObjectNotification">clearObjectNotification</a>
273
409
  * <a href="#addObjectNotify">addObjectNotify</a>
410
+ * <a href="#onCreateError">onCreateError</a>
411
+ * <a href="#onUpdateError">onUpdateError</a>
412
+ * <a href="#onDeleteError">onDeleteError</a>
274
413
 
275
414
  ### <a id="threaded_mode"></a>threaded\_mode property
276
415
 
@@ -477,7 +616,86 @@ Sample call:
477
616
  RhoConnectClient* sclient;
478
617
  ...
479
618
  [sclient addObjectNotify: [[item objectForKey:@"source_id"] intValue] szObject:[item valueForKey:@"object"] ];
480
-
619
+
620
+ ### <a id="onCreateError"></a>onCreateError
621
+
622
+ Instance method. To process 'create-error' errors from server.
623
+
624
+ :::cplusplus
625
+ - (void) onCreateError: (RhoConnectNotify*)notify action: (NSString*)action;
626
+
627
+ <table border="1">
628
+ <tr>
629
+ <td><code>notify</code></td>
630
+ <td>RhoConnectNotify. Notification object passed to notification callback.</td>
631
+ </tr>
632
+ <tr>
633
+ <td><code>action</code></td>
634
+ <td>NSString. May be "delete" or "recreate": "delete" just remove object from client, "recreate" will push this object to server again at next sync.</td>
635
+ </tr>
636
+ </table>
637
+
638
+ Sample call:
639
+
640
+ :::cplusplus
641
+ RhoConnectClient* sclient;
642
+ ...
643
+ if ([notify hasCreateErrors])
644
+ [sclient onCreateError: notify action:@"delete" ];
645
+
646
+
647
+ ### <a id="onUpdateError"></a>onUpdateError
648
+
649
+ Instance method. To process 'update-error' errors from server.
650
+
651
+ :::cplusplus
652
+ - (void) onUpdateError: (RhoConnectNotify*)notify action: (NSString*)action;
653
+
654
+ <table border="1">
655
+ <tr>
656
+ <td><code>notify</code></td>
657
+ <td>RhoConnectNotify. Notification object passed to notification callback.</td>
658
+ </tr>
659
+ <tr>
660
+ <td><code>action</code></td>
661
+ <td>NSString. May be "retry" or "rollback": "retry" will push update object operation to server again at next sync, "rollback" will write rollback objects(comes from server) to client database.</td>
662
+ </tr>
663
+ </table>
664
+
665
+ Sample call:
666
+
667
+ :::cplusplus
668
+ RhoConnectClient* sclient;
669
+ ...
670
+ if ([notify hasUpdateErrors])
671
+ [sclient onUpdateError: notify action:@"retry" ];
672
+
673
+ ### <a id="onDeleteError"></a>onUpdateError
674
+
675
+ Instance method. To process 'delete-error' errors from server.
676
+
677
+ :::cplusplus
678
+ - (void) onDeleteError: (RhoConnectNotify*)notify action: (NSString*)action;
679
+
680
+ <table border="1">
681
+ <tr>
682
+ <td><code>notify</code></td>
683
+ <td>RhoConnectNotify. Notification object passed to notification callback.</td>
684
+ </tr>
685
+ <tr>
686
+ <td><code>action</code></td>
687
+ <td>NSString. May be "retry" - will push delete object operation to server again at next sync.</td>
688
+ </tr>
689
+ </table>
690
+
691
+ Sample call:
692
+
693
+ :::cplusplus
694
+ RhoConnectClient* sclient;
695
+ ...
696
+ if ([notify hasDeleteErrors])
697
+ [sclient onDeleteError: notify action:@"retry" ];
698
+
481
699
  ## RhomModel Class API
482
700
 
483
701
  The RhomModel class contains the following properties and methods for setting and using RhomModel objects; RhomModel objects are RhoConnect models and their attributes.
@@ -751,6 +969,23 @@ Several RhoConnectClient methods return a RhoConnectNotify object. The propertie
751
969
  <td><code>callback_params</code></td>
752
970
  <td>@property(assign) NSString*. Callback parameters.</td>
753
971
  </tr>
972
+ <tr>
973
+ <td><code>hasCreateErrors</code></td>
974
+ <td>(Boolean). Return true if server return create-errors.</td>
975
+ </tr>
976
+ <tr>
977
+ <td><code>hasUpdateErrors</code></td>
978
+ <td>(Boolean). Return true if server return update-errors.</td>
979
+ </tr>
980
+ <tr>
981
+ <td><code>hasDeleteErrors</code></td>
982
+ <td>(Boolean). Return true if server return delete-errors.</td>
983
+ </tr>
984
+ <tr>
985
+ <td><code>isUnknownClientError</code></td>
986
+ <td>(Boolean). Return true if server return unknown client error.</td>
987
+ </tr>
988
+
754
989
  </table>
755
990
 
756
991
  ## RhoConnectObjectNotify Class API