tdlib-ruby 1.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4843f71309fcaf5799ce384c56d1df0a7d7d36131a5e162e3f3fd6d14dda68f7
4
- data.tar.gz: eca6f03dd7e531f6a80595c562954427b71528c84d321a77e8babac791af9692
3
+ metadata.gz: 851d0531bf3de68a43c0f72acf22aa549142fd1b866f915df4af2bf919408211
4
+ data.tar.gz: '0320249654babf8f873936c5e74e09a03b24d1689a6019f9d7edccfc72c5d96c'
5
5
  SHA512:
6
- metadata.gz: 3618eb07efe5081c116efb1ee9cc0cf39ccff51bd1fd1933e3c0e1690b27e1be12087faf91096e1636fc00a55f3edb9797b8c616e38418187fd2a3b2b3bdb157
7
- data.tar.gz: a87798327ce6f1995fbce7f2c5a216ed7c4baa01b65bb81cc109fbca7c757f8e20dce56b2e373a703e83e7ddaaf4af6e69cbc697e8fd845ef2d8b7c4c86c7ad3
6
+ metadata.gz: 3a8ba653e0261bde8fd13e237846f052520d7d9cca6fd808cbbf31937a706978b7b04273931192b5494e6e004c1f06c823ed65615f3a52d5cbbd8da7c5c5bbe2
7
+ data.tar.gz: 93c09fc96b0881d372b46f4f2970b803f99f2bc569657946a431687b46ae6d8b0f47aa6e7437c580ede440248b3cc3802986dc9676084439616348107a57cdcc
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,25 @@
1
+ ### 3.0.1 / 2020-06-29
2
+
3
+ * Fix client dispose
4
+
5
+ ### 3.0.0 / 2020-06-28
6
+
7
+ * Extract schema to separate gem
8
+
9
+ ### 2.1.0 / 2019-10-18
10
+
11
+ * Support tdlib 1.5
12
+ * Fix TD::Client#dispose race condition and client crash
13
+
14
+ ### 2.0.0 / 2019-02-08
15
+
16
+ * Generated types and client functions
17
+ * Async handlers
18
+ * Use ffi instead of fiddle
19
+ * Use Concurrent::Promises
20
+ * TD errors handling in promises
21
+ * Add use_file_database setting to config
22
+
1
23
  ### 1.0.0 / 2018-05-27
2
24
 
3
25
  * Return promises from TD::Client#broadcast
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,59 +63,52 @@ 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
 
103
- ## TD::Client#broadcast
104
-
105
- From version 1.0 TD::Client##broadcast returns [Concurrent::Promise](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html) object.
106
-
107
- ```ruby
108
- me = client.broadcast('@type' => 'getMe').then { |result| puts result }.rescue { |error| puts error }.value
109
- ```
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).
110
112
 
111
113
  ## Configuration
112
114
 
@@ -118,17 +120,18 @@ TD.configure do |config|
118
120
  config.client.api_id = 12345
119
121
  config.client.api_hash = 'your_api_hash'
120
122
  config.client.use_test_dc = true # default: false
121
- config.database_directory = 'path/to/db/dir' # default: "#{Dir.home}/.tdlib-ruby/db"
122
- 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
123
126
  config.client.use_chat_info_database = true # default: true
124
- config.use_secret_chats = true # default: true
125
- config.use_message_database = true # default: true
126
- config.system_language_code = 'ru' # default: 'en'
127
- config.device_model = 'Some device model' # default: 'Ruby TD client'
128
- config.system_version = '42' # default: 'Unknown'
129
- config.application_version = '1.0' # default: '1.0'
130
- config.enable_storage_optimizer = true # default: true
131
- 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
132
135
  end
133
136
  ```
134
137
 
@@ -153,6 +156,12 @@ TD::Client.new(database_directory: 'will override value from config',
153
156
  files_directory: 'will override value from config')
154
157
  ```
155
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
+
156
165
  ## License
157
166
 
158
167
  [MIT](https://github.com/centosadmin/tdlib-ruby/blob/master/LICENSE.txt)
@@ -160,3 +169,5 @@ TD::Client.new(database_directory: 'will override value from config',
160
169
  ## Authors
161
170
 
162
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,108 +1,91 @@
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
61
5
  include Concurrent
6
+ include TD::ClientMethods
62
7
 
63
8
  TIMEOUT = 20
64
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
65
18
  def initialize(td_client = TD::Api.client_create,
66
19
  update_manager = TD::UpdateManager.new(td_client),
67
- proxy: { '@type' => 'proxyEmpty' },
20
+ timeout: TIMEOUT,
68
21
  **extra_config)
69
22
  @td_client = td_client
23
+ @ready = false
24
+ @alive = true
70
25
  @update_manager = update_manager
26
+ @timeout = timeout
71
27
  @config = TD.config.client.to_h.merge(extra_config)
72
- @proxy = proxy
73
28
  @ready_condition_mutex = Mutex.new
74
29
  @ready_condition = ConditionVariable.new
75
- authorize
76
- @update_manager.run
30
+ end
31
+
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
77
54
  end
78
55
 
79
56
  # Sends asynchronous request to the TDLib client and returns Promise object
80
- # @see https://www.rubydoc.info/github/ruby-concurrency/concurrent-ruby/Concurrent/Promise)
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
81
60
  # @example
82
- # client.broadcast(some_query).then { |result| puts result }.rescue
61
+ # client.broadcast(some_query).then { |result| puts result }.rescue { |error| puts [error.code, error.message] }
83
62
  # @param [Hash] query
84
- # @param [Numeric] timeout
85
- # @return [Concurrent::Promise]
86
- def broadcast(query, timeout: TIMEOUT)
87
- Promise.execute do
63
+ # @return [Concurrent::Promises::Future]
64
+ def broadcast(query)
65
+ return dead_client_promise if dead?
66
+
67
+ Promises.future do
88
68
  condition = ConditionVariable.new
89
- extra = TD::Utils.generate_extra(query)
69
+ extra = SecureRandom.uuid
90
70
  result = nil
91
71
  mutex = Mutex.new
92
- handler = ->(update) do
93
- return unless update['@extra'] == extra
72
+
73
+ @update_manager << TD::UpdateHandler.new(TD::Types::Base, extra, disposable: true) do |update|
94
74
  mutex.synchronize do
95
75
  result = update
96
- @update_manager.remove_handler(handler)
97
76
  condition.signal
98
77
  end
99
78
  end
100
- @update_manager.add_handler(handler)
79
+
101
80
  query['@extra'] = extra
81
+
102
82
  mutex.synchronize do
103
- TD::Api.client_send(@td_client, query)
104
- condition.wait(mutex, timeout)
105
- raise TD::TimeoutError if result.nil?
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
106
89
  result
107
90
  end
108
91
  end
@@ -111,8 +94,8 @@ class TD::Client
111
94
  # Sends asynchronous request to the TDLib client and returns received update synchronously
112
95
  # @param [Hash] query
113
96
  # @return [Hash]
114
- def fetch(query, timeout: TIMEOUT)
115
- broadcast(query, timeout: timeout).value
97
+ def fetch(query)
98
+ broadcast(query).value!
116
99
  end
117
100
 
118
101
  alias broadcast_and_receive fetch
@@ -121,70 +104,91 @@ class TD::Client
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
140
121
  end
141
- @update_manager.add_handler(handler)
122
+
123
+ unless update_type < TD::Types::Base
124
+ raise ArgumentError.new("Wrong type specified (#{update_type}). Should be of kind TD::Types::Base")
125
+ end
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::Update::AuthorizationState) && update.authorization_state.is_a?(TD::Types::AuthorizationState::Closed)
171
+ @alive = false
172
+ @ready = false
173
+ sleep 0.001
174
+ TD::Api.client_destroy(@td_client)
175
+ throw(:client_closed)
176
+ end
171
177
 
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)
178
+ def send_to_td_client(query)
179
+ return unless alive?
180
+ TD::Api.client_send(@td_client, query)
181
+ end
182
+
183
+ def timeout_error
184
+ TD::Types::Error.new(code: 0, message: 'Timeout error')
185
+ end
186
+
187
+ def dead_client_promise
188
+ Promises.rejected_future(dead_client_error)
189
+ end
190
+
191
+ def dead_client_error
192
+ TD::Error.new(TD::Types::Error.new(code: 0, message: 'TD client is dead'))
189
193
  end
190
194
  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 = "1.0.0"
3
+ VERSION = "3.0.1"
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,32 +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
- it { is_expected.to satisfy { |result| result.state == :pending } }
39
- it { is_expected.to satisfy { |result| sleep 1; result.state == :fulfilled } }
40
- it { is_expected.to satisfy { |result| sleep 1; result.value['@type'] == 'textEntities' } }
50
+ it { is_expected.to satisfy(&:fulfilled?) }
51
+ it { is_expected.to satisfy { |result| result.value.is_a?(TD::Types::TextEntities) } }
41
52
  end
42
53
  end
43
54
 
44
- describe '#broadcast_and_receive' do
45
- subject { client.on_ready { client.broadcast_and_receive(payload) } }
55
+ describe '#fetch' do
56
+ subject { client.on_ready { client.fetch(payload) } }
46
57
 
47
- it { is_expected.to include('@type', 'entities') }
58
+ it { is_expected.to respond_to(:entities) }
48
59
 
49
60
  context 'when timeout reached' do
50
- 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) } }
51
64
 
52
- it { expect { subject }.to raise_error(TD::TimeoutError) }
65
+ it { expect { subject }.to raise_error(TD::Error) }
53
66
  end
54
67
  end
55
68
 
@@ -64,7 +77,7 @@ describe TD::Client do
64
77
  it 'runs block on update' do
65
78
  subject
66
79
  sleep 1
67
- expect(@result).to include('@type', 'entities')
80
+ expect(@result).to respond_to(:entities)
68
81
  end
69
82
  end
70
83
 
@@ -74,7 +87,7 @@ describe TD::Client do
74
87
  subject do
75
88
  client.on_ready do
76
89
  client.broadcast(payload) do
77
- client.broadcast_and_receive(payload)
90
+ client.fetch(payload)
78
91
  end
79
92
  sleep 1
80
93
  end
data/tdlib-ruby.gemspec CHANGED
@@ -14,26 +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'
33
- gem.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
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'
34
27
 
35
- gem.add_development_dependency 'bundler', '~> 1.10'
36
- gem.add_development_dependency 'rake', '12.3.1'
28
+ gem.add_development_dependency 'bundler', '~> 2.0'
29
+ gem.add_development_dependency 'rake', '~> 13.0'
37
30
  gem.add_development_dependency 'rspec', '~> 3.0'
38
31
  gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
39
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: 1.0.0
4
+ version: 3.0.1
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-27 00:00:00.000000000 Z
11
+ date: 2021-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable
@@ -16,16 +16,30 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.7'
19
+ version: '0.9'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.7'
26
+ version: '0.9'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffi
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
@@ -38,34 +52,48 @@ dependencies:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
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'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: bundler
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
73
  - - "~>"
46
74
  - !ruby/object:Gem::Version
47
- version: '1.10'
75
+ version: '2.0'
48
76
  type: :development
49
77
  prerelease: false
50
78
  version_requirements: !ruby/object:Gem::Requirement
51
79
  requirements:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
- version: '1.10'
82
+ version: '2.0'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rake
57
85
  requirement: !ruby/object:Gem::Requirement
58
86
  requirements:
59
- - - '='
87
+ - - "~>"
60
88
  - !ruby/object:Gem::Version
61
- version: 12.3.1
89
+ version: '13.0'
62
90
  type: :development
63
91
  prerelease: false
64
92
  version_requirements: !ruby/object:Gem::Requirement
65
93
  requirements:
66
- - - '='
94
+ - - "~>"
67
95
  - !ruby/object:Gem::Version
68
- version: 12.3.1
96
+ version: '13.0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: rspec
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -125,12 +153,14 @@ dependencies:
125
153
  description: Ruby bindings and client for TDlib
126
154
  email: ask@southbridge.io
127
155
  executables:
156
+ - build
128
157
  - console
129
158
  extensions: []
130
159
  extra_rdoc_files: []
131
160
  files:
132
161
  - ".document"
133
162
  - ".gitignore"
163
+ - ".gitmodules"
134
164
  - ".rspec"
135
165
  - ".travis.yml"
136
166
  - ".yardopts"
@@ -139,13 +169,14 @@ files:
139
169
  - LICENSE.txt
140
170
  - README.md
141
171
  - Rakefile
172
+ - bin/build
142
173
  - bin/console
143
174
  - lib/tdlib-ruby.rb
144
175
  - lib/tdlib/api.rb
145
176
  - lib/tdlib/client.rb
146
177
  - lib/tdlib/errors.rb
178
+ - lib/tdlib/update_handler.rb
147
179
  - lib/tdlib/update_manager.rb
148
- - lib/tdlib/utils.rb
149
180
  - lib/tdlib/version.rb
150
181
  - spec/integration/tdlib_spec.rb
151
182
  - spec/spec_helper.rb
@@ -155,7 +186,7 @@ homepage: https://github.com/centosadmin/tdlib-ruby
155
186
  licenses:
156
187
  - MIT
157
188
  metadata: {}
158
- post_install_message:
189
+ post_install_message:
159
190
  rdoc_options: []
160
191
  require_paths:
161
192
  - lib
@@ -170,9 +201,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
201
  - !ruby/object:Gem::Version
171
202
  version: '0'
172
203
  requirements: []
173
- rubyforge_project:
174
- rubygems_version: 2.7.6
175
- signing_key:
204
+ rubygems_version: 3.1.4
205
+ signing_key:
176
206
  specification_version: 4
177
207
  summary: Ruby bindings and client for TDlib
178
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