tdlib-ruby 0.9.4 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 81084785487847d98d74b1a27857235c9094031a
4
- data.tar.gz: 665dc58fe58969ddc4f1cd1ccb23f4d03c543192
2
+ SHA256:
3
+ metadata.gz: 06b622d125c84301dcd3113ab5e028fb076922307dea8362688dff32941355fd
4
+ data.tar.gz: 828a68e3d8322d8cd642e4b978069d07caa5fc82658f08752ee906468a508292
5
5
  SHA512:
6
- metadata.gz: dcf903551729e52eba8c6772b8bde87e9ffb350026ca9200c3fb67196f1a41a462ea6d15bf95e2d6b525d57c76e6cb481ef61669c240a6782bc9d792dcaf5bf3
7
- data.tar.gz: 8b704d60ef867628888dfaa87c3197cb81a4aad17155367df9545bac180ca724be821101ddd6b2534bc0dfc847cd0032e44a0f7b577c252fab500b642a2155b4
6
+ metadata.gz: 67cbfcacfe6e972565006967a0ed354d93107ceaf0d521c4e08f919beedd7bfc797a862da9ce01d62d76302693f7220952474e99dc2edb7c5ed14e2b729c8575
7
+ data.tar.gz: 97afca505d4d80f87a43412570f3e982c65826db05d4ec0850d4a6d9300ffcf305e79468cf655082c1afadfa6c6d3f864b49c56cabc6d559b8f18d09598b38be
data/.gitignore CHANGED
@@ -6,3 +6,5 @@
6
6
  /vendor/cache/*.gem
7
7
  /.vscode/
8
8
  /.yardoc/
9
+ /.idea/
10
+ .DS_Store
data/.gitmodules ADDED
File without changes
data/.travis.yml CHANGED
@@ -1,7 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3.6
4
- - 2.5.0
3
+ - 2.6.7
4
+ - 2.7.3
5
+ - 3.0.1
6
+
5
7
  addons:
6
8
  apt:
7
9
  sources:
@@ -19,8 +21,9 @@ env:
19
21
  global:
20
22
  secure: DmAoRsr+dTPVNs486nBXzChjzCCVdoxfmSbvqMBBl0v8uuUIZD1rSfYKcEaL5pmE7vgYq3X+3Dk0gf4ef/p2xPVKqFKrkBiF/7t06WgQTH7003gHMLq3aC7R9+1xvqJDzpoc6UB59Y1fOJBe5YfqDuw1dT9a46tzY2uzoVpEpbN8M/hssaKf6Wuzvpz5yekFeXq2raPwi3aOqvkSG+ODoacVdYQgJ4Vn0//CI2HzWijkQCvdsefWQUKkbgtuLWRp3UNy8AEhVWzQnTcSM7oc+MwMsOI/90DZkTP5n2WJl/CFTM5b70VyrIciG92SvTAhhBo/p7t3QBJa6kJMPXAHh5q+3wqVQA411+CoVF48bO5rKjKY3Ply49uqAzVJRh+Tkhf5uC1pHiZ6QKGxu1Czde+mKItBpaDmJFmQi0CSdv2WYXLnJWOQIO6vc2P3liwpMDRDyaGWOQtUYS+gHAlRbD4NPycIkGjkcjmLMqSEEO1TdCpM+CYwTvNTkRS+9HrWXZNdEfWORDYHgYohoIP6kY6XWSgunUb6F6pVxLoPWJNEBEVuIMZeOa/s9oklxBzD5XXIzBx4QsPYanvfxoN1uOcXlBXbOGqwiqb+urokNDw+BzWhbA+xY03U2+yO0Ujh3HQDyMDtrXEQfaPC0SxpEvIINVYwznG4sMKvbOaCVSo=
21
23
  before_install:
24
+ - gem install -v 2.1.2 bundler
22
25
  - set -e
23
- - git clone https://github.com/tdlib/td
26
+ - git clone https://github.com/tdlib/td.git
24
27
  - cd td
25
28
  - mkdir -p build
26
29
  - cd build
data/ChangeLog.md CHANGED
@@ -1,3 +1,22 @@
1
+ ### 2.1.0 / 2019-10-18
2
+
3
+ * Support tdlib 1.5
4
+ * Fix TD::Client#dispose race condition and client crash
5
+
6
+ ### 2.0.0 / 2019-02-08
7
+
8
+ * Generated types and client functions
9
+ * Async handlers
10
+ * Use ffi instead of fiddle
11
+ * Use Concurrent::Promises
12
+ * TD errors handling in promises
13
+ * Add use_file_database setting to config
14
+
15
+ ### 1.0.0 / 2018-05-27
16
+
17
+ * Return promises from TD::Client#broadcast
18
+ * Add #fetch as alias to #broadcast_and_receive
19
+
1
20
  ### 0.9.4 / 2018-05-16
2
21
 
3
22
  * Fix recursive locking in nested handlers
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tdlib-ruby
2
2
 
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/9362ca2682b7edbae205/maintainability)](https://codeclimate.com/github/centosadmin/tdlib-ruby/maintainability) [![Build Status](https://travis-ci.org/centosadmin/tdlib-ruby.svg?branch=master)](https://travis-ci.org/centosadmin/tdlib-ruby)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/9362ca2682b7edbae205/maintainability)](https://codeclimate.com/github/centosadmin/tdlib-ruby/maintainability) [![Build Status](https://travis-ci.org/southbridgeio/tdlib-ruby.svg?branch=master)](https://travis-ci.org/centosadmin/tdlib-ruby)
4
4
 
5
5
  ## Description
6
6
 
@@ -8,7 +8,7 @@ Ruby bindings and client for TDLib (Telegram database library).
8
8
 
9
9
  ## Requirements
10
10
 
11
- * Ruby 2.3+
11
+ * Ruby 2.4+
12
12
  * Compiled [tdlib](https://github.com/tdlib/td)
13
13
 
14
14
  We have precompiled versions for CentOS 6 & 7 in our repositories:
@@ -23,6 +23,15 @@ http://rpms.southbridge.ru/rhel7/stable/SRPMS/
23
23
 
24
24
  http://rpms.southbridge.ru/rhel6/stable/SRPMS/
25
25
 
26
+ ## Compatibility table
27
+
28
+ | Gem Version | | tdlib version |
29
+ |:-------------:|:-:| :-----------: |
30
+ | 1.x | → | 1.0 - 1.2 |
31
+ | 2.0 | → | 1.3 |
32
+ | 2.1 | → | 1.5 |
33
+ | 2.2 | → | 1.6 |
34
+
26
35
  ## Install
27
36
 
28
37
  Add to your gemfile:
@@ -35,7 +44,7 @@ and run *bundle install*.
35
44
 
36
45
  Or just run *gem install tdlib-ruby*
37
46
 
38
- ## Basic example
47
+ ## Basic authentication example
39
48
 
40
49
  ```ruby
41
50
  require 'tdlib-ruby'
@@ -54,52 +63,53 @@ client = TD::Client.new
54
63
  begin
55
64
  state = nil
56
65
 
57
- client.on('updateAuthorizationState') do |update|
58
- next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitPhoneNumber'
59
- state = :wait_phone
60
- end
61
-
62
- client.on('updateAuthorizationState') do |update|
63
- next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitCode'
64
- state = :wait_code
65
- end
66
-
67
- client.on('updateAuthorizationState') do |update|
68
- next unless update.dig('authorization_state', '@type') == 'authorizationStateReady'
69
- state = :ready
66
+ client.on(TD::Types::Update::AuthorizationState) do |update|
67
+ state = case update.authorization_state
68
+ when TD::Types::AuthorizationState::WaitPhoneNumber
69
+ :wait_phone_number
70
+ when TD::Types::AuthorizationState::WaitCode
71
+ :wait_code
72
+ when TD::Types::AuthorizationState::WaitPassword
73
+ :wait_password
74
+ when TD::Types::AuthorizationState::Ready
75
+ :ready
76
+ else
77
+ nil
78
+ end
70
79
  end
80
+
81
+ client.connect
71
82
 
72
83
  loop do
73
84
  case state
74
- when :wait_phone
75
- p 'Please, enter your phone number:'
85
+ when :wait_phone_number
86
+ puts 'Please, enter your phone number:'
76
87
  phone = STDIN.gets.strip
77
- params = {
78
- '@type' => 'setAuthenticationPhoneNumber',
79
- 'phone_number' => phone
80
- }
81
- client.broadcast_and_receive(params)
88
+ client.set_authentication_phone_number(phone, nil).wait
82
89
  when :wait_code
83
- p 'Please, enter code from SMS:'
90
+ puts 'Please, enter code from SMS:'
84
91
  code = STDIN.gets.strip
85
- params = {
86
- '@type' => 'checkAuthenticationCode',
87
- 'code' => code
88
- }
89
- client.broadcast_and_receive(params)
92
+ client.check_authentication_code(code).wait
93
+ when :wait_password
94
+ puts 'Please, enter 2FA password:'
95
+ password = STDIN.gets.strip
96
+ client.check_authentication_password(password).wait
90
97
  when :ready
91
- @me = client.broadcast_and_receive('@type' => 'getMe')
98
+ client.get_me.then { |user| @me = user }.rescue { |err| puts "error: #{err}" }.wait
92
99
  break
93
100
  end
101
+ sleep 0.1
94
102
  end
95
103
 
96
104
  ensure
97
- client.close
105
+ client.dispose
98
106
  end
99
107
 
100
108
  p @me
101
109
  ```
102
110
 
111
+ Client methods are being executed asynchronously and return Concurrent::Promises::Future (see: https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md).
112
+
103
113
  ## Configuration
104
114
 
105
115
  ```ruby
@@ -110,17 +120,18 @@ TD.configure do |config|
110
120
  config.client.api_id = 12345
111
121
  config.client.api_hash = 'your_api_hash'
112
122
  config.client.use_test_dc = true # default: false
113
- config.database_directory = 'path/to/db/dir' # default: "#{Dir.home}/.tdlib-ruby/db"
114
- config.files_directory = 'path/to/files/dir' # default: "#{Dir.home}/.tdlib-ruby/files"
123
+ config.client.database_directory = 'path/to/db/dir' # default: "#{Dir.home}/.tdlib-ruby/db"
124
+ config.client.files_directory = 'path/to/files/dir' # default: "#{Dir.home}/.tdlib-ruby/files"
125
+ config.client.use_file_database = true # default: true
115
126
  config.client.use_chat_info_database = true # default: true
116
- config.use_secret_chats = true # default: true
117
- config.use_message_database = true # default: true
118
- config.system_language_code = 'ru' # default: 'en'
119
- config.device_model = 'Some device model' # default: 'Ruby TD client'
120
- config.system_version = '42' # default: 'Unknown'
121
- config.application_version = '1.0' # default: '1.0'
122
- config.enable_storage_optimizer = true # default: true
123
- config.ignore_file_names = true # default: false
127
+ config.client.use_secret_chats = true # default: true
128
+ config.client.use_message_database = true # default: true
129
+ config.client.system_language_code = 'ru' # default: 'en'
130
+ config.client.device_model = 'Some device model' # default: 'Ruby TD client'
131
+ config.client.system_version = '42' # default: 'Unknown'
132
+ config.client.application_version = '1.0' # default: '1.0'
133
+ config.client.enable_storage_optimizer = true # default: true
134
+ config.client.ignore_file_names = true # default: false
124
135
  end
125
136
  ```
126
137
 
@@ -145,6 +156,12 @@ TD::Client.new(database_directory: 'will override value from config',
145
156
  files_directory: 'will override value from config')
146
157
  ```
147
158
 
159
+ If the tdlib schema changes, then `./bin/parse` can be run to
160
+ synchronize the Ruby types with the new schema. Please look through
161
+ `lib/tdlib/client_methods.rb` carefully, especially the set_password
162
+ method!
163
+
164
+
148
165
  ## License
149
166
 
150
167
  [MIT](https://github.com/centosadmin/tdlib-ruby/blob/master/LICENSE.txt)
@@ -152,3 +169,5 @@ TD::Client.new(database_directory: 'will override value from config',
152
169
  ## Authors
153
170
 
154
171
  The gem is designed by [Southbridge](https://southbridge.io)
172
+
173
+ Typeization made by [Yuri Mikhaylov](https://github.com/yurijmi)
data/bin/build ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'etc'
4
+
5
+ `
6
+ set -e
7
+ cd td
8
+ mkdir -p build
9
+ cd build
10
+ cmake -DCMAKE_BUILD_TYPE=Release #{'-DOPENSSL_ROOT_DIR=/opt/homebrew/Cellar/openssl@1.1/1.1.1k/' if RbConfig::CONFIG['host_os'] =~ /darwin|mac os/} -DCMAKE_INSTALL_PREFIX:PATH=../tdlib ..
11
+ cmake --build . --target install -j #{Etc.nprocessors}
12
+ `
data/lib/tdlib-ruby.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'tdlib/version'
2
2
  require 'dry/configurable'
3
+ require 'concurrent-ruby'
3
4
 
4
5
  module TD
5
6
  extend Dry::Configurable
@@ -15,11 +16,12 @@ module TD
15
16
  setting :encryption_key
16
17
 
17
18
  setting :client do
18
- setting :api_id
19
+ setting :api_id, &:to_i
19
20
  setting :api_hash
20
21
  setting :use_test_dc, false
21
22
  setting :database_directory, "#{Dir.home}/.tdlib-ruby/db"
22
23
  setting :files_directory, "#{Dir.home}/.tdlib-ruby/data"
24
+ setting :use_file_database, true
23
25
  setting :use_chat_info_database, true
24
26
  setting :use_secret_chats, true
25
27
  setting :use_message_database, true
@@ -32,8 +34,9 @@ module TD
32
34
  end
33
35
  end
34
36
 
37
+ require 'tdlib-schema'
35
38
  require 'tdlib/errors'
36
39
  require 'tdlib/api'
37
- require 'tdlib/utils'
38
40
  require 'tdlib/client'
41
+ require 'tdlib/update_handler'
39
42
  require 'tdlib/update_manager'
data/lib/tdlib/api.rb CHANGED
@@ -1,5 +1,5 @@
1
- require 'fiddle/import'
2
1
  require 'json'
2
+ require 'ffi'
3
3
 
4
4
  module TD::Api
5
5
  module_function
@@ -18,7 +18,7 @@ module TD::Api
18
18
 
19
19
  def client_receive(client, timeout)
20
20
  update = Dl.td_json_client_receive(client, timeout)
21
- update.null? ? nil : JSON.parse(update.to_s)
21
+ JSON.parse(update) if update
22
22
  end
23
23
 
24
24
  def client_destroy(client)
@@ -34,7 +34,7 @@ module TD::Api
34
34
  end
35
35
 
36
36
  module Dl
37
- extend Fiddle::Importer
37
+ extend FFI::Library
38
38
 
39
39
  @mutex = Mutex.new
40
40
 
@@ -42,16 +42,18 @@ module TD::Api
42
42
 
43
43
  def method_missing(method_name, *args)
44
44
  @mutex.synchronize do
45
- return if respond_to?(method_name)
46
- dlload(find_lib)
45
+ return public_send(method_name, *args) if respond_to?(method_name)
47
46
 
48
- extern 'void* td_json_client_create()'
49
- extern 'void* td_json_client_send(void*, char*)'
50
- extern 'char* td_json_client_receive(void*, double)'
51
- extern 'char* td_json_client_execute(void*, char*)'
52
- extern 'void td_set_log_verbosity_level(int)'
53
- extern 'void td_json_client_destroy(void*)'
54
- extern 'void td_set_log_file_path(char*)'
47
+ find_lib
48
+
49
+ attach_function :td_json_client_create, [], :pointer
50
+ attach_function :td_json_client_receive, [:pointer, :double], :string, blocking: true
51
+ attach_function :td_json_client_send, [:pointer, :string], :pointer, blocking: true
52
+ attach_function :td_json_client_execute, [:pointer, :string], :string, blocking: true
53
+ attach_function :td_json_client_destroy, [:pointer], :void
54
+ attach_function :td_set_log_file_path, [:string], :int
55
+ attach_function :td_set_log_max_file_size, [:long_long], :void
56
+ attach_function :td_set_log_verbosity_level, [:int], :void
55
57
 
56
58
  undef method_missing
57
59
  public_send(method_name, *args)
@@ -66,9 +68,12 @@ module TD::Api
66
68
  elsif defined?(Rails) && File.exist?(Rails.root.join('vendor', file_name))
67
69
  Rails.root.join('vendor')
68
70
  end
69
- return `ldconfig -p | grep libtdjson`[/=> (.*?)\n/m, 1] if os == :linux && lib_path.nil?
70
- raise TD::MissingLibPathError unless lib_path
71
- File.join(lib_path, file_name)
71
+ full_path = File.join(lib_path.to_s, file_name)
72
+ ffi_lib full_path
73
+ full_path
74
+ rescue LoadError
75
+ ffi_lib 'tdjson'
76
+ ffi_libraries.first.name
72
77
  end
73
78
 
74
79
  def lib_extension
data/lib/tdlib/client.rb CHANGED
@@ -1,190 +1,193 @@
1
+ require 'securerandom'
2
+
1
3
  # Simple client for TDLib.
2
- # @example
3
- # TD.configure do |config|
4
- # config.lib_path = 'path_to_tdlibjson'
5
- # config.encryption_key = 'your_encryption_key'
6
- #
7
- # config.client.api_id = your_api_id
8
- # config.client.api_hash = 'your_api_hash'
9
- # end
10
- #
11
- # client = TD::Client.new
12
- #
13
- # begin
14
- # state = nil
15
- #
16
- # client.on('updateAuthorizationState') do |update|
17
- # next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitPhoneNumber'
18
- # state = :wait_phone
19
- # end
20
- #
21
- # client.on('updateAuthorizationState') do |update|
22
- # next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitCode'
23
- # state = :wait_code
24
- # end
25
- #
26
- # client.on('updateAuthorizationState') do |update|
27
- # next unless update.dig('authorization_state', '@type') == 'authorizationStateReady'
28
- # state = :ready
29
- # end
30
- #
31
- # loop do
32
- # case state
33
- # when :wait_phone
34
- # p 'Please, enter your phone number:'
35
- # phone = STDIN.gets.strip
36
- # params = {
37
- # '@type' => 'setAuthenticationPhoneNumber',
38
- # 'phone_number' => phone
39
- # }
40
- # client.broadcast_and_receive(params)
41
- # when :wait_code
42
- # p 'Please, enter code from SMS:'
43
- # code = STDIN.gets.strip
44
- # params = {
45
- # '@type' => 'checkAuthenticationCode',
46
- # 'code' => code
47
- # }
48
- # client.broadcast_and_receive(params)
49
- # when :ready
50
- # @me = client.broadcast_and_receive('@type' => 'getMe')
51
- # break
52
- # end
53
- # end
54
- #
55
- # ensure
56
- # client.close
57
- # end
58
- #
59
- # p @me
60
4
  class TD::Client
5
+ include Concurrent
6
+ include TD::ClientMethods
7
+
61
8
  TIMEOUT = 20
62
9
 
10
+ def self.ready(*args)
11
+ new(*args).connect
12
+ end
13
+
14
+ # @param [FFI::Pointer] td_client
15
+ # @param [TD::UpdateManager] update_manager
16
+ # @param [Numeric] timeout
17
+ # @param [Hash] extra_config optional configuration hash that will be merged into tdlib client configuration
63
18
  def initialize(td_client = TD::Api.client_create,
64
19
  update_manager = TD::UpdateManager.new(td_client),
65
- proxy: { '@type' => 'proxyEmpty' },
20
+ timeout: TIMEOUT,
66
21
  **extra_config)
67
22
  @td_client = td_client
23
+ @ready = false
24
+ @alive = true
68
25
  @update_manager = update_manager
26
+ @timeout = timeout
69
27
  @config = TD.config.client.to_h.merge(extra_config)
70
- @proxy = proxy
71
28
  @ready_condition_mutex = Mutex.new
72
29
  @ready_condition = ConditionVariable.new
73
- authorize
74
- @update_manager.run
75
30
  end
76
31
 
77
- # Sends asynchronous request to the TDLib client
32
+ # Adds initial authorization state handler and runs update manager
33
+ # Returns future that will be fulfilled when client is ready
34
+ # @return [Concurrent::Promises::Future]
35
+ def connect
36
+ on TD::Types::Update::AuthorizationState do |update|
37
+ case update.authorization_state
38
+ when TD::Types::AuthorizationState::WaitTdlibParameters
39
+ set_tdlib_parameters(parameters: TD::Types::TdlibParameters.new(**@config))
40
+ when TD::Types::AuthorizationState::WaitEncryptionKey
41
+ check_database_encryption_key(encryption_key: TD.config.encryption_key).then do
42
+ @ready_condition_mutex.synchronize do
43
+ @ready = true
44
+ @ready_condition.broadcast
45
+ end
46
+ end
47
+ else
48
+ # do nothing
49
+ end
50
+ end
51
+
52
+ @update_manager.run(callback: method(:handle_update))
53
+ ready
54
+ end
55
+
56
+ # Sends asynchronous request to the TDLib client and returns Promise object
57
+ # @see TD::ClientMethods List of available queries as methods
58
+ # @see https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md
59
+ # Concurrent::Promise documentation
60
+ # @example
61
+ # client.broadcast(some_query).then { |result| puts result }.rescue { |error| puts [error.code, error.message] }
78
62
  # @param [Hash] query
79
- # @yield [update] yields update to the block as soon as it's received
63
+ # @return [Concurrent::Promises::Future]
80
64
  def broadcast(query)
81
- if block_given?
82
- extra = TD::Utils.generate_extra(query)
83
- handler = ->(update) do
84
- return unless update['@extra'] == extra
85
- yield update
86
- @update_manager.remove_handler(handler)
65
+ return dead_client_promise if dead?
66
+
67
+ Promises.future do
68
+ condition = ConditionVariable.new
69
+ extra = SecureRandom.uuid
70
+ result = nil
71
+ mutex = Mutex.new
72
+
73
+ @update_manager << TD::UpdateHandler.new(TD::Types::Base, extra, disposable: true) do |update|
74
+ mutex.synchronize do
75
+ result = update
76
+ condition.signal
77
+ end
87
78
  end
88
- @update_manager.add_handler(handler)
79
+
89
80
  query['@extra'] = extra
81
+
82
+ mutex.synchronize do
83
+ send_to_td_client(query)
84
+ condition.wait(mutex, @timeout)
85
+ error = nil
86
+ error = result if result.is_a?(TD::Types::Error)
87
+ error = timeout_error if result.nil?
88
+ raise TD::Error.new(error) if error
89
+ result
90
+ end
90
91
  end
91
- TD::Api.client_send(@td_client, query)
92
92
  end
93
93
 
94
94
  # Sends asynchronous request to the TDLib client and returns received update synchronously
95
95
  # @param [Hash] query
96
96
  # @return [Hash]
97
- def broadcast_and_receive(query, timeout: TIMEOUT)
98
- condition = ConditionVariable.new
99
- extra = TD::Utils.generate_extra(query)
100
- result = nil
101
- mutex = Mutex.new
102
- handler = ->(update) do
103
- return unless update['@extra'] == extra
104
- mutex.synchronize do
105
- result = update
106
- @update_manager.remove_handler(handler)
107
- condition.signal
108
- end
109
- end
110
- @update_manager.add_handler(handler)
111
- query['@extra'] = extra
112
- mutex.synchronize do
113
- TD::Api.client_send(@td_client, query)
114
- condition.wait(mutex, timeout)
115
- raise TD::TimeoutError if result.nil?
116
- result
117
- end
97
+ def fetch(query)
98
+ broadcast(query).value!
118
99
  end
119
100
 
101
+ alias broadcast_and_receive fetch
102
+
120
103
  # Synchronously executes TDLib request
121
104
  # Only a few requests can be executed synchronously
122
105
  # @param [Hash] query
123
106
  def execute(query)
107
+ return dead_client_error if dead?
124
108
  TD::Api.client_execute(@td_client, query)
125
109
  end
126
110
 
127
- # Returns current authorization state (it's offline request)
128
- # @return [Hash]
129
- def authorization_state
130
- broadcast_and_receive('@type' => 'getAuthorizationState')
131
- end
132
-
133
111
  # Binds passed block as a handler for updates with type of *update_type*
134
- # @param [String] update_type
112
+ # @param [String, Class] update_type
135
113
  # @yield [update] yields update to the block as soon as it's received
136
- def on(update_type, &_)
137
- handler = ->(update) do
138
- return unless update['@type'] == update_type
139
- yield update
114
+ def on(update_type, &action)
115
+ if update_type.is_a?(String)
116
+ if (type_const = TD::Types::LOOKUP_TABLE[update_type])
117
+ update_type = TD::Types.const_get("TD::Types::#{type_const}")
118
+ else
119
+ raise ArgumentError.new("Can't find class for #{update_type}")
120
+ end
121
+ end
122
+
123
+ unless update_type < TD::Types::Base
124
+ raise ArgumentError.new("Wrong type specified (#{update_type}). Should be of kind TD::Types::Base")
140
125
  end
141
- @update_manager.add_handler(handler)
126
+
127
+ @update_manager << TD::UpdateHandler.new(update_type, &action)
142
128
  end
143
129
 
144
- def on_ready(timeout: TIMEOUT, &_)
145
- @ready_condition_mutex.synchronize do
146
- return(yield self) if @ready || (@ready_condition.wait(@ready_condition_mutex, timeout) && @ready)
147
- raise TD::TimeoutError
130
+ # returns future that will be fulfilled when client is ready
131
+ # @return [Concurrent::Promises::Future]
132
+ def ready
133
+ return dead_client_promise if dead?
134
+ return Promises.fulfilled_future(self) if ready?
135
+
136
+ Promises.future do
137
+ @ready_condition_mutex.synchronize do
138
+ next self if @ready || (@ready_condition.wait(@ready_condition_mutex, @timeout) && @ready)
139
+ raise TD::Error.new(timeout_error)
140
+ end
148
141
  end
149
142
  end
150
143
 
144
+ # @deprecated
145
+ def on_ready(&action)
146
+ ready.then(&action).value!
147
+ end
148
+
151
149
  # Stops update manager and destroys TDLib client
152
- def close
153
- @update_manager.stop
154
- TD::Api.client_destroy(@td_client)
150
+ def dispose
151
+ return if dead?
152
+ close.then { get_authorization_state }
153
+ end
154
+
155
+ def alive?
156
+ @alive
157
+ end
158
+
159
+ def dead?
160
+ !alive?
161
+ end
162
+
163
+ def ready?
164
+ @ready
155
165
  end
156
166
 
157
167
  private
158
168
 
159
- def authorize
160
- tdlib_params_query = {
161
- '@type' => 'setTdlibParameters',
162
- parameters: { '@type' => 'tdlibParameters', **@config }
163
- }
164
- encryption_key_query = {
165
- '@type' => 'checkDatabaseEncryptionKey',
166
- }
167
-
168
- if TD.config.encryption_key
169
- encryption_key_query['encryption_key'] = TD.config.encryption_key
170
- end
169
+ def handle_update(update)
170
+ return unless update.is_a?(TD::Types::AuthorizationState::Closed)
171
+ @alive = false
172
+ @ready = false
173
+ TD::Api.client_destroy(@td_client)
174
+ throw(:client_closed)
175
+ end
171
176
 
172
- handler = ->(update) do
173
- return unless update['@type'] == 'updateAuthorizationState'
174
- case update.dig('authorization_state', '@type')
175
- when 'authorizationStateWaitTdlibParameters'
176
- broadcast(tdlib_params_query)
177
- when 'authorizationStateWaitEncryptionKey'
178
- broadcast(encryption_key_query)
179
- else
180
- broadcast('@type' => 'setProxy', 'proxy' => @proxy)
181
- @update_manager.remove_handler(handler)
182
- @ready_condition_mutex.synchronize do
183
- @ready = true
184
- @ready_condition.broadcast
185
- end
186
- end
187
- end
188
- @update_manager.add_handler(handler)
177
+ def send_to_td_client(query)
178
+ return unless alive?
179
+ TD::Api.client_send(@td_client, query)
180
+ end
181
+
182
+ def timeout_error
183
+ TD::Types::Error.new(code: 0, message: 'Timeout error')
184
+ end
185
+
186
+ def dead_client_promise
187
+ Promises.rejected_future(dead_client_error)
188
+ end
189
+
190
+ def dead_client_error
191
+ TD::Error.new(TD::Types::Error.new(code: 0, message: 'TD client is dead'))
189
192
  end
190
193
  end
data/lib/tdlib/errors.rb CHANGED
@@ -1,8 +1,32 @@
1
- class TD::MissingLibPathError < StandardError
2
- def initialize(message = 'Please, configure the path to tdlibjson library')
3
- super
1
+ module TD
2
+ class MissingLibPathError < StandardError
3
+ def initialize(message = 'Please, configure the path to tdlibjson library')
4
+ super
5
+ end
4
6
  end
5
- end
6
7
 
7
- class TD::TimeoutError < Timeout::Error
8
+ # Proxy class that is used in failed promises to represent TDlib errors
9
+ class Error < StandardError
10
+ def initialize(td_error)
11
+ @td_error = td_error
12
+ end
13
+
14
+ def method_missing(method, *args)
15
+ @td_error.public_send(method, *args)
16
+ end
17
+
18
+ def respond_to_missing?(*args)
19
+ @td_error.respond_to?(*args)
20
+ end
21
+
22
+ def to_s
23
+ @td_error.inspect
24
+ end
25
+
26
+ def message
27
+ @td_error.message
28
+ end
29
+
30
+ alias inspect to_s
31
+ end
8
32
  end
@@ -0,0 +1,39 @@
1
+ class TD::UpdateHandler
2
+ include Concurrent::Async
3
+
4
+ attr_reader :update_type, :extra
5
+
6
+ def initialize(update_type, extra = nil, disposable: false, &action)
7
+ super()
8
+
9
+ @action = action
10
+ @update_type = update_type
11
+ @extra = extra
12
+ @disposable = disposable
13
+ end
14
+
15
+ def run(update)
16
+ action.call(update)
17
+ rescue StandardError => e
18
+ warn("Uncaught exception in handler #{self}: #{e.message}")
19
+ raise
20
+ end
21
+
22
+ def match?(update, extra = nil)
23
+ update.is_a?(update_type) && (self.extra.nil? || self.extra == extra)
24
+ end
25
+
26
+ def disposable?
27
+ disposable
28
+ end
29
+
30
+ def to_s
31
+ "TD::UpdateHandler (#{update_type}#{": #{extra}" if extra})#{' disposable' if disposable?}"
32
+ end
33
+
34
+ alias inspect to_s
35
+
36
+ private
37
+
38
+ attr_reader :action, :disposable
39
+ end
@@ -1,11 +1,9 @@
1
1
  class TD::UpdateManager
2
2
  TIMEOUT = 30
3
3
 
4
- attr_reader :handlers
5
-
6
4
  def initialize(td_client)
7
5
  @td_client = td_client
8
- @handlers = []
6
+ @handlers = Concurrent::Array.new
9
7
  @mutex = Mutex.new
10
8
  end
11
9
 
@@ -13,31 +11,36 @@ class TD::UpdateManager
13
11
  @mutex.synchronize { @handlers << handler }
14
12
  end
15
13
 
16
- def remove_handler(handler)
14
+ alias << add_handler
15
+
16
+ def run(callback: nil)
17
17
  Thread.start do
18
- @mutex.synchronize { @handlers.delete(handler) }
18
+ catch(:client_closed) { loop { handle_update(callback: callback) } }
19
+ @mutex.synchronize { @handlers = [] }
19
20
  end
20
21
  end
21
22
 
22
- def run
23
- @update_loop_thread = Thread.start do
24
- loop { stopped? ? break : handle_update }
25
- end
26
- end
23
+ private
27
24
 
28
- def stop
29
- @stopped = true
30
- end
25
+ attr_reader :handlers
31
26
 
32
- def stopped?
33
- !!@stopped
34
- end
27
+ def handle_update(callback: nil)
28
+ update = TD::Api.client_receive(@td_client, TIMEOUT)
35
29
 
36
- private
30
+ unless update.nil?
31
+ extra = update.delete('@extra')
32
+ update = TD::Types.wrap(update)
33
+ callback&.call(update)
37
34
 
38
- def handle_update
39
- update = TD::Api.client_receive(@td_client, TIMEOUT)
40
- @mutex.synchronize { handlers = @handlers.dup }
41
- handlers.each { |h| h.call(update) } unless update.nil?
35
+ match_handlers!(update, extra).each { |h| h.async.run(update) }
36
+ end
37
+ end
38
+
39
+ def match_handlers!(update, extra)
40
+ @mutex.synchronize do
41
+ matched_handlers = handlers.select { |h| h.match?(update, extra) }
42
+ matched_handlers.each { |h| handlers.delete(h) if h.disposable? }
43
+ matched_handlers
44
+ end
42
45
  end
43
46
  end
data/lib/tdlib/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module TD
2
2
  # tdlib-ruby version
3
- VERSION = "0.9.4"
3
+ VERSION = "3.0.0"
4
4
  end
@@ -2,8 +2,10 @@ require 'spec_helper'
2
2
  require 'tdlib-ruby'
3
3
 
4
4
  describe TD::Client do
5
- let(:client) { TD::Client.new }
6
- let(:payload) { { '@type': 'getTextEntities', 'text': '@telegram' } }
5
+ let(:client) { TD::Client.new(timeout: timeout).tap(&:connect) }
6
+ let!(:payload) { { '@type' => 'getTextEntities', 'text' => text } }
7
+ let!(:text) { '@telegram' }
8
+ let(:timeout) { TD::Client::TIMEOUT }
7
9
 
8
10
  before do
9
11
  TD.configure do |config|
@@ -17,6 +19,14 @@ describe TD::Client do
17
19
  TD::Api.set_log_verbosity_level(1)
18
20
  end
19
21
 
22
+ around do |example|
23
+ begin
24
+ example.run
25
+ ensure
26
+ client.dispose
27
+ end
28
+ end
29
+
20
30
  describe '#on_ready' do
21
31
  subject { client.on_ready { [client, 'ready'] } }
22
32
 
@@ -24,39 +34,35 @@ describe TD::Client do
24
34
  it { is_expected.to include('ready') }
25
35
 
26
36
  context 'when timeout reached' do
27
- subject { client.on_ready(timeout: 0.0001) { [client, 'ready'] } }
37
+ let(:timeout) { 0.0001 }
28
38
 
29
- it { expect { subject }.to raise_error(TD::TimeoutError) }
39
+ subject { client.on_ready { [client, 'ready'] } }
40
+
41
+ it { expect { subject }.to raise_error(TD::Error) }
30
42
  end
31
43
  end
32
44
 
33
45
  describe '#broadcast' do
34
46
  context 'when no block given' do
35
- subject { client.on_ready { client.broadcast(payload) } }
47
+ subject { client.ready.then { client.get_text_entities(text: text) }.flat.wait }
36
48
 
37
49
  it { expect { subject }.not_to raise_error(Exception) }
38
- end
39
-
40
- context 'when block given' do
41
- subject { client.on_ready { client.broadcast(payload) { |update| @result = update } } }
42
-
43
- it 'runs block on update' do
44
- subject
45
- sleep 1
46
- expect(@result).to include('@type', 'entities')
47
- end
50
+ it { is_expected.to satisfy(&:fulfilled?) }
51
+ it { is_expected.to satisfy { |result| result.value.is_a?(TD::Types::TextEntities) } }
48
52
  end
49
53
  end
50
54
 
51
- describe '#broadcast_and_receive' do
52
- subject { client.on_ready { client.broadcast_and_receive(payload) } }
55
+ describe '#fetch' do
56
+ subject { client.on_ready { client.fetch(payload) } }
53
57
 
54
- it { is_expected.to include('@type', 'entities') }
58
+ it { is_expected.to respond_to(:entities) }
55
59
 
56
60
  context 'when timeout reached' do
57
- subject { client.on_ready(timeout: 0.0001) { client.broadcast_and_receive(payload) } }
61
+ let(:timeout) { 0.0001 }
62
+
63
+ subject { client.on_ready { client.fetch(payload) } }
58
64
 
59
- it { expect { subject }.to raise_error(TD::TimeoutError) }
65
+ it { expect { subject }.to raise_error(TD::Error) }
60
66
  end
61
67
  end
62
68
 
@@ -71,7 +77,7 @@ describe TD::Client do
71
77
  it 'runs block on update' do
72
78
  subject
73
79
  sleep 1
74
- expect(@result).to include('@type', 'entities')
80
+ expect(@result).to respond_to(:entities)
75
81
  end
76
82
  end
77
83
 
@@ -81,7 +87,7 @@ describe TD::Client do
81
87
  subject do
82
88
  client.on_ready do
83
89
  client.broadcast(payload) do
84
- client.broadcast_and_receive(payload)
90
+ client.fetch(payload)
85
91
  end
86
92
  sleep 1
87
93
  end
data/tdlib-ruby.gemspec CHANGED
@@ -14,25 +14,19 @@ Gem::Specification.new do |gem|
14
14
  gem.email = "ask@southbridge.io"
15
15
  gem.homepage = "https://github.com/centosadmin/tdlib-ruby"
16
16
 
17
- gem.files = `git ls-files`.split($/)
17
+ gem.files = `git ls-files`.split($/) - ['lib/tdlib/td_api_tl_parser.rb']
18
18
 
19
- `git submodule --quiet foreach --recursive pwd`.split($/).each do |submodule|
20
- submodule.sub!("#{Dir.pwd}/",'')
21
-
22
- Dir.chdir(submodule) do
23
- `git ls-files`.split($/).map do |subpath|
24
- gem.files << File.join(submodule,subpath)
25
- end
26
- end
27
- end
28
19
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
29
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
30
21
  gem.require_paths = ['lib']
31
22
 
32
- gem.add_runtime_dependency 'dry-configurable', '~> 0.7'
23
+ gem.add_runtime_dependency 'dry-configurable', '~> 0.9'
24
+ gem.add_runtime_dependency 'concurrent-ruby', '~> 1.1'
25
+ gem.add_runtime_dependency 'ffi', '~> 1.0'
26
+ gem.add_runtime_dependency 'tdlib-schema'
33
27
 
34
- gem.add_development_dependency 'bundler', '~> 1.10'
35
- gem.add_development_dependency 'rake', '12.3.1'
28
+ gem.add_development_dependency 'bundler', '~> 2.0'
29
+ gem.add_development_dependency 'rake', '~> 13.0'
36
30
  gem.add_development_dependency 'rspec', '~> 3.0'
37
31
  gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
38
32
  gem.add_development_dependency 'yard', '~> 0.9'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tdlib-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Southbridge
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-16 00:00:00.000000000 Z
11
+ date: 2021-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable
@@ -16,42 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.7'
19
+ version: '0.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: '0.7'
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffi
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tdlib-schema
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: bundler
29
71
  requirement: !ruby/object:Gem::Requirement
30
72
  requirements:
31
73
  - - "~>"
32
74
  - !ruby/object:Gem::Version
33
- version: '1.10'
75
+ version: '2.0'
34
76
  type: :development
35
77
  prerelease: false
36
78
  version_requirements: !ruby/object:Gem::Requirement
37
79
  requirements:
38
80
  - - "~>"
39
81
  - !ruby/object:Gem::Version
40
- version: '1.10'
82
+ version: '2.0'
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: rake
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
- - - '='
87
+ - - "~>"
46
88
  - !ruby/object:Gem::Version
47
- version: 12.3.1
89
+ version: '13.0'
48
90
  type: :development
49
91
  prerelease: false
50
92
  version_requirements: !ruby/object:Gem::Requirement
51
93
  requirements:
52
- - - '='
94
+ - - "~>"
53
95
  - !ruby/object:Gem::Version
54
- version: 12.3.1
96
+ version: '13.0'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: rspec
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -111,12 +153,14 @@ dependencies:
111
153
  description: Ruby bindings and client for TDlib
112
154
  email: ask@southbridge.io
113
155
  executables:
156
+ - build
114
157
  - console
115
158
  extensions: []
116
159
  extra_rdoc_files: []
117
160
  files:
118
161
  - ".document"
119
162
  - ".gitignore"
163
+ - ".gitmodules"
120
164
  - ".rspec"
121
165
  - ".travis.yml"
122
166
  - ".yardopts"
@@ -125,13 +169,14 @@ files:
125
169
  - LICENSE.txt
126
170
  - README.md
127
171
  - Rakefile
172
+ - bin/build
128
173
  - bin/console
129
174
  - lib/tdlib-ruby.rb
130
175
  - lib/tdlib/api.rb
131
176
  - lib/tdlib/client.rb
132
177
  - lib/tdlib/errors.rb
178
+ - lib/tdlib/update_handler.rb
133
179
  - lib/tdlib/update_manager.rb
134
- - lib/tdlib/utils.rb
135
180
  - lib/tdlib/version.rb
136
181
  - spec/integration/tdlib_spec.rb
137
182
  - spec/spec_helper.rb
@@ -141,7 +186,7 @@ homepage: https://github.com/centosadmin/tdlib-ruby
141
186
  licenses:
142
187
  - MIT
143
188
  metadata: {}
144
- post_install_message:
189
+ post_install_message:
145
190
  rdoc_options: []
146
191
  require_paths:
147
192
  - lib
@@ -156,9 +201,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
201
  - !ruby/object:Gem::Version
157
202
  version: '0'
158
203
  requirements: []
159
- rubyforge_project:
160
- rubygems_version: 2.6.14
161
- signing_key:
204
+ rubygems_version: 3.1.4
205
+ signing_key:
162
206
  specification_version: 4
163
207
  summary: Ruby bindings and client for TDlib
164
208
  test_files:
data/lib/tdlib/utils.rb DELETED
@@ -1,7 +0,0 @@
1
- module TD::Utils
2
- module_function
3
-
4
- def generate_extra(query)
5
- "#{query.hash}#{Time.now.to_f}"
6
- end
7
- end