goat 0.3.45 → 0.3.46

Sign up to get free protection for your applications and to get access to all the features.
@@ -39,7 +39,7 @@ class Object
39
39
  while c
40
40
  cs << c
41
41
  return cs if c == upto
42
- raise "#{upto} isn't in the hierachy of #{start.inspect}" if c == Object && upto != Object
42
+ raise "#{start.inspect} isn't in the hierachy of #{upto}" if c == Object && upto != Object
43
43
 
44
44
  c = c.superclass
45
45
  end
@@ -53,34 +53,40 @@ var Goat = {}
53
53
  Goat.RT = {
54
54
  version: 0,
55
55
  pendingTxns: {},
56
+ completedTxns: {},
56
57
  ops: {},
57
58
  consecutiveFailures: 0
58
59
  };
59
60
 
60
61
  (function(rt) {
61
62
  function updateFailed(msg) {
62
- console.error(msg);
63
+ Goat.error('updateFailed', msg);
63
64
  }
64
65
 
65
66
  function node(par, pos) {
66
- if(pos === null)
67
+ Goat.debug('node', par, pos, pos === null)
68
+ if(pos === null || pos === undefined)
67
69
  return $('#' + par);
68
70
  else
69
71
  return $("#" + par + " > :nth-child(" + (pos + 1) + ")");
70
72
  }
71
73
 
72
74
  function removeNode(par, pos, html) {
75
+ Goat.debug('removeNode', node(par, pos));
73
76
  node(par, pos).remove();
74
77
  }
75
78
 
76
79
  function addNode(par, pos, html) {
77
- node(par, pos).before(html);
80
+ Goat.debug('addNode', node(par, pos), html);
81
+ if(pos == 0)
82
+ node(par).prepend(html)
83
+ else
84
+ node(par, pos - 1).after(html);
78
85
  }
79
86
 
80
87
  function replaceNode(par, pos, html) {
81
- console.log("rep " + par + " " + pos);
82
- console.log(node(par, pos));
83
- node(par, pos).html(html);
88
+ Goat.warn('Replace fault', node(par, pos));
89
+ node(par, pos).replaceWith(html);
84
90
  if(pos === null)
85
91
  delete Goat.components[par];
86
92
  }
@@ -92,9 +98,10 @@ Goat.RT = {
92
98
  var html = up.html;
93
99
  var js = up.js;
94
100
  var css = up.css;
101
+ var id = up.id; // only for rem
95
102
 
96
103
  if(t == "rem") {
97
- removeNode(par, pos, html);
104
+ removeNode(par, pos, id);
98
105
  } else if(t == "add") {
99
106
  addNode(par, pos, html);
100
107
  } else if(t == "rep") {
@@ -121,8 +128,6 @@ Goat.RT = {
121
128
  script.innerHTML = js;
122
129
  addToHead(script);
123
130
  Goat.loadComponents();
124
- /*console.log("Eval " + js);
125
- eval(js);*/
126
131
  }
127
132
 
128
133
  function updateReceived(m) {
@@ -164,31 +169,30 @@ Goat.RT = {
164
169
  return pending;
165
170
  }
166
171
 
167
- function completeTxn(txn) {
168
- if(rt.pendingTxns[txn]) {
169
- delete rt.pendingTxns[txn];
172
+ function completeTxn(txnid) {
173
+ if(rt.pendingTxns[txnid]) {
174
+ var txn = rt.ops[txnid];
175
+
176
+ delete rt.pendingTxns[txnid];
177
+ rt.completedTxns[txnid] = true
170
178
 
171
179
  if(pendingCount() == 0) {
172
180
  Goat.LoadingIndicator.hide();
173
181
  }
174
182
 
175
183
  if(txn.complete) txn.complete();
184
+ } else if(rt.completedTxns[txnid]) {
185
+ Goat.warn('Duplicate txn_complete message for txn ' + txnid);
176
186
  } else {
177
- console.error("Can't complete txn " + txn + ": no such txn was started");
187
+ Goat.error('Can\'t complete txn ' + txn + ': no such txn was started');
178
188
  }
179
189
  }
180
190
 
181
- function txnCompleteMsg(msg) {
182
- console.log('txnCompleteMsg');
183
- console.log(msg);
184
- completeTxn(msg['txn']);
185
- }
186
-
187
191
  rt.updateReceived = updateReceived;
188
192
  rt.node = node;
189
193
  rt.newTxn = newTxn;
190
194
  rt.beginTxn = beginTxn;
191
- rt.txnCompleteMsg = txnCompleteMsg;
195
+ rt.completeTxn = completeTxn;
192
196
 
193
197
  return rt;
194
198
  })(Goat.RT);
@@ -203,7 +207,7 @@ Goat.LoadingIndicator = {
203
207
  (function(ld) {
204
208
  function initIndicator() {
205
209
  if(!ld.url || !ld.where) {
206
- console.error("Loading indicator not configured");
210
+ Goat.error("Loading indicator not configured");
207
211
  return;
208
212
  }
209
213
  ld.indicator = $("<img id=\"loading_indicator\" src=\"" + ld.url + "\">");
@@ -278,7 +282,7 @@ Goat.RPC = Class.extend({
278
282
  },
279
283
 
280
284
  rpcFailure: function() {
281
- console.error("RPC error: couldn't load " + this.name);
285
+ Goat.error("RPC error: couldn't load " + this.name);
282
286
  alert("An error was encountered connecting to the server. This could be because of a problem with your internet connection, or with the server itself. You should try refreshing this page.");
283
287
  if(this.rpcError) this.rpcError(this);
284
288
  },
@@ -351,14 +355,28 @@ $.extend(Goat, {
351
355
  activeChannel: null,
352
356
  pageDead: false,
353
357
  components: {},
354
- page_id: null
358
+ page_id: null,
355
359
  });
356
360
 
357
361
  $.extend(Goat, {
362
+ isLocal: function() {
363
+ return window.location.host.match(/127\.0\.0\.1/) || window.location.host.match(/localhost/)
364
+ },
365
+
366
+ console_apply: function(f, a) {
367
+ if(this.isLocal() || f == 'warn' || f == 'error')
368
+ console[f].apply(console, a);
369
+ },
370
+
371
+ debug: function() { this.console_apply('debug', arguments); },
372
+ warn: function() { this.console_apply('warn', arguments); },
373
+ error: function() { this.console_apply('error', arguments); },
374
+ log: function() { this.console_apply('log', arguments); },
375
+
358
376
  closure: function(fn) { return closure(this, fn); },
359
377
 
360
378
  channelURL: function() {
361
- if(window.location.host.match(/127\.0\.0\.1/) || window.location.host.match(/localhost/))
379
+ if(this.isLocal())
362
380
  return 'http://127.0.0.1:8050/channel';
363
381
  else
364
382
  return '/channel';
@@ -378,34 +396,34 @@ $.extend(Goat, {
378
396
  },
379
397
 
380
398
  messageReceived: function(m) {
381
- console.log(m);
399
+ Goat.log('messageReceived', m.type, m);
382
400
 
383
401
  var t = m['type'];
384
402
  if(t == 'component_updated') {
385
403
  Goat.RT.updateReceived(m);
386
404
  } else if(t == 'redirect') {
387
405
  if(m["location"]) {
388
- console.log("Redirecting to " + m['location']);
406
+ Goat.log('Redirecting to ' + m['location']);
389
407
  sleep(2);
390
408
  window.location = m['location'];
391
409
  } else {
392
- console.error("Tried to redirect to null. Buggy message: " + m);
410
+ Goat.error('Tried to redirect to null. Buggy message: ', m);
393
411
  }
394
412
  } else if(t == 'page_expired') {
395
413
  alert("Due to inactivity, you'll need to refresh this page.");
396
414
  Goat.setPageDead();
397
415
  } else if(t == 'txn_complete') {
398
- Goat.RT.txnCompleteMsg(m);
416
+ Goat.RT.completeTxn(m['txn']);
399
417
  } else if(t == 'alert') {
400
418
  this.showAlert(m);
401
419
  } else {
402
- console.log("Unknown message type: " + m);
420
+ Goat.error('Unknown message type: ', m);
403
421
  }
404
422
  },
405
423
 
406
424
  channelDataReceived: function(data) {
407
425
  if(!data) {
408
- console.log("Null data received in channelDataReceived")
426
+ Goat.error('Null data received in channelDataReceived')
409
427
  this.reopenChannel();
410
428
  return;
411
429
  }
@@ -415,12 +433,11 @@ $.extend(Goat, {
415
433
  if(data['messages']) {
416
434
  $(data['messages']).each(this.closure(function(i, m) { this.messageReceived(m) }));
417
435
  } else {
418
- console.error("Bad channel data: " + data)
436
+ Goat.error('Bad channel data: ', data)
419
437
  }
420
438
  },
421
439
 
422
440
  reopenChannel: function() {
423
- console.log("reopenChannel()");
424
441
  var fails = this.channelOpenFails;
425
442
  setTimeout(function() { Goat.openChannel(); }, Math.min(30000, Math.max(1000, Math.pow(1.5, fails))));
426
443
  this.channelOpenFails++;
@@ -428,14 +445,14 @@ $.extend(Goat, {
428
445
 
429
446
  openChannel: function() {
430
447
  if(Goat.pageDead) {
431
- console.log("not opening channel: page dead");
448
+ Goat.log("not opening channel: page dead");
432
449
  return;
433
450
  }
434
451
 
435
- console.log("opening channel");
452
+ Goat.debug("opening channel");
436
453
 
437
454
  if(this.activeChannel) {
438
- console.error("can't open channel: channel already open");
455
+ Goat.error('can\'t open channel: channel already open');
439
456
  return;
440
457
  }
441
458
 
@@ -456,7 +473,7 @@ $.extend(Goat, {
456
473
  function injectScriptTag(tag) {
457
474
 
458
475
  var logError = closure(this, function() {
459
- console.error("Goat: error loading " + src);
476
+ Goat.error('Goat: error loading ', src);
460
477
  });
461
478
 
462
479
  var base = Goat.channelURL();
@@ -487,8 +504,7 @@ $.extend(Goat, {
487
504
  cache: false,
488
505
  success: function(data) {},
489
506
  error: function(req, stat, err) {
490
- console.log("submit form error");
491
- console.log({req: req, status: stat, err: err});
507
+ Goat.error('submit form error', req, stat, err);
492
508
  }
493
509
  });
494
510
 
@@ -499,6 +515,12 @@ $.extend(Goat, {
499
515
  this.components[id] = obj;
500
516
  },
501
517
 
518
+ init: function() {
519
+ if(Goat.isLocal())
520
+ Goat.log('Running in debug mode');
521
+ this.loadComponents();
522
+ },
523
+
502
524
  loadComponents: function() {
503
525
  $.each(Goat.components, function(_, c) {
504
526
  if(!c.isLoaded())
@@ -508,7 +530,7 @@ $.extend(Goat, {
508
530
  });
509
531
 
510
532
  $(document).ready(function() {
511
- Goat.loadComponents();
533
+ Goat.init();
512
534
  setTimeout(function() { if(Goat.page_id) Goat.openChannel(); }, 1000);
513
535
  });
514
536
 
@@ -22,6 +22,14 @@ module Goat
22
22
  send_message('register_page', 'pgid' => pgid, 'user' => user, 'components' => cs.map(&:to_hash))
23
23
  end
24
24
 
25
+ def self.update_page(pgid, txn, updates)
26
+ send_message('update_page',
27
+ 'pgid' => pgid,
28
+ 'txn' => txn,
29
+ 'updates' => updates.map(&:to_hash)
30
+ )
31
+ end
32
+
25
33
  def self.live_components(cls, spec)
26
34
  resp = send_message('live_components', {'class' => cls, 'spec' => spec}, true)
27
35
  JSON.load(resp)['response'].map{|h| ComponentSkeleton.from_hash(h)}
@@ -36,17 +44,23 @@ module Goat
36
44
  components_updated(txn, pgid, [update])
37
45
  end
38
46
 
47
+ def self.components_update_completed(cs)
48
+ puts "Ack for components_updated: #{cs.inspect}"
49
+ end
50
+
39
51
  def self.components_updated(txn, pgid, updates)
40
- send_message('components_updated',
52
+ send_message('components_updated', {
41
53
  'txn' => txn,
42
54
  'pgid' => pgid,
43
- 'updates' => updates.map(&:to_hash)
55
+ 'updates' => updates.map(&:to_hash)},
56
+ true
44
57
  )
45
58
  end
46
59
  end
47
60
 
48
61
  class NoStateSrvConnectionError < RuntimeError; end
49
62
 
63
+ # this is an ugly mix of sync and async stuff
50
64
  class StateSrvConnection < EM::Connection
51
65
  include EM::P::LineText2
52
66
 
@@ -66,13 +80,29 @@ module Goat
66
80
  self.connect(@host, @port)
67
81
  end
68
82
 
69
- def self.send_message_sync(msg)
70
- s = TCPSocket.open(@host, @port)
71
- s.write(msg.to_json + "\n")
72
- resp = s.readline
83
+ def self.reconnect_sync
84
+ @sock = TCPSocket.open(@host, @port)
85
+ end
86
+
87
+ def self.sync_connection_active?
88
+ @sock && !@sock.closed?
89
+ end
90
+
91
+ def self.send_message_sync(msg, failed_last_time=false)
92
+ reconnect_sync unless sync_connection_active?
93
+ @sock.write(msg.to_json + "\n")
94
+ resp = @sock.readline
73
95
  Goat.logd("=> #{resp.inspect}") if $verbose
74
- s.close
75
96
  resp
97
+ rescue Errno::ECONNRESET, EOFError => e
98
+ # almost certainly connection was closed and we didn't notice
99
+ if failed_last_time
100
+ raise e
101
+ else
102
+ Goat.logw "Reinitializing sync connection to state-srv (#{e.inspect})"
103
+ reconnect_sync
104
+ send_message_sync(msg, true)
105
+ end
76
106
  end
77
107
 
78
108
  def self.send_message(*args)
@@ -103,6 +133,16 @@ module Goat
103
133
  end
104
134
  end
105
135
 
136
+ def message_received(msg)
137
+ msg = msg['response']
138
+
139
+ if msg['type'] = 'update_ack'
140
+ StateSrvClient.components_update_completed(msg['components'])
141
+ else
142
+ raise "Unknown message type: #{msg['type']}"
143
+ end
144
+ end
145
+
106
146
  def send_message(t, msg, sync=false)
107
147
  msg = msg.merge('type' => t)
108
148
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goat
3
3
  version: !ruby/object:Gem::Version
4
- hash: 73
4
+ hash: 79
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 45
10
- version: 0.3.45
9
+ - 46
10
+ version: 0.3.46
11
11
  platform: ruby
12
12
  authors:
13
13
  - Patrick Collison
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-01 00:00:00 +00:00
18
+ date: 2011-03-09 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -151,7 +151,7 @@ files:
151
151
  - lib/goat/dynamic.rb
152
152
  - lib/goat/extn.rb
153
153
  - lib/goat/goat.js
154
- - lib/goat/html.rb
154
+ - lib/goat/dom.rb
155
155
  - lib/goat/js/component.js
156
156
  - lib/goat/mongo.rb
157
157
  - lib/goat/net-common.rb
@@ -1,301 +0,0 @@
1
- require 'rack/utils'
2
-
3
- module Goat
4
- module DOMTools
5
- class Traverser
6
- def initialize(tree, dlg, transpose)
7
- @tree = tree
8
- @dlg = dlg
9
- @transpose = transpose
10
- end
11
-
12
- def dom_node?(node)
13
- node.is_a?(Array) && node.first.is_a?(Symbol)
14
- end
15
-
16
- def tag(node); node[0]; end
17
- def attrs(node); node[1] if node[1].is_a?(Hash); end
18
- def body(node); node[1].is_a?(Hash) ? node[2..-1] : node[1..-1]; end
19
- def domid(node); attrs(node) ? attrs(node)[:id] : nil; end
20
-
21
- def to_node(tag, attrs, body)
22
- tag = [tag]
23
- tag << attrs if attrs
24
- tag << body
25
- end
26
-
27
- def replacement_block
28
- @rep = nil
29
- @replacement_block ||= lambda {|new| @rep = new}
30
- end
31
-
32
- def traverse(node)
33
- if node.is_a?(String)
34
- @dlg.string(node, &replacement_block)
35
- @rep || node
36
- elsif node == nil
37
- @dlg.string('', &replacement_block)
38
- @rep || nil
39
- elsif dom_node?(node)
40
- @dlg.node(node, &replacement_block)
41
- rep = @rep || node
42
-
43
- if @transpose
44
- if rep != node
45
- rep
46
- else
47
- to_node(tag(node), attrs(node), traverse(body(node)))
48
- end
49
- else
50
- traverse(body(node))
51
- end
52
- elsif node.is_a?(Array)
53
- if node.size == 1
54
- traverse(node.first)
55
- else
56
- if @transpose
57
- node.map{|x| traverse(x)}
58
- else
59
- node.each{|x| traverse(x)}
60
- end
61
- end
62
- elsif node.kind_of?(Component)
63
- @dlg.component(node, &replacement_block)
64
- @rep || node
65
- else
66
- raise "Unknown object in the dom: #{node.inspect}"
67
- end
68
- end
69
-
70
- def traverse!
71
- traverse(@tree)
72
- end
73
-
74
- class BlockTraverser
75
- # would be infinitely nicer if you could just yield from a block
76
- def initialize(blk, transpose)
77
- @blk = blk
78
- @transpose = transpose
79
- end
80
-
81
- def node(node, &blk)
82
- if @transpose
83
- @blk.call(node, blk)
84
- else
85
- @blk.call(node)
86
- end
87
- end
88
-
89
- def string(str, &blk)
90
- if @transpose
91
- @blk.call(str, blk)
92
- else
93
- @blk.call(str)
94
- end
95
- end
96
-
97
- def component(c, &blk)
98
- if @transpose
99
- @blk.call(c, blk)
100
- else
101
- @blk.call(c)
102
- end
103
- end
104
- end
105
-
106
- def self.traverse(tree, dlg=nil, transpose=false, &blk)
107
- d = nil
108
-
109
- if dlg
110
- d = dlg
111
- elsif blk
112
- d = BlockTraverser.new(blk, transpose)
113
- else
114
- raise "Need a delegate"
115
- end
116
-
117
- self.new(tree, d, transpose).traverse!
118
- end
119
-
120
- def self.transpose(tree, dlg=nil, &blk)
121
- traverse(tree, dlg, true, &blk)
122
- end
123
- end
124
-
125
- def self.traverse(tree, dlg=nil, &blk); Traverser.traverse(tree, dlg, &blk); end
126
- def self.transpose(tree, dlg=nil, &blk); Traverser.transpose(tree, dlg, &blk); end
127
-
128
- def self.expanded_dom(dom)
129
- DOMTools.transpose(dom) do |elt, update|
130
- if elt.kind_of?(Component)
131
- raise "Component #{elt} has no ID: was super's initialize called?" unless elt.id
132
- Dynamic[:expander].component_used(elt)
133
- update.call(elt.component(elt.expanded_dom))
134
- end
135
- end
136
- end
137
-
138
- def self.inject_prefixes(id, dom)
139
- DOMTools.traverse(dom) do |elt|
140
- if elt.kind_of?(Array) && elt.first.is_a?(Symbol) && elt[1].is_a?(Hash)
141
- attrs = elt[1]
142
- elt[1] = attrs.map_to_hash do |k, v|
143
- if v.kind_of?(String)
144
- [k, v.prefix_ns(id)]
145
- elsif v.kind_of?(Array) && HTMLBuilder::ARRAY_ATTRS.include?(k)
146
- [k, v]
147
- elsif v.kind_of?(Integer) && HTMLBuilder::INTEGER_ATTRS.include?(k)
148
- [k, v]
149
- else
150
- raise "Invalid object #{v.inspect} to get a prefix in dom:\n#{dom.inspect}"
151
- end
152
- end
153
- end
154
- end
155
- dom
156
- end
157
-
158
- class ::String
159
- def to_html(builder)
160
- Rack::Utils.escape_html(self)
161
- end
162
- end
163
-
164
- class ::NilClass
165
- def to_html(builder)
166
- ''
167
- end
168
- end
169
-
170
- class ::Goat::HTMLString < String
171
- def to_html(builder)
172
- self
173
- end
174
- end
175
-
176
- class ::Array
177
- def to_html(builder)
178
- builder.array_to_html(self)
179
- end
180
- end
181
-
182
- class InvalidBodyError < RuntimeError
183
- attr_reader :body
184
-
185
- def initialize(body)
186
- super("Invalid body: #{body.inspect}")
187
- @body = body
188
- end
189
- end
190
-
191
- class HTMLBuilder
192
- ARRAY_ATTRS = [:class]
193
- INTEGER_ATTRS = [:colspan, :rowspan]
194
-
195
- class TagBuilder
196
- # TODO: gmail trick of only a single onclick() handler
197
-
198
- def self.build(tag, attrs, body)
199
- self.new(tag, attrs, body).dispatch
200
- end
201
-
202
- def initialize(tag, attrs, body)
203
- @tag = tag
204
- @attrs = attrs
205
- @body = body
206
-
207
- rewrite_attrs
208
- end
209
-
210
- def rewrite_attrs
211
- new = {}
212
-
213
- @attrs.map_to_hash do |k, v|
214
- if k == :class && v.kind_of?(Array)
215
- new[:class] = @attrs[:class].join(' ')
216
- else
217
- new[k] = v
218
- end
219
- end
220
-
221
- @attrs = new
222
- end
223
-
224
- def build_node
225
- [@tag, @attrs, @body]
226
- end
227
-
228
- def a_tag
229
- unless @attrs.include?(:href)
230
- @attrs[:href] = 'javascript:void(0)'
231
- end
232
-
233
- build_node
234
- end
235
-
236
- def input_tag
237
- unless @attrs.include?(:name)
238
- $stderr.puts "Warning: no name for <#{@tag} #{@attrs.inspect}>#{@body.inspect}</#{@tag}>"
239
-
240
- # this is somewhat ungainly: we generate a name automatically by hashing the values of the
241
- # other attrs. this may conflict - ideally would track per-page state for these.
242
- # purpose is to have name-less inputs preserve their values when user goes back to page.
243
- # webkit in safari 5 gets confused when inputs are nameless.
244
- unless (vals = @attrs.values{|k, v| v.kind_of?(String)}).empty?
245
- $stderr.puts "Generating a name automatically..."
246
- @attrs[:name] = vals.join.md5[0..10]
247
- end
248
- end
249
-
250
- build_node
251
- end
252
-
253
- def dispatch
254
- meth = "#{@tag}_tag".to_sym
255
-
256
- if self.respond_to?(meth)
257
- self.send(meth)
258
- else
259
- build_node
260
- end
261
- end
262
- end
263
-
264
- def standalone_tags
265
- %w{br img input}
266
- end
267
-
268
- def attrs_to_html(attrs)
269
- attrs.map {|k, v| "#{k}=\"#{v}\""}.join(' ')
270
- end
271
-
272
- def array_to_html(ar)
273
- if ar.first.kind_of?(Symbol)
274
- tag = ar[0]
275
- have_attrs = ar[1].is_a?(Hash)
276
- attrs = have_attrs ? ar[1] : {}
277
- body = ar[(have_attrs ? 2 : 1)..-1]
278
-
279
- tag, attrs, body = TagBuilder.build(tag, attrs, body)
280
- lonely = standalone_tags.include?(tag.to_s)
281
-
282
- open_tag = "<#{tag}#{attrs.empty? ? '' : (' ' + attrs_to_html(attrs))}>"
283
- body_html = body.empty? ? '' : body.map{|x| x.to_html(self)}.join
284
- close_tag = (lonely ? '' : "</#{tag}>")
285
-
286
- "#{open_tag}#{body_html}#{close_tag}"
287
- else
288
- ar.map{|x| x.to_html(self)}.join
289
- end
290
- end
291
-
292
- def initialize(input)
293
- @input = input
294
- end
295
-
296
- def html
297
- @input.to_html(self)
298
- end
299
- end
300
- end
301
- end