share-data-watcher 0.2.1
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 +7 -0
- data/.gitignore +36 -0
- data/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +40 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +124 -0
- data/README.md +11 -0
- data/bitbucket-pipelines.yml +36 -0
- data/config.reek +7 -0
- data/docker-compose.yml +21 -0
- data/example/sample +1 -0
- data/example/sample.rb +28 -0
- data/lib/share-data-watcher/class_interface.rb +44 -0
- data/lib/share-data-watcher/connection.rb +58 -0
- data/lib/share-data-watcher/instance_methods.rb +92 -0
- data/lib/share-data-watcher/loggable.rb +31 -0
- data/lib/share-data-watcher/struct.rb +37 -0
- data/lib/share-data-watcher/type/base.rb +104 -0
- data/lib/share-data-watcher/type/fetch.rb +39 -0
- data/lib/share-data-watcher/type/watch.rb +82 -0
- data/lib/share-data-watcher/type.rb +7 -0
- data/lib/share-data-watcher/version.rb +5 -0
- data/lib/share-data-watcher.rb +25 -0
- data/share-data-watch.gemspec +29 -0
- metadata +138 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1a9dd6ba2eb14954eff10b7dbf83658af09a1b3e4c7a6c0ca80ea955b3bdb26d
|
|
4
|
+
data.tar.gz: 7a6982813617d097edaa1ca5e0369e95ee6181ac45e2d1f8d8f9488f6d630cd6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5ad3ab8e0b3a5928f751123c93c1528845ab64ffde519d30b714bc92494583ca606e97dee28ff1533359e091ff7656c4541686e6a4754220e5b737a3b4271cae
|
|
7
|
+
data.tar.gz: 3f81af461d2a95999b2c0340d5e8b7b16f522d90edfed427bbd7417b474fbfd286a10134396d8385697c8cdae9401176856b6fbca0b3bf15b4ac7ef08e4af9a1
|
data/.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
*.gem
|
|
2
|
+
*.rbc
|
|
3
|
+
.bundle
|
|
4
|
+
.config
|
|
5
|
+
coverage
|
|
6
|
+
InstalledFiles
|
|
7
|
+
lib/bundler/man
|
|
8
|
+
pkg
|
|
9
|
+
rdoc
|
|
10
|
+
spec/reports
|
|
11
|
+
test/tmp
|
|
12
|
+
test/version_tmp
|
|
13
|
+
tmp
|
|
14
|
+
|
|
15
|
+
# YARD artifacts
|
|
16
|
+
.yardoc
|
|
17
|
+
_yardoc
|
|
18
|
+
doc/
|
|
19
|
+
|
|
20
|
+
# Vim
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# ctags
|
|
25
|
+
tags
|
|
26
|
+
|
|
27
|
+
#Mac
|
|
28
|
+
.DS_Store
|
|
29
|
+
tmp/
|
|
30
|
+
|
|
31
|
+
.idea
|
|
32
|
+
reports/
|
|
33
|
+
target/
|
|
34
|
+
log
|
|
35
|
+
migrations/*.db
|
|
36
|
+
vendor
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Common configuration.
|
|
2
|
+
|
|
3
|
+
require:
|
|
4
|
+
- rubocop-performance
|
|
5
|
+
|
|
6
|
+
AllCops:
|
|
7
|
+
TargetRubyVersion: 2.5
|
|
8
|
+
Exclude:
|
|
9
|
+
- target/**/*
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
#################### Layout ###########################
|
|
13
|
+
Layout/LineLength:
|
|
14
|
+
Max: 120
|
|
15
|
+
|
|
16
|
+
#################### Metrics ###########################
|
|
17
|
+
Metrics/MethodLength:
|
|
18
|
+
Max: 20
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.5.5
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
All notable changes to this project will be documented in this file
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
***
|
|
6
|
+
|
|
7
|
+
## [0.2.1]
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
* after_change hook pass builded object to proc hook
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
***
|
|
14
|
+
|
|
15
|
+
## [0.2.0]
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
* Watch Type support after_change hook
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
23
|
+
## [0.1.1]
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
* Regx Key support "-" And "_"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
***
|
|
30
|
+
|
|
31
|
+
## [0.1.0]
|
|
32
|
+
# First Release
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
### Deleted
|
|
39
|
+
|
|
40
|
+
***
|
data/Gemfile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'http://rubygems.org'
|
|
4
|
+
|
|
5
|
+
ruby '~> 2.5'
|
|
6
|
+
|
|
7
|
+
gemspec
|
|
8
|
+
|
|
9
|
+
group :test do
|
|
10
|
+
gem 'as-duration'
|
|
11
|
+
gem 'faker'
|
|
12
|
+
gem 'fuubar'
|
|
13
|
+
gem 'rspec'
|
|
14
|
+
gem 'rspec-nc'
|
|
15
|
+
gem 'simplecov'
|
|
16
|
+
gem 'ulid'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group :development do
|
|
20
|
+
gem 'rubocop', require: false
|
|
21
|
+
gem 'rubocop-performance', require: false
|
|
22
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
share-data-watcher (0.2.1)
|
|
5
|
+
bundler (>= 1.16.1)
|
|
6
|
+
dry-struct (>= 1.2.0)
|
|
7
|
+
etcdv3 (~> 0.10.0)
|
|
8
|
+
multi_json (>= 1.14.1)
|
|
9
|
+
oj (>= 3.9.2)
|
|
10
|
+
|
|
11
|
+
GEM
|
|
12
|
+
remote: http://rubygems.org/
|
|
13
|
+
specs:
|
|
14
|
+
as-duration (0.1.1)
|
|
15
|
+
ast (2.4.0)
|
|
16
|
+
concurrent-ruby (1.1.6)
|
|
17
|
+
diff-lcs (1.3)
|
|
18
|
+
docile (1.3.2)
|
|
19
|
+
dry-configurable (0.11.1)
|
|
20
|
+
concurrent-ruby (~> 1.0)
|
|
21
|
+
dry-core (~> 0.4, >= 0.4.7)
|
|
22
|
+
dry-equalizer (~> 0.2)
|
|
23
|
+
dry-container (0.7.2)
|
|
24
|
+
concurrent-ruby (~> 1.0)
|
|
25
|
+
dry-configurable (~> 0.1, >= 0.1.3)
|
|
26
|
+
dry-core (0.4.9)
|
|
27
|
+
concurrent-ruby (~> 1.0)
|
|
28
|
+
dry-equalizer (0.3.0)
|
|
29
|
+
dry-inflector (0.2.0)
|
|
30
|
+
dry-logic (1.0.6)
|
|
31
|
+
concurrent-ruby (~> 1.0)
|
|
32
|
+
dry-core (~> 0.2)
|
|
33
|
+
dry-equalizer (~> 0.2)
|
|
34
|
+
dry-struct (1.3.0)
|
|
35
|
+
dry-core (~> 0.4, >= 0.4.4)
|
|
36
|
+
dry-equalizer (~> 0.3)
|
|
37
|
+
dry-types (~> 1.3)
|
|
38
|
+
ice_nine (~> 0.11)
|
|
39
|
+
dry-types (1.3.1)
|
|
40
|
+
concurrent-ruby (~> 1.0)
|
|
41
|
+
dry-container (~> 0.3)
|
|
42
|
+
dry-core (~> 0.4, >= 0.4.4)
|
|
43
|
+
dry-equalizer (~> 0.3)
|
|
44
|
+
dry-inflector (~> 0.1, >= 0.1.2)
|
|
45
|
+
dry-logic (~> 1.0, >= 1.0.2)
|
|
46
|
+
etcdv3 (0.10.2)
|
|
47
|
+
grpc (~> 1.17)
|
|
48
|
+
faker (2.10.2)
|
|
49
|
+
i18n (>= 1.6, < 2)
|
|
50
|
+
fuubar (2.5.0)
|
|
51
|
+
rspec-core (~> 3.0)
|
|
52
|
+
ruby-progressbar (~> 1.4)
|
|
53
|
+
google-protobuf (3.11.4)
|
|
54
|
+
googleapis-common-protos-types (1.0.4)
|
|
55
|
+
google-protobuf (~> 3.0)
|
|
56
|
+
grpc (1.27.0)
|
|
57
|
+
google-protobuf (~> 3.11)
|
|
58
|
+
googleapis-common-protos-types (~> 1.0)
|
|
59
|
+
i18n (1.8.2)
|
|
60
|
+
concurrent-ruby (~> 1.0)
|
|
61
|
+
ice_nine (0.11.2)
|
|
62
|
+
jaro_winkler (1.5.4)
|
|
63
|
+
multi_json (1.14.1)
|
|
64
|
+
oj (3.10.2)
|
|
65
|
+
parallel (1.19.1)
|
|
66
|
+
parser (2.7.0.2)
|
|
67
|
+
ast (~> 2.4.0)
|
|
68
|
+
rainbow (3.0.0)
|
|
69
|
+
rexml (3.2.4)
|
|
70
|
+
rspec (3.9.0)
|
|
71
|
+
rspec-core (~> 3.9.0)
|
|
72
|
+
rspec-expectations (~> 3.9.0)
|
|
73
|
+
rspec-mocks (~> 3.9.0)
|
|
74
|
+
rspec-core (3.9.1)
|
|
75
|
+
rspec-support (~> 3.9.1)
|
|
76
|
+
rspec-expectations (3.9.0)
|
|
77
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
78
|
+
rspec-support (~> 3.9.0)
|
|
79
|
+
rspec-mocks (3.9.1)
|
|
80
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
81
|
+
rspec-support (~> 3.9.0)
|
|
82
|
+
rspec-nc (0.3.0)
|
|
83
|
+
rspec (>= 3)
|
|
84
|
+
terminal-notifier (>= 1.4)
|
|
85
|
+
rspec-support (3.9.2)
|
|
86
|
+
rubocop (0.80.0)
|
|
87
|
+
jaro_winkler (~> 1.5.1)
|
|
88
|
+
parallel (~> 1.10)
|
|
89
|
+
parser (>= 2.7.0.1)
|
|
90
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
91
|
+
rexml
|
|
92
|
+
ruby-progressbar (~> 1.7)
|
|
93
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
|
94
|
+
rubocop-performance (1.5.2)
|
|
95
|
+
rubocop (>= 0.71.0)
|
|
96
|
+
ruby-progressbar (1.10.1)
|
|
97
|
+
simplecov (0.18.2)
|
|
98
|
+
docile (~> 1.1)
|
|
99
|
+
simplecov-html (~> 0.11)
|
|
100
|
+
simplecov-html (0.12.0)
|
|
101
|
+
terminal-notifier (2.0.0)
|
|
102
|
+
ulid (1.2.0)
|
|
103
|
+
unicode-display_width (1.6.1)
|
|
104
|
+
|
|
105
|
+
PLATFORMS
|
|
106
|
+
ruby
|
|
107
|
+
|
|
108
|
+
DEPENDENCIES
|
|
109
|
+
as-duration
|
|
110
|
+
faker
|
|
111
|
+
fuubar
|
|
112
|
+
rspec
|
|
113
|
+
rspec-nc
|
|
114
|
+
rubocop
|
|
115
|
+
rubocop-performance
|
|
116
|
+
share-data-watcher!
|
|
117
|
+
simplecov
|
|
118
|
+
ulid
|
|
119
|
+
|
|
120
|
+
RUBY VERSION
|
|
121
|
+
ruby 2.5.1p57
|
|
122
|
+
|
|
123
|
+
BUNDLED WITH
|
|
124
|
+
1.17.2
|
data/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# share-data-watcher
|
|
2
|
+
|
|
3
|
+
Share Data Watcher For Game Operation
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
- https://bitbucket.org/gamesource/share-data-watcher/src/master/example/
|
|
7
|
+
- https://bitbucket.org/gamesource/share-data-watcher/src/master/spec/functional/sample/
|
|
8
|
+
|
|
9
|
+
Struct Type Please Ref:
|
|
10
|
+
- https://dry-rb.org/gems/dry-struct/1.0/
|
|
11
|
+
- https://dry-rb.org/gems/dry-types/1.2/
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# This is a sample build configuration for Ruby.
|
|
2
|
+
# Check our guides at https://confluence.atlassian.com/x/8r-5Mw for more examples.
|
|
3
|
+
# Only use spaces to indent your .yml configuration.
|
|
4
|
+
# -----
|
|
5
|
+
# You can specify a custom docker image from Docker Hub as your build environment.
|
|
6
|
+
image: ruby:2.5.5
|
|
7
|
+
|
|
8
|
+
pipelines:
|
|
9
|
+
default:
|
|
10
|
+
- step:
|
|
11
|
+
caches:
|
|
12
|
+
- bundler
|
|
13
|
+
script:
|
|
14
|
+
- bundle install
|
|
15
|
+
- bundle exec rspec -cfd
|
|
16
|
+
services:
|
|
17
|
+
- etcd
|
|
18
|
+
|
|
19
|
+
definitions:
|
|
20
|
+
caches:
|
|
21
|
+
bundler: ./vendor
|
|
22
|
+
services:
|
|
23
|
+
etcd:
|
|
24
|
+
image: quay.io/coreos/etcd:v3.3
|
|
25
|
+
variables:
|
|
26
|
+
ETCD_NAME: 'etcd-1'
|
|
27
|
+
ETCD_HEARTBEAT_INTERVAL: '250'
|
|
28
|
+
ETCD_ELECTION_TIMEOUT: '1250'
|
|
29
|
+
ETCD_INITIAL_CLUSTER_STATE: 'new'
|
|
30
|
+
ETCD_INITIAL_ADVERTISE_PEER_URLS: 'http://0.0.0.0:2380'
|
|
31
|
+
ETCD_INITIAL_CLUSTER: 'etcd-1=http://0.0.0.0:2380'
|
|
32
|
+
ETCD_INITIAL_CLUSTER_TOKEN: 'mys3cr3ttok3n'
|
|
33
|
+
ETCD_LISTEN_PEER_URLS: 'http://0.0.0.0:2380'
|
|
34
|
+
ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379'
|
|
35
|
+
ETCD_ADVERTISE_CLIENT_URLS: 'http://0.0.0.0:2379'
|
|
36
|
+
ETCD_AUTO_COMPACTION_RETENTION: 1
|
data/config.reek
ADDED
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: '3'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
etcd-1:
|
|
5
|
+
image: quay.io/coreos/etcd:v3.3
|
|
6
|
+
container_name: etcd-1
|
|
7
|
+
environment:
|
|
8
|
+
ETCD_NAME: 'etcd-1'
|
|
9
|
+
ETCD_HEARTBEAT_INTERVAL: '250'
|
|
10
|
+
ETCD_ELECTION_TIMEOUT: '1250'
|
|
11
|
+
ETCD_INITIAL_CLUSTER_STATE: 'new'
|
|
12
|
+
ETCD_INITIAL_ADVERTISE_PEER_URLS: 'http://0.0.0.0:2380'
|
|
13
|
+
ETCD_INITIAL_CLUSTER: 'etcd-1=http://0.0.0.0:2380'
|
|
14
|
+
ETCD_INITIAL_CLUSTER_TOKEN: 'mys3cr3ttok3n'
|
|
15
|
+
ETCD_LISTEN_PEER_URLS: 'http://0.0.0.0:2380'
|
|
16
|
+
ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379'
|
|
17
|
+
ETCD_ADVERTISE_CLIENT_URLS: 'http://0.0.0.0:2379'
|
|
18
|
+
ETCD_AUTO_COMPACTION_RETENTION: 1
|
|
19
|
+
ports:
|
|
20
|
+
- "2379:2379"
|
|
21
|
+
- "2380:2380"
|
data/example/sample
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../spec/functional/sample
|
data/example/sample.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/share-data-watcher'
|
|
4
|
+
require_relative 'sample/watcher'
|
|
5
|
+
require 'logger'
|
|
6
|
+
require 'ulid'
|
|
7
|
+
require 'json'
|
|
8
|
+
|
|
9
|
+
endpoints = ENV.fetch('ETCD_URL', 'http://0.0.0.0:2379')
|
|
10
|
+
healthcheck_interval = 10..30
|
|
11
|
+
ShareDataWatcher.logger = Logger.new(STDOUT)
|
|
12
|
+
|
|
13
|
+
watcher = Watcher.new(endpoints, healthcheck_interval: healthcheck_interval)
|
|
14
|
+
etcd_conn = Etcdv3.new(endpoints: endpoints, allow_reconnect: false)
|
|
15
|
+
|
|
16
|
+
Thread.new do
|
|
17
|
+
5.times do
|
|
18
|
+
sleep 3
|
|
19
|
+
value = ULID.generate
|
|
20
|
+
key = 'go.game_sessions.force_leave'
|
|
21
|
+
etcd_conn.put(key, JSON.dump([value]))
|
|
22
|
+
puts "#{Time.now} :: update #{key} value #{value}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
watcher.start
|
|
27
|
+
|
|
28
|
+
sleep
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ShareDataWatcher
|
|
4
|
+
# Class Methods
|
|
5
|
+
module ClassInterface
|
|
6
|
+
%i[watch fetch].each do |mesh|
|
|
7
|
+
define_method(mesh) do |name, key, **opts|
|
|
8
|
+
create_type(
|
|
9
|
+
type: mesh.to_sym,
|
|
10
|
+
name: name,
|
|
11
|
+
params: [
|
|
12
|
+
key,
|
|
13
|
+
opts
|
|
14
|
+
]
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def types
|
|
20
|
+
@types ||= []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
TypeParams = Object::Struct.new(
|
|
26
|
+
:name,
|
|
27
|
+
:type,
|
|
28
|
+
:params,
|
|
29
|
+
keyword_init: true
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def create_type(name:, type:, params:)
|
|
33
|
+
types << TypeParams.new(
|
|
34
|
+
type: type,
|
|
35
|
+
name: name,
|
|
36
|
+
params: params
|
|
37
|
+
)
|
|
38
|
+
define_method(name) do
|
|
39
|
+
@types[name]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'etcdv3'
|
|
4
|
+
|
|
5
|
+
class ShareDataWatcher
|
|
6
|
+
# Etcd connection client
|
|
7
|
+
class Connection
|
|
8
|
+
attr_reader :conn
|
|
9
|
+
|
|
10
|
+
DEFAULT_FETCH_TIMEOUT = 1
|
|
11
|
+
|
|
12
|
+
def initialize(endpoints)
|
|
13
|
+
@conn = Etcdv3.new(endpoints: endpoints, allow_reconnect: false)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def connecting_endpoint
|
|
17
|
+
conn.conn.connection.endpoint.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def healthcheck
|
|
21
|
+
conn.version
|
|
22
|
+
true
|
|
23
|
+
rescue StandardError
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def watch(key, prefix: true, start_revision: nil)
|
|
28
|
+
opt = {
|
|
29
|
+
timeout: Etcdv3::Watch::INFINITE_FUTURE
|
|
30
|
+
}
|
|
31
|
+
opt[:range_end] = increase_one_bit(key) if prefix
|
|
32
|
+
opt[:start_revision] = start_revision if start_revision
|
|
33
|
+
conn.watch(key, opt) do |events|
|
|
34
|
+
events.each do |event|
|
|
35
|
+
yield(event.type, event.kv)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fetch(key, prefix: false, timeout: nil)
|
|
41
|
+
opt = {}
|
|
42
|
+
opt[:range_end] = increase_one_bit(key) if prefix
|
|
43
|
+
opt[:timeout] = timeout || DEFAULT_FETCH_TIMEOUT
|
|
44
|
+
res = conn.get(key, opt)
|
|
45
|
+
revision = res.header.revision
|
|
46
|
+
kvs = res.kvs
|
|
47
|
+
block_given? ? yield(revision, kvs) : [revision, kvs]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
def increase_one_bit(key)
|
|
53
|
+
str = key.dup
|
|
54
|
+
str[-1] = (str.getbyte(-1) + 1).chr
|
|
55
|
+
str
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ShareDataWatcher
|
|
4
|
+
# Instance Methods For Shard Data Watcher
|
|
5
|
+
module InstanceMethods
|
|
6
|
+
attr_reader :endpoints
|
|
7
|
+
attr_reader :healthcheck_interval
|
|
8
|
+
|
|
9
|
+
def initialize(endpoints = nil, **opt)
|
|
10
|
+
@endpoints = endpoints || DEFAULT_ENDPOINT
|
|
11
|
+
@healthcheck_thread = nil
|
|
12
|
+
@healthcheck_interval = opt[:healthcheck_interval] || DEFAULT_HEALTHCHECK_INTERVAL
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start
|
|
17
|
+
@mutex.synchronize do
|
|
18
|
+
self.class.types.each do |type|
|
|
19
|
+
init_type(type)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
until valid?; sleep 1; end
|
|
24
|
+
setup_healthcheck
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def restart
|
|
29
|
+
shutdown
|
|
30
|
+
start
|
|
31
|
+
setup_healthcheck
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def shutdown
|
|
36
|
+
types.each_value(&:shutdown)
|
|
37
|
+
types.clear
|
|
38
|
+
@healthcheck_thread.exit
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def valid?
|
|
43
|
+
types.each do |_, value|
|
|
44
|
+
return false unless value.valid?
|
|
45
|
+
end
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_h
|
|
50
|
+
types.each_with_object({}) do |(key, value), result|
|
|
51
|
+
result[key] = value.to_h
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
alias to_hash to_h
|
|
56
|
+
|
|
57
|
+
protected
|
|
58
|
+
|
|
59
|
+
def init_type(type)
|
|
60
|
+
klass = Type.const_get(str_camelize(type.type.to_s))
|
|
61
|
+
types[type.name] = klass.new(*compose_params(type.params))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def setup_healthcheck
|
|
65
|
+
@healthcheck_thread = Thread.new do
|
|
66
|
+
loop do
|
|
67
|
+
sleep rand(healthcheck_interval)
|
|
68
|
+
types.each do |key, value|
|
|
69
|
+
if value.healthcheck
|
|
70
|
+
debug("#{key} alive!")
|
|
71
|
+
else
|
|
72
|
+
debug("#{key} dead! restart now")
|
|
73
|
+
@mutex.synchronize { obj.restart }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def compose_params(params)
|
|
81
|
+
params.dup.unshift(endpoints)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def types
|
|
85
|
+
@types ||= {}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def str_camelize(str)
|
|
89
|
+
str.dup.split('_').map(&:capitalize).join
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ShareDataWatcher
|
|
4
|
+
# Logger Module
|
|
5
|
+
module Loggable
|
|
6
|
+
def info(string)
|
|
7
|
+
ShareDataWatcher.logger&.info(string)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def debug(string)
|
|
11
|
+
ShareDataWatcher.logger&.debug(string)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def warn(string)
|
|
15
|
+
ShareDataWatcher.logger&.warn(string)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def error(err)
|
|
19
|
+
logger = ShareDataWatcher.logger
|
|
20
|
+
case err
|
|
21
|
+
when StandardError
|
|
22
|
+
logger&.error(
|
|
23
|
+
err: "#{err.class}:#{err.message}",
|
|
24
|
+
backtrace: err.backtrace.join("\n")
|
|
25
|
+
)
|
|
26
|
+
else
|
|
27
|
+
logger&.error err
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ShareDataWatcher
|
|
4
|
+
module Struct
|
|
5
|
+
# Base Type
|
|
6
|
+
class Base < Dry::Struct
|
|
7
|
+
include Dry.Types()
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def build(_data)
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# String Type
|
|
17
|
+
class String < Base
|
|
18
|
+
class << self
|
|
19
|
+
def build(data)
|
|
20
|
+
data.to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Hash Type
|
|
26
|
+
class Hash < Base
|
|
27
|
+
class << self
|
|
28
|
+
def build(data)
|
|
29
|
+
MultiJson.load(
|
|
30
|
+
data,
|
|
31
|
+
symbolize_keys: true
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ShareDataWatcher
|
|
4
|
+
module Type
|
|
5
|
+
# Base Type
|
|
6
|
+
class Base
|
|
7
|
+
include ShareDataWatcher::Loggable
|
|
8
|
+
|
|
9
|
+
extend Forwardable
|
|
10
|
+
def_delegators :connection, :connecting_endpoint, :healthcheck
|
|
11
|
+
|
|
12
|
+
attr_reader :key
|
|
13
|
+
|
|
14
|
+
def initialize(endpoints, key, opt = {})
|
|
15
|
+
@key = key
|
|
16
|
+
@struct = determine_struct_klass(opt[:struct])
|
|
17
|
+
@extension = opt[:extension]
|
|
18
|
+
@regex = extension && Regexp.new("^#{key}#{extension.gsub(/[.]/, '[.]').gsub(/%{(\w+)}/, '([a-zA-Z0-9\-\_]+)')}$")
|
|
19
|
+
@connection = Connection.new(endpoints)
|
|
20
|
+
reset
|
|
21
|
+
on_init
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def get(**params)
|
|
25
|
+
key = fetch_key(params)
|
|
26
|
+
values[key.to_sym]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def valid?
|
|
30
|
+
@valid
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def shutdown
|
|
34
|
+
reset
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def restart
|
|
38
|
+
shutdown
|
|
39
|
+
on_init
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_h
|
|
43
|
+
values.each_with_object({}) do |(key, value), result|
|
|
44
|
+
result[key] = value.respond_to?(:to_h) ? value.to_h : value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
alias to_hash to_h
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
def determine_struct_klass(klass)
|
|
53
|
+
return Struct::String if klass.nil?
|
|
54
|
+
raise ArgumentError, 'struct class must inherited from Struct::Base' unless klass <= Struct::Base
|
|
55
|
+
|
|
56
|
+
klass
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def on_init; end
|
|
60
|
+
|
|
61
|
+
attr_reader :values
|
|
62
|
+
attr_reader :struct
|
|
63
|
+
attr_reader :connection
|
|
64
|
+
attr_reader :extension
|
|
65
|
+
attr_reader :regex
|
|
66
|
+
|
|
67
|
+
def fetch_key(**params)
|
|
68
|
+
prefix? ? format("#{key}#{extension}", params) : key
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def prefix?
|
|
72
|
+
!extension.nil?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def put(key, value)
|
|
76
|
+
return unless regex_check?(key)
|
|
77
|
+
|
|
78
|
+
info("ShareDataWatcher #{key} update: #{value}")
|
|
79
|
+
values[key.to_sym] = struct.build(value)
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
delete(key)
|
|
82
|
+
error e
|
|
83
|
+
error "remove #{key} on ShareDataWatcher #{self.key}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def regex_check?(key)
|
|
87
|
+
return true unless regex && regex !~ key
|
|
88
|
+
|
|
89
|
+
info "#{key} is not match #{regex}."
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def delete(key)
|
|
94
|
+
info("ShareDataWatcher delete: #{key}")
|
|
95
|
+
values.delete(key.to_sym)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def reset
|
|
99
|
+
@valid = false
|
|
100
|
+
@values = {}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ShareDataWatcher
|
|
4
|
+
module Type
|
|
5
|
+
# Fetch Type
|
|
6
|
+
# For cache share watch
|
|
7
|
+
class Fetch < Base
|
|
8
|
+
attr_reader :timeout
|
|
9
|
+
|
|
10
|
+
# opt[:struct]
|
|
11
|
+
# opt[:extension]
|
|
12
|
+
# opt[:timeout]
|
|
13
|
+
def initialize(endpoints, key, opt = {})
|
|
14
|
+
@timeout = opt[:timeout]
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(**params)
|
|
19
|
+
key = fetch_key(params)
|
|
20
|
+
values[key.to_sym] || fetch(key)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
def fetch(key)
|
|
26
|
+
_revision, kvs = connection.fetch(key, prefix: false, timeout: timeout)
|
|
27
|
+
kv = kvs.first
|
|
28
|
+
return nil unless kv
|
|
29
|
+
|
|
30
|
+
put(kv.key, kv.value)
|
|
31
|
+
values[key.to_sym]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_init
|
|
35
|
+
@valid = true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ShareDataWatcher
|
|
4
|
+
module Type
|
|
5
|
+
# Watch Type
|
|
6
|
+
# Watch key changes
|
|
7
|
+
class Watch < Base
|
|
8
|
+
attr_reader :start_revision
|
|
9
|
+
attr_reader :retry_interval
|
|
10
|
+
attr_reader :after_change
|
|
11
|
+
|
|
12
|
+
DEFAULT_RETRY_INTERVAL = 30
|
|
13
|
+
|
|
14
|
+
# opt[:struct]
|
|
15
|
+
# opt[:retry_interval]
|
|
16
|
+
# opt[:extension]
|
|
17
|
+
def initialize(endpoints, key, opt = {})
|
|
18
|
+
@retry_interval = opt[:retry_interval] || DEFAULT_RETRY_INTERVAL
|
|
19
|
+
@after_change = opt[:after_change] || nil
|
|
20
|
+
|
|
21
|
+
if after_change && !after_change.is_a?(Proc)
|
|
22
|
+
raise ArgumentError, 'after_change must be proc'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@thread = nil
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def shutdown
|
|
30
|
+
@thread.exit
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
|
|
36
|
+
def reset
|
|
37
|
+
@start_revision = -1
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def on_init
|
|
42
|
+
@thread = Thread.new do
|
|
43
|
+
setup
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def init_values
|
|
48
|
+
@start_revision, kvs = connection.fetch(key, prefix: prefix?)
|
|
49
|
+
info("ShareDataWatcher init #{key}, kvs: #{kvs}, start_revision: #{start_revision}")
|
|
50
|
+
|
|
51
|
+
kvs.each do |kv|
|
|
52
|
+
put(kv.key, kv.value)
|
|
53
|
+
end
|
|
54
|
+
@valid = true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def setup_watcher
|
|
58
|
+
connection.watch(key, prefix: prefix?, start_revision: start_revision) do |type, kv|
|
|
59
|
+
case type
|
|
60
|
+
when :PUT
|
|
61
|
+
put(kv.key, kv.value)
|
|
62
|
+
after_change&.call(type, kv.key, values[kv.key.to_sym])
|
|
63
|
+
when :DELETE
|
|
64
|
+
delete(kv.key)
|
|
65
|
+
after_change&.call(type, kv.key, nil)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def setup
|
|
71
|
+
init_values
|
|
72
|
+
setup_watcher
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
reset
|
|
75
|
+
error(e)
|
|
76
|
+
error("Reset ShareDataWatcher #{key}")
|
|
77
|
+
sleep retry_interval
|
|
78
|
+
retry
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'etcdv3'
|
|
4
|
+
require 'multi_json'
|
|
5
|
+
require 'oj'
|
|
6
|
+
require 'dry-struct'
|
|
7
|
+
|
|
8
|
+
# Watch etcd value for IAS
|
|
9
|
+
class ShareDataWatcher
|
|
10
|
+
require_relative 'share-data-watcher/loggable'
|
|
11
|
+
|
|
12
|
+
Dir["#{File.dirname(__FILE__)}/share-data-watcher/**.rb"]
|
|
13
|
+
.each { |file| require_relative file }
|
|
14
|
+
|
|
15
|
+
include Loggable
|
|
16
|
+
include InstanceMethods
|
|
17
|
+
extend ClassInterface
|
|
18
|
+
|
|
19
|
+
DEFAULT_ENDPOINT = 'http://localhost:2379'
|
|
20
|
+
DEFAULT_HEALTHCHECK_INTERVAL = (30..90).freeze
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
attr_accessor :logger
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require File.expand_path('lib/share-data-watcher/version', __dir__)
|
|
4
|
+
# $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'share-data-watcher'
|
|
8
|
+
spec.version = ShareDataWatcher::VERSION
|
|
9
|
+
spec.authors = ['Cheok Meng Chan']
|
|
10
|
+
spec.email = ['cheokmeng.chan@gamesourcecloud.com']
|
|
11
|
+
spec.required_ruby_version = '>= 2.5'
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Game Operation Data Watcher'
|
|
14
|
+
spec.description = 'Game Operation Data Watcher For IAS'
|
|
15
|
+
spec.homepage = 'https://bitbucket.org/gamesource/go-data-watcher'
|
|
16
|
+
|
|
17
|
+
if spec.respond_to?(:metadata)
|
|
18
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
|
19
|
+
else
|
|
20
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
|
21
|
+
end
|
|
22
|
+
spec.require_paths = ['lib']
|
|
23
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
24
|
+
spec.add_dependency 'bundler', '>= 1.16.1'
|
|
25
|
+
spec.add_dependency 'dry-struct', '>= 1.2.0'
|
|
26
|
+
spec.add_dependency 'etcdv3', '~> 0.10.0'
|
|
27
|
+
spec.add_dependency 'multi_json', '>= 1.14.1'
|
|
28
|
+
spec.add_dependency 'oj', '>= 3.9.2'
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: share-data-watcher
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Cheok Meng Chan
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2024-06-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 1.16.1
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 1.16.1
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: dry-struct
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.2.0
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 1.2.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: etcdv3
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 0.10.0
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 0.10.0
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: multi_json
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 1.14.1
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 1.14.1
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: oj
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 3.9.2
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 3.9.2
|
|
83
|
+
description: Game Operation Data Watcher For IAS
|
|
84
|
+
email:
|
|
85
|
+
- cheokmeng.chan@gamesourcecloud.com
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- ".gitignore"
|
|
91
|
+
- ".rspec"
|
|
92
|
+
- ".rubocop.yml"
|
|
93
|
+
- ".ruby-version"
|
|
94
|
+
- CHANGELOG.md
|
|
95
|
+
- Gemfile
|
|
96
|
+
- Gemfile.lock
|
|
97
|
+
- README.md
|
|
98
|
+
- bitbucket-pipelines.yml
|
|
99
|
+
- config.reek
|
|
100
|
+
- docker-compose.yml
|
|
101
|
+
- example/sample
|
|
102
|
+
- example/sample.rb
|
|
103
|
+
- lib/share-data-watcher.rb
|
|
104
|
+
- lib/share-data-watcher/class_interface.rb
|
|
105
|
+
- lib/share-data-watcher/connection.rb
|
|
106
|
+
- lib/share-data-watcher/instance_methods.rb
|
|
107
|
+
- lib/share-data-watcher/loggable.rb
|
|
108
|
+
- lib/share-data-watcher/struct.rb
|
|
109
|
+
- lib/share-data-watcher/type.rb
|
|
110
|
+
- lib/share-data-watcher/type/base.rb
|
|
111
|
+
- lib/share-data-watcher/type/fetch.rb
|
|
112
|
+
- lib/share-data-watcher/type/watch.rb
|
|
113
|
+
- lib/share-data-watcher/version.rb
|
|
114
|
+
- share-data-watch.gemspec
|
|
115
|
+
homepage: https://bitbucket.org/gamesource/go-data-watcher
|
|
116
|
+
licenses: []
|
|
117
|
+
metadata:
|
|
118
|
+
allowed_push_host: https://rubygems.org
|
|
119
|
+
post_install_message:
|
|
120
|
+
rdoc_options: []
|
|
121
|
+
require_paths:
|
|
122
|
+
- lib
|
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
|
+
requirements:
|
|
125
|
+
- - ">="
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
version: '2.5'
|
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
|
+
requirements:
|
|
130
|
+
- - ">="
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
version: '0'
|
|
133
|
+
requirements: []
|
|
134
|
+
rubygems_version: 3.0.3.1
|
|
135
|
+
signing_key:
|
|
136
|
+
specification_version: 4
|
|
137
|
+
summary: Game Operation Data Watcher
|
|
138
|
+
test_files: []
|