baza.rb 0.0.8 → 0.0.10

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.
data/lib/baza-rb.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2024 Zerocracy
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  require 'base64'
24
7
  require 'elapsed'
@@ -26,8 +9,11 @@ require 'fileutils'
26
9
  require 'iri'
27
10
  require 'loog'
28
11
  require 'retries'
12
+ require 'stringio'
29
13
  require 'tago'
14
+ require 'tempfile'
30
15
  require 'typhoeus'
16
+ require 'zlib'
31
17
  require_relative 'baza-rb/version'
32
18
 
33
19
  # Interface to the API of zerocracy.com.
@@ -37,9 +23,18 @@ require_relative 'baza-rb/version'
37
23
  # results returned.
38
24
  #
39
25
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
40
- # Copyright:: Copyright (c) 2024 Yegor Bugayenko
26
+ # Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
41
27
  # License:: MIT
42
28
  class BazaRb
29
+ # When the server failed (503).
30
+ class ServerFailure < StandardError; end
31
+
32
+ # When request timeout.
33
+ class TimedOut < StandardError; end
34
+
35
+ # Unexpected response arrived from the server.
36
+ class BadResponse < StandardError; end
37
+
43
38
  # Ctor.
44
39
  #
45
40
  # @param [String] host Host name
@@ -78,7 +73,7 @@ class BazaRb
78
73
  'Content-Length' => data.size
79
74
  )
80
75
  unless meta.empty?
81
- hdrs = hdrs.merge('X-Zerocracy-Meta' => meta.map { |v| Base64.encode64(v).gsub("\n", '') }.join(' '))
76
+ hdrs = hdrs.merge('X-Zerocracy-Meta' => meta.map { |v| Base64.encode64(v).delete("\n") }.join(' '))
82
77
  end
83
78
  params = {
84
79
  connecttimeout: @timeout,
@@ -88,7 +83,7 @@ class BazaRb
88
83
  }
89
84
  elapsed(@loog) do
90
85
  ret =
91
- with_retries(max_tries: @retries) do
86
+ with_retries(max_tries: @retries, rescue: TimedOut) do
92
87
  checked(
93
88
  Typhoeus::Request.put(
94
89
  home.append('push').append(name).to_s,
@@ -109,7 +104,7 @@ class BazaRb
109
104
  def pull(id)
110
105
  raise 'The ID of the job is nil' if id.nil?
111
106
  raise 'The ID of the job must be a positive integer' unless id.positive?
112
- data = 0
107
+ data = ''
113
108
  elapsed(@loog) do
114
109
  Tempfile.open do |file|
115
110
  File.open(file, 'wb') do |f|
@@ -126,7 +121,7 @@ class BazaRb
126
121
  request.on_body do |chunk|
127
122
  f.write(chunk)
128
123
  end
129
- with_retries(max_tries: @retries) do
124
+ with_retries(max_tries: @retries, rescue: TimedOut) do
130
125
  request.run
131
126
  end
132
127
  checked(request.response)
@@ -148,7 +143,7 @@ class BazaRb
148
143
  finished = false
149
144
  elapsed(@loog) do
150
145
  ret =
151
- with_retries(max_tries: @retries) do
146
+ with_retries(max_tries: @retries, rescue: TimedOut) do
152
147
  checked(
153
148
  Typhoeus::Request.get(
154
149
  home.append('finished').append(id).to_s,
@@ -172,7 +167,7 @@ class BazaRb
172
167
  stdout = ''
173
168
  elapsed(@loog) do
174
169
  ret =
175
- with_retries(max_tries: @retries) do
170
+ with_retries(max_tries: @retries, rescue: TimedOut) do
176
171
  checked(
177
172
  Typhoeus::Request.get(
178
173
  home.append('stdout').append("#{id}.txt").to_s,
@@ -196,7 +191,7 @@ class BazaRb
196
191
  code = 0
197
192
  elapsed(@loog) do
198
193
  ret =
199
- with_retries(max_tries: @retries) do
194
+ with_retries(max_tries: @retries, rescue: TimedOut) do
200
195
  checked(
201
196
  Typhoeus::Request.get(
202
197
  home.append('exit').append("#{id}.txt").to_s,
@@ -217,10 +212,10 @@ class BazaRb
217
212
  def verified(id)
218
213
  raise 'The ID of the job is nil' if id.nil?
219
214
  raise 'The ID of the job must be a positive integer' unless id.positive?
220
- verdict = 0
215
+ verdict = ''
221
216
  elapsed(@loog) do
222
217
  ret =
223
- with_retries(max_tries: @retries) do
218
+ with_retries(max_tries: @retries, rescue: TimedOut) do
224
219
  checked(
225
220
  Typhoeus::Request.get(
226
221
  home.append('jobs').append(id).append('verified.txt').to_s,
@@ -243,16 +238,18 @@ class BazaRb
243
238
  raise 'The "name" of the job may not be empty' if name.empty?
244
239
  raise 'The "owner" of the lock is nil' if owner.nil?
245
240
  elapsed(@loog) do
246
- with_retries(max_tries: @retries) do
247
- checked(
248
- Typhoeus::Request.get(
249
- home.append('lock').append(name).add(owner:).to_s,
250
- headers:
251
- ),
252
- 302
253
- )
254
- end
255
- throw :"Job name '#{name}' locked at #{@host}"
241
+ ret =
242
+ with_retries(max_tries: @retries, rescue: TimedOut) do
243
+ checked(
244
+ Typhoeus::Request.get(
245
+ home.append('lock').append(name).add(owner:).to_s,
246
+ headers:
247
+ ),
248
+ [302, 409]
249
+ )
250
+ end
251
+ throw :"Job name '#{name}' locked at #{@host}" if ret.code == 302
252
+ raise "Failed to lock '#{name}' job at #{@host}, it's most probably already locked"
256
253
  end
257
254
  end
258
255
 
@@ -265,7 +262,7 @@ class BazaRb
265
262
  raise 'The "name" of the job may not be empty' if name.empty?
266
263
  raise 'The "owner" of the lock is nil' if owner.nil?
267
264
  elapsed(@loog) do
268
- with_retries(max_tries: @retries) do
265
+ with_retries(max_tries: @retries, rescue: TimedOut) do
269
266
  checked(
270
267
  Typhoeus::Request.get(
271
268
  home.append('unlock').append(name).add(owner:).to_s,
@@ -285,10 +282,10 @@ class BazaRb
285
282
  def recent(name)
286
283
  raise 'The "name" of the job is nil' if name.nil?
287
284
  raise 'The "name" of the job may not be empty' if name.empty?
288
- job = 0
285
+ job = nil
289
286
  elapsed(@loog) do
290
287
  ret =
291
- with_retries(max_tries: @retries) do
288
+ with_retries(max_tries: @retries, rescue: TimedOut) do
292
289
  checked(
293
290
  Typhoeus::Request.get(
294
291
  home.append('recent').append("#{name}.txt").to_s,
@@ -309,10 +306,10 @@ class BazaRb
309
306
  def name_exists?(name)
310
307
  raise 'The "name" of the job is nil' if name.nil?
311
308
  raise 'The "name" of the job may not be empty' if name.empty?
312
- exists = 0
309
+ exists = false
313
310
  elapsed(@loog) do
314
311
  ret =
315
- with_retries(max_tries: @retries) do
312
+ with_retries(max_tries: @retries, rescue: TimedOut) do
316
313
  checked(
317
314
  Typhoeus::Request.get(
318
315
  home.append('exists').append(name).to_s,
@@ -338,11 +335,12 @@ class BazaRb
338
335
  id = nil
339
336
  elapsed(@loog) do
340
337
  ret =
341
- with_retries(max_tries: @retries) do
338
+ with_retries(max_tries: @retries, rescue: TimedOut) do
342
339
  checked(
343
340
  Typhoeus::Request.post(
344
341
  home.append('durables').append('place').to_s,
345
342
  body: {
343
+ '_csrf' => csrf,
346
344
  'jname' => jname,
347
345
  'file' => File.basename(file),
348
346
  'zip' => File.open(file, 'rb')
@@ -370,7 +368,7 @@ class BazaRb
370
368
  raise 'The "file" of the durable is nil' if file.nil?
371
369
  raise "The file '#{file}' is absent" unless File.exist?(file)
372
370
  elapsed(@loog) do
373
- with_retries(max_tries: @retries) do
371
+ with_retries(max_tries: @retries, rescue: TimedOut) do
374
372
  checked(
375
373
  Typhoeus::Request.put(
376
374
  home.append('durables').append(id).to_s,
@@ -408,7 +406,7 @@ class BazaRb
408
406
  request.on_body do |chunk|
409
407
  f.write(chunk)
410
408
  end
411
- with_retries(max_tries: @retries) do
409
+ with_retries(max_tries: @retries, rescue: TimedOut) do
412
410
  request.run
413
411
  end
414
412
  checked(request.response)
@@ -427,7 +425,7 @@ class BazaRb
427
425
  raise 'The "owner" of the lock is nil' if owner.nil?
428
426
  raise 'The "owner" of the lock may not be empty' if owner.empty?
429
427
  elapsed(@loog) do
430
- with_retries(max_tries: @retries) do
428
+ with_retries(max_tries: @retries, rescue: TimedOut) do
431
429
  checked(
432
430
  Typhoeus::Request.get(
433
431
  home.append('durables').append(id).append('lock').add(owner:).to_s,
@@ -450,7 +448,7 @@ class BazaRb
450
448
  raise 'The "owner" of the lock is nil' if owner.nil?
451
449
  raise 'The "owner" of the lock may not be empty' if owner.empty?
452
450
  elapsed(@loog) do
453
- with_retries(max_tries: @retries) do
451
+ with_retries(max_tries: @retries, rescue: TimedOut) do
454
452
  checked(
455
453
  Typhoeus::Request.get(
456
454
  home.append('durables').append(id).append('unlock').add(owner:).to_s,
@@ -463,6 +461,38 @@ class BazaRb
463
461
  end
464
462
  end
465
463
 
464
+ # Transfer some funds to another user.
465
+ #
466
+ # @param [String] recipient GitHub name (e.g. "yegor256") of the recipient
467
+ # @param [Float] amount The amount in Z/USDT (not zents!)
468
+ # @param [String] summary The description of the payment
469
+ def transfer(recipient, amount, summary)
470
+ raise 'The "recipient" is nil' if recipient.nil?
471
+ raise 'The "amount" is nil' if amount.nil?
472
+ raise 'The "amount" must be Float' unless amount.is_a?(Float)
473
+ raise 'The "summary" is nil' if summary.nil?
474
+ elapsed(@loog) do
475
+ with_retries(max_tries: @retries, rescue: TimedOut) do
476
+ checked(
477
+ Typhoeus::Request.post(
478
+ home.append('account').append('transfer').to_s,
479
+ body: {
480
+ '_csrf' => csrf,
481
+ 'human' => recipient,
482
+ 'amount' => amount.to_s,
483
+ 'summary' => summary
484
+ },
485
+ headers:,
486
+ connecttimeout: @timeout,
487
+ timeout: @timeout
488
+ ),
489
+ 302
490
+ )
491
+ end
492
+ throw :"Transferred ##{amount} to @#{recipient} at #{@host}"
493
+ end
494
+ end
495
+
466
496
  # Pop job from the server.
467
497
  #
468
498
  # @param [String] owner Who is acting (could be any text)
@@ -470,7 +500,6 @@ class BazaRb
470
500
  # @return [Boolean] TRUE if job taken, otherwise false
471
501
  def pop(owner, zip)
472
502
  raise 'The "zip" of the job is nil' if zip.nil?
473
- raise "The 'zip' file is absent: #{zip}" unless File.exist?(zip)
474
503
  success = false
475
504
  FileUtils.rm_f(zip)
476
505
  elapsed(@loog) do
@@ -487,7 +516,7 @@ class BazaRb
487
516
  request.on_body do |chunk|
488
517
  f.write(chunk)
489
518
  end
490
- with_retries(max_tries: @retries) do
519
+ with_retries(max_tries: @retries, rescue: TimedOut) do
491
520
  request.run
492
521
  end
493
522
  ret = request.response
@@ -503,17 +532,17 @@ class BazaRb
503
532
  success
504
533
  end
505
534
 
506
- # Submit a ZIP archvie to finish a job.
535
+ # Submit a ZIP archive to finish a job.
507
536
  #
508
537
  # @param [Integer] id The ID of the job on the server
509
538
  # @param [String] zip The path to the ZIP file with the content of the archive
510
539
  def finish(id, zip)
511
- raise 'The "id" of the job is nil' if id.nil?
512
- raise 'The "id" of the job must be an integer' unless id.is_a?(Integer)
540
+ raise 'The ID of the job is nil' if id.nil?
541
+ raise 'The ID of the job must be a positive integer' unless id.positive?
513
542
  raise 'The "zip" of the job is nil' if zip.nil?
514
543
  raise "The 'zip' file is absent: #{zip}" unless File.exist?(zip)
515
544
  elapsed(@loog) do
516
- with_retries(max_tries: @retries) do
545
+ with_retries(max_tries: @retries, rescue: TimedOut) do
517
546
  checked(
518
547
  Typhoeus::Request.put(
519
548
  home.append('finish').add(id:).to_s,
@@ -531,6 +560,65 @@ class BazaRb
531
560
  end
532
561
  end
533
562
 
563
+ # Enter a valve.
564
+ #
565
+ # @param [String] name Name of the job
566
+ # @param [String] badge Unique badge of the valve
567
+ # @param [String] why The reason
568
+ # @param [nil|Integer] job The ID of the job
569
+ # @return [String] The result just calculated or retrieved
570
+ def enter(name, badge, why, job)
571
+ elapsed(@loog, intro: "Entered valve #{badge} to #{name}") do
572
+ with_retries(max_tries: @retries, rescue: TimedOut) do
573
+ ret = checked(
574
+ Typhoeus::Request.get(
575
+ home.append('valves').append('result').add(badge:).to_s,
576
+ headers:
577
+ ),
578
+ [200, 204]
579
+ )
580
+ return ret.body if ret.code == 200
581
+ r = yield
582
+ uri = home.append('valves').append('add')
583
+ uri = uri.add(job:) unless job.nil?
584
+ checked(
585
+ Typhoeus::Request.post(
586
+ uri.to_s,
587
+ body: {
588
+ '_csrf' => csrf,
589
+ 'name' => name,
590
+ 'badge' => badge,
591
+ 'why' => why,
592
+ 'result' => r.to_s
593
+ },
594
+ headers:
595
+ ),
596
+ 302
597
+ )
598
+ r
599
+ end
600
+ end
601
+ end
602
+
603
+ # Get CSRF token from the server.
604
+ # @return [String] The token for this user
605
+ def csrf
606
+ token = nil
607
+ elapsed(@loog) do
608
+ with_retries(max_tries: @retries, rescue: TimedOut) do
609
+ token = checked(
610
+ Typhoeus::Request.get(
611
+ home.append('csrf').to_s,
612
+ headers:
613
+ ),
614
+ 200
615
+ ).body
616
+ end
617
+ throw :"CSRF token retrieved (#{token.length} chars)"
618
+ end
619
+ token
620
+ end
621
+
534
622
  private
535
623
 
536
624
  def headers
@@ -556,7 +644,7 @@ class BazaRb
556
644
  end
557
645
 
558
646
  def gzip(data)
559
- ''.dup.tap do |result|
647
+ (+'').tap do |result|
560
648
  io = StringIO.new(result)
561
649
  gz = Zlib::GzipWriter.new(io)
562
650
  gz.write(data)
@@ -571,14 +659,19 @@ class BazaRb
571
659
  .scheme(@ssl ? 'https' : 'http')
572
660
  end
573
661
 
662
+ # Check the HTTP response and return it.
663
+ #
664
+ # @param [Typhoeus::Response] ret The response
665
+ # @param [Array<Integer>] allowed List of acceptable HTTP codes
666
+ # @return [Typhoeus::Response] The same response
574
667
  def checked(ret, allowed = [200])
575
668
  allowed = [allowed] unless allowed.is_a?(Array)
576
669
  mtd = (ret.request.original_options[:method] || '???').upcase
577
670
  url = ret.effective_url
578
671
  if ret.return_code == :operation_timedout
579
672
  msg = "#{mtd} #{url} timed out in #{ret.total_time}s"
580
- @loog.debug(msg)
581
- raise msg
673
+ @loog.error(msg)
674
+ raise TimedOut, msg
582
675
  end
583
676
  log = "#{mtd} #{url} -> #{ret.code} (#{format('%0.2f', ret.total_time)}s)"
584
677
  if allowed.include?(ret.code)
@@ -608,6 +701,7 @@ class BazaRb
608
701
  when 0
609
702
  msg += ', most likely an internal error'
610
703
  end
611
- raise msg
704
+ @loog.error(msg)
705
+ raise ServerFailure, msg
612
706
  end
613
707
  end
data/test/test__helper.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2024 Zerocracy
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  $stdout.sync = true
24
7