tdlib-ruby 1.0.0 → 3.0.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 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