Class: BazaRb

Inherits:
Object
  • Object
show all
Defined in:
lib/baza-rb.rb,
lib/baza-rb/version.rb

Overview

Just a version.

We keep this file separate from the “baza-rb.rb” in order to have an ability to include it from the “.gemspec” script, without including all other packages (thus failing the build).

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2024-2026 Yegor Bugayenko

License

MIT

Defined Under Namespace

Classes: BadCompression, BadResponse, ConnectionFailed, Fake, ServerFailure, TimedOut

Constant Summary collapse

DEFAULT_CHUNK_SIZE =

How big are the chunks we send, by default, in bytes. Numbers larger than 1Mb may lead to problems with the server, since sending time will be too long and the server may drop connections. Better keep it as is: 1Mb.

1_000_000
VERSION =
'0.14.4'

Instance Method Summary collapse

Constructor Details

#initialize(host, port, token, ssl: true, timeout: 30, retries: 5, pause: 1, loog: Loog::NULL, compress: true) ⇒ BazaRb

Initialize a new Zerocracy API client.

Parameters:

  • host (String)

    The API host name (e.g., ‘api.zerocracy.com’)

  • port (Integer)

    The TCP port to connect to (usually 443 for HTTPS)

  • token (String)

    Your Zerocracy API authentication token

  • ssl (Boolean) (defaults to: true)

    Whether to use SSL/HTTPS (default: true)

  • timeout (Float) (defaults to: 30)

    Connection and request timeout in seconds (default: 30)

  • retries (Integer) (defaults to: 5)

    Number of retries on connection failure (default: 3)

  • pause (Integer) (defaults to: 1)

    The factor on pause (<1 means faster, >1 means slower)

  • loog (Loog) (defaults to: Loog::NULL)

    The logging facility (default: Loog::NULL)

  • compress (Boolean) (defaults to: true)

    Whether to use GZIP compression for requests/responses (default: true)



69
70
71
72
73
74
75
76
77
78
79
# File 'lib/baza-rb.rb', line 69

def initialize(host, port, token, ssl: true, timeout: 30, retries: 5, pause: 1, loog: Loog::NULL, compress: true)
  @host = host
  @port = port
  @ssl = ssl
  @token = token
  @timeout = timeout
  @loog = loog
  @retries = retries
  @pause = pause
  @compress = compress
end

Instance Method Details

#balanceFloat

Get current balance of the authenticated user.

Returns:

  • (Float)

    The balance in zents (Ƶ), where 1 Ƶ = 1 USDT

Raises:

  • (ServerFailure)

    If authentication fails or server returns an error



99
100
101
102
103
104
105
106
107
# File 'lib/baza-rb.rb', line 99

def balance
  z = nil
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('account').append('balance'))
    z = ret.body.to_f
    throw :"The balance is Ƶ#{z}, at #{@host}"
  end
  z
end

#csrfString

Get CSRF token from the server for authenticated requests.

The CSRF token is required for POST requests to prevent cross-site request forgery attacks.

Returns:

  • (String)

    The CSRF token for the authenticated user

Raises:



535
536
537
538
539
540
541
542
# File 'lib/baza-rb.rb', line 535

def csrf
  token = nil
  elapsed(@loog, level: Logger::INFO) do
    token = get(home.append('csrf')).body
    throw :"CSRF token retrieved (#{token.length} chars)"
  end
  token
end

#durable_find(pname, file) ⇒ Integer?

Find a durable by job name and file name.

Parameters:

  • pname (String)

    The name of the job

  • file (String)

    The file name

Returns:

  • (Integer, nil)

    The ID of the durable if found, nil if not found



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/baza-rb.rb', line 412

def durable_find(pname, file)
  raise 'The "pname" is nil' if pname.nil?
  raise 'The "pname" may not be empty' if pname.empty?
  raise 'The "file" is nil' if file.nil?
  raise 'The "file" may not be empty' if file.empty?
  id = nil
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('durable-find').add(file:, pname:), [200, 404])
    if ret.code == 200
      id = ret.body.to_i
      throw :"Found durable ##{id} for job \"#{pname}\" file \"#{file}\" at #{@host}"
    else
      throw :"Durable not found for job \"#{pname}\" file \"#{file}\" at #{@host}"
    end
  end
  id
end

#durable_load(id, file) ⇒ Object

Load a single durable from server to local file.

Parameters:

  • id (Integer)

    The ID of the durable

  • file (String)

    The local file path to save the downloaded durable

Raises:



356
357
358
359
360
361
362
363
364
365
# File 'lib/baza-rb.rb', line 356

def durable_load(id, file)
  raise 'The ID of the durable is nil' if id.nil?
  raise 'The ID of the durable must be an Integer' unless id.is_a?(Integer)
  raise 'The ID of the durable must be a positive integer' unless id.positive?
  raise 'The "file" of the durable is nil' if file.nil?
  elapsed(@loog, level: Logger::INFO) do
    download(home.append('durables').append(id), file)
    throw :"Durable ##{id} loaded #{File.size(file)} bytes from #{@host}"
  end
end

#durable_lock(id, owner) ⇒ Object

Lock a single durable.

Parameters:

  • id (Integer)

    The ID of the durable

  • owner (String)

    The owner of the lock

Raises:



372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/baza-rb.rb', line 372

def durable_lock(id, owner)
  raise 'The ID of the durable is nil' if id.nil?
  raise 'The ID of the durable must be an Integer' unless id.is_a?(Integer)
  raise 'The ID of the durable must be a positive integer' unless id.positive?
  raise 'The "owner" of the lock is nil' if owner.nil?
  raise 'The "owner" of the lock may not be empty' if owner.empty?
  elapsed(@loog, level: Logger::INFO) do
    post(
      home.append('durables').append(id).append('lock'),
      { 'owner' => owner }
    )
    throw :"Durable ##{id} locked at #{@host}"
  end
end

#durable_place(pname, file) ⇒ Integer

Place a single durable file on the server.

The file provided will only be uploaded to the server if the durable is currently absent. If the durable is present, the file will be ignored. It is expected to use only small placeholder files, not real data.

Parameters:

  • pname (String)

    The name of the product on the server

  • file (String)

    The path to the file to upload

Returns:

  • (Integer)

    The ID of the created durable

Raises:



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/baza-rb.rb', line 309

def durable_place(pname, file)
  raise 'The "pname" of the durable is nil' if pname.nil?
  raise 'The "pname" of the durable may not be empty' if pname.empty?
  raise 'The "file" of the durable is nil' if file.nil?
  raise "The file '#{file}' is absent" unless File.exist?(file)
  if File.size(file) > 1024
    raise "The file '#{file}' is too big (#{File.size(file)} bytes) for durable_place(), use durable_save() instead"
  end
  id = nil
  elapsed(@loog, level: Logger::INFO) do
    ret = post(
      home.append('durable-place'),
      {
        'pname' => pname,
        'file' => File.basename(file),
        'zip' => File.open(file, 'rb')
      }
    )
    id = ret.headers['X-Zerocracy-DurableId'].to_i
    throw :"Durable ##{id} (#{file}, #{File.size(file)} bytes) placed for job \"#{pname}\" at #{@host}"
  end
  id
end

#durable_save(id, file, chunk_size: DEFAULT_CHUNK_SIZE) ⇒ Object

Save a single durable from local file to server.

Parameters:

  • id (Integer)

    The ID of the durable

  • file (String)

    The file to upload

  • chunk_size (Integer) (defaults to: DEFAULT_CHUNK_SIZE)

    Maximum size of one chunk

Raises:



339
340
341
342
343
344
345
346
347
348
349
# File 'lib/baza-rb.rb', line 339

def durable_save(id, file, chunk_size: DEFAULT_CHUNK_SIZE)
  raise 'The ID of the durable is nil' if id.nil?
  raise 'The ID of the durable must be an Integer' unless id.is_a?(Integer)
  raise 'The ID of the durable must be a positive integer' unless id.positive?
  raise 'The "file" of the durable is nil' if file.nil?
  raise "The file '#{file}' is absent" unless File.exist?(file)
  elapsed(@loog, level: Logger::INFO) do
    upload(home.append('durables').append(id), file, chunk_size:)
    throw :"Durable ##{id} saved #{File.size(file)} bytes to #{@host}"
  end
end

#durable_unlock(id, owner) ⇒ Object

Unlock a single durable.

Parameters:

  • id (Integer)

    The ID of the durable

  • owner (String)

    The owner of the lock

Raises:



392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/baza-rb.rb', line 392

def durable_unlock(id, owner)
  raise 'The ID of the durable is nil' if id.nil?
  raise 'The ID of the durable must be an Integer' unless id.is_a?(Integer)
  raise 'The ID of the durable must be a positive integer' unless id.positive?
  raise 'The "owner" of the lock is nil' if owner.nil?
  raise 'The "owner" of the lock may not be empty' if owner.empty?
  elapsed(@loog, level: Logger::INFO) do
    post(
      home.append('durables').append(id).append('unlock'),
      { 'owner' => owner }
    )
    throw :"Durable ##{id} unlocked at #{@host}"
  end
end

#enter(pname, badge, why, job) { ... } ⇒ String

Enter a valve to cache or retrieve a computation result.

Valves prevent duplicate computations by caching results. If a result for the given badge already exists, it’s returned. Otherwise, the block is executed and its result is cached.

Parameters:

  • pname (String)

    Name of the product

  • badge (String)

    Unique identifier for this valve/computation

  • why (String)

    The reason/description for entering this valve

  • job (nil|Integer)

    Optional job ID to associate with this valve

Yields:

  • Block that computes the result if not cached

Returns:

  • (String)

    The cached result or newly computed result from the block

Raises:



506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/baza-rb.rb', line 506

def enter(pname, badge, why, job)
  elapsed(@loog, good: "Entered valve #{badge} to #{pname}") do
    retry_it do
      ret = get(home.append('result').add(badge:), [200, 204])
      return ret.body if ret.code == 200
      r = yield
      uri = home.append('valves')
      uri = uri.add(job:) unless job.nil?
      post(
        uri,
        {
          'badge' => badge,
          'pname' => pname,
          'result' => r.to_s,
          'why' => why
        }
      )
      r
    end
  end
end

#exit_code(id) ⇒ Integer

Read and return the exit code of the job.

Parameters:

  • id (Integer)

    The ID of the job on the server

Returns:

  • (Integer)

    The exit code

Raises:

  • (ServerFailure)

    If the job doesn’t exist or retrieval fails



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/baza-rb.rb', line 195

def exit_code(id)
  raise 'The ID of the job is nil' if id.nil?
  raise 'The ID of the job must be a positive integer' unless id.positive?
  code = 0
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('exit').append("#{id}.txt"))
    code = ret.body.to_i
    throw :"The exit code of the job ##{id} is #{code}"
  end
  code
end

#fee(tab, amount, summary, job) ⇒ Integer

Pay a fee associated with a job.

Parameters:

  • tab (String)

    The category/type of the fee (use “unknown” if not sure)

  • amount (Float)

    The fee amount in Ƶ (zents)

  • summary (String)

    The description/reason for the fee

  • job (Integer)

    The ID of the job this fee is for

Returns:

  • (Integer)

    Receipt ID for the fee payment

Raises:



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/baza-rb.rb', line 469

def fee(tab, amount, summary, job)
  raise 'The "tab" is nil' if tab.nil?
  raise 'The "amount" is nil' if amount.nil?
  raise 'The "amount" must be Float' unless amount.is_a?(Float)
  raise 'The "job" is nil' if job.nil?
  raise 'The "job" must be Integer' unless job.is_a?(Integer)
  raise 'The "summary" is nil' if summary.nil?
  id = nil
  elapsed(@loog, level: Logger::INFO) do
    ret = post(
      home.append('account').append('fee'),
      {
        'amount' => format('%0.6f', amount),
        'job' => job.to_s,
        'summary' => summary,
        'tab' => tab
      }
    )
    id = ret.headers['X-Zerocracy-ReceiptId'].to_i
    throw :"Fee Ƶ#{format('%0.6f', amount)} paid at #{@host}"
  end
  id
end

#finished?(id) ⇒ Boolean

Check if the job with this ID is finished already.

Parameters:

  • id (Integer)

    The ID of the job on the server

Returns:

  • (Boolean)

    TRUE if the job has completed execution, FALSE otherwise

Raises:



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/baza-rb.rb', line 161

def finished?(id)
  raise 'The ID of the job is nil' if id.nil?
  raise 'The ID of the job must be a positive integer' unless id.positive?
  fin = false
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('finished').append(id))
    fin = ret.body == 'yes'
    throw :"The job ##{id} is #{'not yet ' unless fin}finished at #{@host}#{" (#{ret.body.inspect})" unless fin}"
  end
  fin
end

#lock(pname, owner) ⇒ Object

Lock the name.

Parameters:

  • pname (String)

    The name of the product on the server

  • owner (String)

    The owner of the lock (any string)

Raises:

  • (RuntimeError)

    If the name is already locked

  • (ServerFailure)

    If the lock operation fails



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/baza-rb.rb', line 230

def lock(pname, owner)
  raise 'The "pname" of the product is nil' if pname.nil?
  raise 'The "pname" of the product may not be empty' if pname.empty?
  raise 'The "owner" of the lock is nil' if owner.nil?
  raise 'The "owner" of the lock may not be empty' if owner.empty?
  elapsed(@loog, level: Logger::INFO) do
    ret = post(
      home.append('lock').append(pname),
      { 'owner' => owner },
      [302, 409]
    )
    throw :"Product name #{pname.inspect} locked at #{@host}" if ret.code == 302
    raise "Failed to lock #{pname.inspect} product at #{@host}, it's already locked"
  end
end

#name_exists?(pname) ⇒ Boolean

Check whether the name of the job exists on the server.

Parameters:

  • pname (String)

    The name of the product on the server

Returns:

  • (Boolean)

    TRUE if such name exists



286
287
288
289
290
291
292
293
294
295
296
# File 'lib/baza-rb.rb', line 286

def name_exists?(pname)
  raise 'The "pname" of the product is nil' if pname.nil?
  raise 'The "pname" of the product may not be empty' if pname.empty?
  exists = false
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('exists').append(pname))
    exists = ret.body == 'yes'
    throw :"The name #{pname.inspect} #{exists ? 'exists' : "doesn't exist"} at #{@host}"
  end
  exists
end

#pull(id) ⇒ String

Pull factbase from the server for a specific job.

Parameters:

  • id (Integer)

    The ID of the job on the server

Returns:

  • (String)

    Binary data of the factbase (can be saved to file)

Raises:



142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/baza-rb.rb', line 142

def pull(id)
  raise 'The ID of the job is nil' if id.nil?
  raise 'The ID of the job must be a positive integer' unless id.positive?
  data = ''
  elapsed(@loog, level: Logger::INFO) do
    Tempfile.open do |file|
      download(home.append('pull').append("#{id}.fb"), file.path)
      data = File.binread(file)
      throw :"Pulled #{data.bytesize} bytes of job ##{id} factbase at #{@host}"
    end
  end
  data
end

#push(pname, data, meta, chunk_size: DEFAULT_CHUNK_SIZE) ⇒ Object

Push factbase to the server to create a new job.

Parameters:

  • pname (String)

    The unique name of the product on the server

  • data (String)

    The binary data to push to the server (factbase content)

  • meta (Array<String>)

    List of metadata strings to attach to the job

  • chunk_size (Integer) (defaults to: DEFAULT_CHUNK_SIZE)

    Maximum size of one chunk

Raises:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/baza-rb.rb', line 116

def push(pname, data, meta, chunk_size: DEFAULT_CHUNK_SIZE)
  raise 'The "name" of the job is nil' if pname.nil?
  raise 'The "name" of the job may not be empty' if pname.empty?
  raise 'The "data" of the job is nil' if data.nil?
  raise 'The "meta" of the job is nil' if meta.nil?
  elapsed(@loog, level: Logger::INFO) do
    Tempfile.open do |file|
      File.binwrite(file.path, data)
      upload(
        home.append('push').append(pname),
        file.path,
        headers.merge(
          'X-Zerocracy-Meta' => meta.map { |v| Base64.encode64(v).delete("\n") }.join(' ')
        ),
        chunk_size:
      )
    end
    throw :"Pushed #{data.bytesize} bytes to #{@host}"
  end
end

#recent(name) ⇒ Integer

Get the ID of the job by the name.

Parameters:

  • name (String)

    The name of the job on the server

Returns:

  • (Integer)

    The ID of the job on the server

Raises:

  • (ServerFailure)

    If the job doesn’t exist or retrieval fails



270
271
272
273
274
275
276
277
278
279
280
# File 'lib/baza-rb.rb', line 270

def recent(name)
  raise 'The "name" of the job is nil' if name.nil?
  raise 'The "name" of the job may not be empty' if name.empty?
  job = nil
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('recent').append("#{name}.txt"))
    job = ret.body.to_i
    throw :"The recent \"#{name}\" job's ID is ##{job} at #{@host}"
  end
  job
end

#stdout(id) ⇒ String

Read and return the stdout of the job.

Parameters:

  • id (Integer)

    The ID of the job on the server

Returns:

  • (String)

    The stdout, as a text

Raises:

  • (ServerFailure)

    If the job doesn’t exist or retrieval fails



178
179
180
181
182
183
184
185
186
187
188
# File 'lib/baza-rb.rb', line 178

def stdout(id)
  raise 'The ID of the job is nil' if id.nil?
  raise 'The ID of the job must be a positive integer' unless id.positive?
  stdout = ''
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('stdout').append("#{id}.txt"))
    stdout = ret.body
    throw :"The stdout of the job ##{id} has #{stdout.split("\n").count} lines"
  end
  stdout
end

#transfer(recipient, amount, summary, job: nil) ⇒ Integer

Transfer funds to another user.

Parameters:

  • recipient (String)

    GitHub username of the recipient (e.g. “yegor256”)

  • amount (Float)

    The amount to transfer in Ƶ (zents)

  • summary (String)

    The description/reason for the payment

  • job (Integer) (defaults to: nil)

    Optional job ID to associate with this transfer

Returns:

  • (Integer)

    Receipt ID for the transaction

Raises:



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/baza-rb.rb', line 438

def transfer(recipient, amount, summary, job: nil)
  raise 'The "recipient" is nil' if recipient.nil?
  raise 'The "amount" is nil' if amount.nil?
  raise 'The "amount" must be Float' unless amount.is_a?(Float)
  raise 'The "summary" is nil' if summary.nil?
  id = nil
  body = {
    'human' => recipient,
    'amount' => format('%0.6f', amount),
    'summary' => summary
  }
  body['job'] = job unless job.nil?
  elapsed(@loog, level: Logger::INFO) do
    ret = post(
      home.append('account').append('transfer'),
      body
    )
    id = ret.headers['X-Zerocracy-ReceiptId'].to_i
    throw :"Transferred Ƶ#{format('%0.6f', amount)} to @#{recipient} at #{@host}"
  end
  id
end

#unlock(pname, owner) ⇒ Object

Unlock the name.

Parameters:

  • pname (String)

    The name of the job on the server

  • owner (String)

    The owner of the lock (any string)

Raises:



251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/baza-rb.rb', line 251

def unlock(pname, owner)
  raise 'The "pname" of the job is nil' if pname.nil?
  raise 'The "pname" of the job may not be empty' if pname.empty?
  raise 'The "owner" of the lock is nil' if owner.nil?
  raise 'The "owner" of the lock may not be empty' if owner.empty?
  elapsed(@loog, level: Logger::INFO) do
    post(
      home.append('unlock').append(pname),
      { 'owner' => owner }
    )
    throw :"Job name #{pname.inspect} unlocked at #{@host}"
  end
end

#verified(id) ⇒ String

Read and return the verification verdict of the job.

Parameters:

  • id (Integer)

    The ID of the job on the server

Returns:

  • (String)

    The verdict

Raises:

  • (ServerFailure)

    If the job doesn’t exist or retrieval fails



212
213
214
215
216
217
218
219
220
221
222
# File 'lib/baza-rb.rb', line 212

def verified(id)
  raise 'The ID of the job is nil' if id.nil?
  raise 'The ID of the job must be a positive integer' unless id.positive?
  verdict = ''
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('jobs').append(id).append('verified.txt'))
    verdict = ret.body
    throw :"The verdict of the job ##{id} is #{verdict.inspect}"
  end
  verdict
end

#whoamiString

Get GitHub login name of the logged in user.

Returns:

  • (String)

    GitHub nickname of the authenticated user

Raises:

  • (ServerFailure)

    If authentication fails or server returns an error



85
86
87
88
89
90
91
92
93
# File 'lib/baza-rb.rb', line 85

def whoami
  nick = nil
  elapsed(@loog, level: Logger::INFO) do
    ret = get(home.append('whoami'))
    nick = ret.body
    throw :"I know that I am @#{nick}, at #{@host}"
  end
  nick
end