seapig-server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +36 -0
  4. data/Rakefile +37 -0
  5. data/bin/seapig-server +289 -0
  6. data/lib/seapig-server.rb +4 -0
  7. data/lib/seapig/version.rb +3 -0
  8. data/test/dummy/README.rdoc +28 -0
  9. data/test/dummy/Rakefile +6 -0
  10. data/test/dummy/app/assets/javascripts/application.js +15 -0
  11. data/test/dummy/app/assets/javascripts/json-patch.js +392 -0
  12. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  13. data/test/dummy/app/controllers/application_controller.rb +9 -0
  14. data/test/dummy/app/helpers/application_helper.rb +2 -0
  15. data/test/dummy/app/views/application/index.html.slim +10 -0
  16. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  17. data/test/dummy/bin/bundle +3 -0
  18. data/test/dummy/bin/rails +4 -0
  19. data/test/dummy/bin/rake +4 -0
  20. data/test/dummy/bin/setup +29 -0
  21. data/test/dummy/config.ru +4 -0
  22. data/test/dummy/config/application.rb +26 -0
  23. data/test/dummy/config/boot.rb +5 -0
  24. data/test/dummy/config/database.yml +85 -0
  25. data/test/dummy/config/environment.rb +5 -0
  26. data/test/dummy/config/environments/development.rb +41 -0
  27. data/test/dummy/config/environments/production.rb +79 -0
  28. data/test/dummy/config/environments/test.rb +42 -0
  29. data/test/dummy/config/initializers/assets.rb +11 -0
  30. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  31. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  32. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/test/dummy/config/initializers/inflections.rb +16 -0
  34. data/test/dummy/config/initializers/mime_types.rb +4 -0
  35. data/test/dummy/config/initializers/session_store.rb +3 -0
  36. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  37. data/test/dummy/config/locales/en.yml +23 -0
  38. data/test/dummy/config/routes.rb +56 -0
  39. data/test/dummy/config/secrets.yml +22 -0
  40. data/test/dummy/lib/seapigs/random.rb +14 -0
  41. data/test/dummy/log/development.log +1665 -0
  42. data/test/dummy/public/404.html +67 -0
  43. data/test/dummy/public/422.html +67 -0
  44. data/test/dummy/public/500.html +66 -0
  45. data/test/dummy/public/favicon.ico +0 -0
  46. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/-o-ZYYvENmbyGnbDFt6qAW7obI2QzsO9rM9EA7XlUHg.cache +0 -0
  47. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/1PVg-zJVu7--eXgp92_OEGf9YMjumXrwhnEbNQdV4h8.cache +1 -0
  48. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/2R9FCEwuVplMI7I5GoTBtw2Er_ap6H5s2otDK-m5XCk.cache +0 -0
  49. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/3eEw6ayC9zVKEVm7sgtqdiFFsi-0w4VyeWxR-hk72xg.cache +0 -0
  50. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/5Lly_CA8DZvPhQV2jDQx-Y6P_y3Ygra9t5jfSlGhHDA.cache +0 -0
  51. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/66l7LMqenzmlIoboeeCMFEZ6J_6zFdXmqNs-gu79P94.cache +1 -0
  52. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/6vXjezhMg67rV3BDCPOxLeOVbgCHn0aDwBXCA-N7_To.cache +2 -0
  53. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/7SWrjVR_xhTD9BCS4ifXzZIDn6dp3guSJjF8v5pYDRc.cache +1 -0
  54. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/98GLSy3KoIvHccw_zbxLg3FpmPEUKmGUr5oL7YZmvOU.cache +127 -0
  55. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9Kel6H2jL5qJlsGHIcYRbxGaV-rMj_teA3CD1eaUVmk.cache +2 -0
  56. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9T1WawqT-S8RLLgLI4J-o5mcGyy-wwU2mYMBwTuTl4o.cache +0 -0
  57. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BXgyeZH3I-u535wy4F0jP3wmfV8m8WIWtXqHJKdBC2Q.cache +0 -0
  58. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BsW5E247kS632hUZ5-NHcjkfprM3AwsB8dksndomUAY.cache +1 -0
  59. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/CW1ly2CbYhIQ3PoOCrGTUOxS8a03kI-4spp4REHx6mc.cache +0 -0
  60. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Duqe6Cf2ThStcjIWL29RZnPilo1v6Vi7p86VOEBKqCs.cache +0 -0
  61. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/ETKdv5blZ86XWjAQcmxAQq2wK6RT9QvGqd7uV7DK1Iw.cache +2 -0
  62. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/HCc1doOYzZaBpAX7ozNMpo1ErZFli_aaKFQ73oRipH0.cache +2 -0
  63. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/KS-8mb2c29Zj9YWoyTAojF-sqg-aKMYQr6FkxGuoBWQ.cache +0 -0
  64. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Lgka3bEq-I8Y6xz-LVIYNVeNIWkmW1NKDnOHOkYcxtE.cache +1 -0
  65. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/OI6uxGcnsKavdWTtwDAasU3wPx8QXhzBgV0X2n1KjMQ.cache +0 -0
  66. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/PgVoTat4a0VNolKPJS5RtDX7XcbAGbsqhquIWgWBBWE.cache +0 -0
  67. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Tq5PhgpHymowY-e-a3airP7Q2OwxNuNC0792hdlAJRc.cache +1 -0
  68. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VegRto_fFjOSBWQRgNRnnOiV3yByrpUI9b5IEhRvrDI.cache +1 -0
  69. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VqL7bJxBiq13xYePLO71Spa6RfD5dFCeRRLGYb5jEqw.cache +0 -0
  70. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/X5QxTUYFP4VOH_7p2Qh34msJtFA6fOnJ0mM7Zip7dRU.cache +1 -0
  71. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_2P0GKBCbJ61e8RdTBx4rJr8TsUYKFJYd7N0ZhA7o6k.cache +0 -0
  72. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_mxi_K1gl7n32DYq8bFfbWrIRQrU3QQiuukc63_UBb4.cache +1 -0
  73. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/a1-5JrPXbtVr0ytoeAR0GzDsG_rlYUHm_sKEC1VHrrM.cache +1 -0
  74. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/aShHx921rUO4rZzH4135LWkt4TSWfqhpQMN8JsV2OqE.cache +0 -0
  75. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/b9Ac87wpHRTGZL-mBacNdb343KQ1426WdjSu03OVlz8.cache +0 -0
  76. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/beCZt_nFEyk5iX6v4bgC3qrdFMgSM0IgrwxaBTFEFA0.cache +1 -0
  77. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/bkclf1HVUxdntd8cKLvWGX5Pq-E12kwCwakqLo8RoN4.cache +1 -0
  78. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/csrXH9BNqM8JCRjroMFCt2hluEvIvM0neY_ZQySl58A.cache +1 -0
  79. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/fsxRcZUqoELh6-D0RWowwDKHYmUkGzh5HMFuwMMWk1g.cache +1 -0
  80. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/h2XCtwcdt21JcAtjhfoOuIjifaMeMaYwPcFGnmNc2ng.cache +1 -0
  81. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +3 -0
  82. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j3q-1jQzykD1sMeX9INl7jygCKtC0XJ8JkWkFQkL6qg.cache +2 -0
  83. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j4VcbkSejSElTiJk3ehlEcJE9HRTG7T14-wVhUDocaA.cache +1 -0
  84. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/jU_8gMY9eZiN785s1lakKFjnK5ox1EA-Na1fKxuYcjs.cache +1 -0
  85. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +2 -0
  86. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/q3UrpsuPTq0hMrkTWoYoYEZL4u05PdVc4L5NXKuFDgQ.cache +0 -0
  87. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/sX4-Kn9knBu7plHg7gUT-ryVktGvmZfacNm1cUysjWs.cache +1 -0
  88. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/umxcUEQeoCOqF0ZwXlNqJEOyT_vhMK8iGO4--jduIDU.cache +1 -0
  89. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/urzLHu3R3qKeFqygSDL0NVDcyw8BFEA6i9jFeweVZQ4.cache +1 -0
  90. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vCljKmhe1Sy9j9TDIB9DrQRa8dYlkPg8nyvsZfqfCmw.cache +0 -0
  91. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vn6FA8BrDkisPSH4VFlc7tgmkzpx1DsOtDp5C3cdaRU.cache +1 -0
  92. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/wtuO4JuGsyO8emVQL7t1bm1fvl6YzGfBHc3tJJ49uvs.cache +2 -0
  93. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/z9KVC85iQuroh2hLYTGZhgZHDlh7183qTlfed3Uhtnw.cache +0 -0
  94. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/znvRewDLwMSRAUjlQozzMBQ_IGWvw9fVVOh9aeaXxTA.cache +0 -0
  95. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/zxZcbwt4p6Q1Z7yZfuWYKTXjetmNZI8MgkKazd4PjoA.cache +0 -0
  96. data/test/integration/navigation_test.rb +8 -0
  97. data/test/seapig_test.rb +7 -0
  98. data/test/test_helper.rb +20 -0
  99. metadata +275 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 33a42ea75372a1f1cf7fa95c5f7ca88ad46d789c
4
+ data.tar.gz: daf21ca47cc732516621bb5b05fa6a453655203a
5
+ SHA512:
6
+ metadata.gz: 68d63fc5779b3f3c3e19df5d403c7227ec8a4e233a82a23189db8bec15ce2fde2796706abaf913f45de8435116597cdc06b416d245972f634f781bceaf6cc8f8
7
+ data.tar.gz: 9dc56616fb19d88bbab9ce34f22faf22494b1700faf1679eef3f45a1bc24c506fead2a4dbcc355b69b23bc056ba86f8672cd99bfd5b763b731d45c286ec5e045
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 yunta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = Seapig
2
+
3
+ Nothing here yet.
4
+
5
+ To cover:
6
+
7
+ * what are seapigs and why they exist? (why not just having a db table?)
8
+ * link to https://www.youtube.com/watch?v=_y4DbZivHCY
9
+ * properties of seapig system
10
+ * data is cached in memory
11
+ * only diffs are sent
12
+ * data is generated in separate processes
13
+ * data gets regenerated on dependency change (immediately on db change)
14
+ * describe current limits
15
+ * one level deps only
16
+ * single object can have at most one generation process running at all times
17
+ * cache is dropped when last client unlinks
18
+ * no client-side timeout detection
19
+ * postgres only (but is that really a problem? ;))
20
+ * graph of server/client side seapig object states
21
+ * disclaimer on non-efficient code. it's all a draft, a test of idea.
22
+ * rails not needed
23
+ * stuff will change
24
+ * seapig = firebase for apps that need real db
25
+ * works nicely with mithril
26
+ * how to use this shit
27
+ * dummy app is a minimal demo
28
+ * bundle exec rails s
29
+ * bundle exec ruby ../../bin/seapig-server.rb
30
+ * bundle exec ruby ../../bin/seapig-worker.rb ws://127.0.0.1:3001/seapig
31
+ * bundle exec ruby ../../bin/seapig-notifier.rb ws://127.0.0.1:3001/seapig
32
+ * application.js needs: require seapig/seapig
33
+ * application.js needs: require json-patch
34
+ * ActiveRecord models that are used for triggering regeneration of data need:
35
+ * acts_as_seapig_dependency
36
+ * and seapig_dependency_changed after commits
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Seapig'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ #APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ #load 'rails/tasks/engine.rake'
19
+
20
+
21
+ #load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
data/bin/seapig-server ADDED
@@ -0,0 +1,289 @@
1
+ #!/bin/env ruby
2
+
3
+ require 'websocket-eventmachine-server'
4
+ require 'json'
5
+ require 'jsondiff'
6
+ require 'hana'
7
+ require 'set'
8
+
9
+
10
+ class String
11
+
12
+ def starexp
13
+ Regexp.new(Regexp.escape(self).gsub('\*','.*?'))
14
+ end
15
+
16
+
17
+ def starexp?
18
+ self.include?('*')
19
+ end
20
+
21
+ end
22
+
23
+
24
+
25
+ class SeapigObject
26
+
27
+ attr_reader :id, :version, :valid
28
+
29
+ @@objects_by_id = Hash.new { |hash,object_id|
30
+ puts "Creating: "+(object = SeapigObject.new(object_id)).id
31
+ hash[object_id] = object
32
+ }
33
+
34
+
35
+ def self.[](id)
36
+ @@objects_by_id[id]
37
+ end
38
+
39
+
40
+ def self.all
41
+ @@objects_by_id.values
42
+ end
43
+
44
+
45
+ def self.matching(id)
46
+ @@objects_by_id.values.select { |object| object.matches?(id) }
47
+ end
48
+
49
+
50
+ def matches?(id)
51
+ @id =~ id.starexp
52
+ end
53
+
54
+
55
+ def initialize(id)
56
+ @id = id
57
+ @valid = false
58
+ @object = {}
59
+ @version = 0
60
+ @@objects_by_id[@id] = self
61
+ end
62
+
63
+
64
+ def self.gc
65
+ used_object_ids = Set.new
66
+ Client.all.each { |client| used_object_ids.merge(client.consumes.map { |object| object.id }) } #objects with direct consuments (no pattern matching)
67
+ @@objects_by_id.values.each { |object| used_object_ids << object.id if Client.all.find { |client| client.produces.include?(object.id) } and Client.all.find { |client| client.consumes.find { |consumed| consumed.id.starexp? and object.id =~ consumed.id.starexp } } } #objects having producers AND wildcard consuments
68
+ @@objects_by_id.values.each { |object| object.version.keys.each { |key| used_object_ids << key } if object.version.kind_of?(Hash) } # objects that others depend on
69
+ @@objects_by_id.keys.select { |id| not used_object_ids.include?(id) }.each { |id|
70
+ Client.all.select { |client| client.consumes.find { |object| object.id.starexp? and id =~ object.id.starexp } }.each { |client|
71
+ puts "Destroying: "+id
72
+ client.socket.send JSON.dump(action: 'object-destroy', id: id)
73
+ }
74
+ puts "Deleting: "+id
75
+ @@objects_by_id.delete(id)
76
+ }
77
+ end
78
+
79
+
80
+ def patch(patch, value, from_version, new_version)
81
+ print "Patching version:"+@version.inspect+" from_version:"+from_version.inspect+" new_version:"+new_version.inspect+' --> '
82
+ if from_version == @version or from_version == 0
83
+ puts 'Clean'
84
+ old_object = JSON.load(JSON.dump(@object))
85
+ old_version = @version
86
+ @object.clear if from_version == 0 or value
87
+ Hana::Patch.new(patch).apply(@object) if patch
88
+ @object.merge!(value) if value
89
+ @version = new_version
90
+ Client.all.each { |client| send(client, old_version, old_object) }
91
+ SeapigObject.all.each { |object| object.check_validity }
92
+ elsif from_version > @version
93
+ puts "Lost some updates, reinitializing object"
94
+ @version = 0
95
+ @object.clear
96
+ @valid = false
97
+ else
98
+ puts "Late update, ignoring"
99
+ end
100
+ end
101
+
102
+
103
+ def send(client, old_version, old_object, patch = nil)
104
+ return false if not client.consumes.find { |object| (object == self) or self.matches?(object.id) }
105
+ puts 'Sending '+self.id
106
+ client.socket.send JSON.dump(
107
+ action: 'object-update',
108
+ id: @id,
109
+ old_version: old_version,
110
+ new_version: @version,
111
+ patch: (patch or JsonDiff.generate(old_object, @object)))
112
+ end
113
+
114
+
115
+ def check_validity
116
+ @valid = if not @version.kind_of?(Hash) then (@version > 0) else not @version.to_a.find { |dependency_id, dependency_version|
117
+ SeapigObject[dependency_id] and SeapigObject[dependency_id].version and SeapigObject[dependency_id].version > dependency_version
118
+ } end
119
+ end
120
+
121
+
122
+ def inspect
123
+ '<SO:%s:%s:%s>'%[@id, @version, (@valid and 'V' or 'I')]
124
+ end
125
+
126
+ end
127
+
128
+
129
+
130
+ class Client
131
+
132
+ attr_reader :produces, :consumes, :socket, :producing, :index
133
+ attr_accessor :options
134
+
135
+ @@clients_by_socket = {}
136
+ @@count = 0
137
+
138
+
139
+ def self.[](id)
140
+ @@clients_by_socket[id]
141
+ end
142
+
143
+
144
+ def self.all
145
+ @@clients_by_socket.values
146
+ end
147
+
148
+
149
+ def initialize(socket)
150
+ @index = @@count += 1
151
+ puts 'Creating client: '+@index.to_s
152
+ @socket = socket
153
+ @options = {}
154
+ @produces = []
155
+ @consumes = []
156
+ @producing = nil
157
+ @@clients_by_socket[socket] = self
158
+ end
159
+
160
+
161
+ def id
162
+ (@options['name'] or "") + ':' + @index.to_s
163
+ end
164
+
165
+
166
+ def destroy
167
+ puts 'Destroying client: '+@index.to_s
168
+ @@clients_by_socket.delete(@socket)
169
+ SeapigObject.gc
170
+ Client.all.find { |client| client.assign(@producing) } if @producing and SeapigObject.all.include?(@producing)
171
+ end
172
+
173
+
174
+ def producer_register(pattern)
175
+ @produces.push(pattern)
176
+ SeapigObject.all.each { |object| self.assign(object) }
177
+ end
178
+
179
+
180
+ def consumer_register(object)
181
+ @consumes.push(object)
182
+ Client.all.each { |client| client.produces.each { |pattern| SeapigObject[pattern] if (not pattern.starexp?) and (pattern =~ object.id.starexp) } } if object.id.starexp?
183
+ SeapigObject.matching(object.id).each { |object|
184
+ Client.all.find { |client| client.assign(object) }
185
+ object.send(self,nil,{}) if object.valid
186
+ }
187
+ end
188
+
189
+
190
+ def assign(object)
191
+ puts 'Assign? %20s <> %-30s - %s'%[self.id, object.id, [object.valid,object.id.starexp?,@producing,Client.all.find { |client| client.producing == object },(not @produces.find { |pattern| object.id =~ pattern.starexp })].map { |b| b and 'T' or 'F' }.join('')]
192
+ return true if object.valid
193
+ return true if object.id.starexp?
194
+ return false if @producing
195
+ return true if Client.all.find { |client| client.producing == object }
196
+ return false if not @produces.find { |pattern| object.id =~ pattern.starexp }
197
+ puts 'Assigning: '+object.id+' to: '+self.id
198
+ @socket.send JSON.dump(action: 'object-produce', id: object.id)
199
+ @producing = object
200
+ end
201
+
202
+
203
+ def release(object)
204
+ puts 'Releasing: '+object.id+' from: '+self.id
205
+ @producing = nil if @producing == object
206
+ end
207
+
208
+
209
+ def ping
210
+ @socket.ping
211
+ end
212
+
213
+
214
+ def pong
215
+ @pong_time = Time.new
216
+ end
217
+
218
+
219
+ def check_ping_timeout
220
+ @socket.close if Time.new - pong > 60
221
+ end
222
+ end
223
+
224
+
225
+
226
+
227
+
228
+ EM.run {
229
+
230
+
231
+ WebSocket::EventMachine::Server.start(host: "0.0.0.0", port: 3001) { |client_socket|
232
+
233
+ client_socket.onmessage { |message|
234
+ client = Client[client_socket]
235
+ message = JSON.load message
236
+ puts "-"*80 + ' ' + Time.new.to_s + "\nMessage: %-20s %-30s %s:%s"%[client.id, message['action'], (message["id"] and "ID" or "Pattern"),(message["id"] or message["pattern"])]
237
+ object = SeapigObject[message['id']] if message['id']
238
+ case message['action']
239
+ when 'object-producer-register'
240
+ fail unless message['pattern']
241
+ client.producer_register(message['pattern'])
242
+ when 'object-producer-unregister'
243
+ fail unless message['pattern']
244
+ client.producer_unregister(message['pattern'])
245
+ when 'object-patch'
246
+ fail unless message['id'] and message['new_version'] and message['old_version']
247
+ SeapigObject.gc
248
+ if SeapigObject.all.include?(object) # ignoring objects nobody listens to
249
+ object.patch(message['patch'], message['value'], message['old_version'], message['new_version'])
250
+ client.release(object)
251
+ SeapigObject.all.each { |object| Client.all.find { |client| client.assign(object) } }
252
+ end
253
+ when 'object-consumer-register'
254
+ fail unless message['id']
255
+ client.consumer_register(object)
256
+ when 'object-consumer-unregister'
257
+ fail unless message['id']
258
+ client.consumer_unregister(object)
259
+ when 'client-options-set'
260
+ fail unless message['options']
261
+ client.options = message['options']
262
+ else
263
+ puts '***** WTF, got message with action: ' + message['action'].inspect
264
+ end
265
+ puts "Clients:\n"+Client.all.map { |client| "\t%-20s produces:%s consumes:%s"%[client.id,client.produces.inspect,client.consumes.map { |obj| obj.id }] }.join("\n")+"\n"
266
+ puts "Objects:\n"+SeapigObject.all.map { |object| "\t%s"%[object.inspect] }.join("\n")+"\n"
267
+ }
268
+
269
+
270
+ client_socket.onopen { Client.new(client_socket) }
271
+ client_socket.onclose { Client[client_socket].destroy if Client[client_socket] }
272
+ client_socket.onpong { Client[client_socket].pong }
273
+ }
274
+
275
+
276
+ EM.add_periodic_timer(10) {
277
+ Client.all.each { |client|
278
+ client.ping
279
+ }
280
+ }
281
+
282
+
283
+ EM.add_periodic_timer(10) {
284
+ Client.all.each { |client|
285
+ client.check_ping_timeout
286
+ }
287
+ }
288
+
289
+ }
@@ -0,0 +1,4 @@
1
+ #require "seapig/server.rb"
2
+
3
+ module Seapig
4
+ end
@@ -0,0 +1,3 @@
1
+ module Seapig
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
14
+ //= require seapig/seapig
15
+ //= require json-patch