tina4ruby 3.10.11 → 3.10.13
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 +4 -4
- data/lib/tina4/orm.rb +5 -0
- data/lib/tina4/rack_app.rb +10 -0
- data/lib/tina4/session_handlers/database_handler.rb +6 -0
- data/lib/tina4/session_handlers/file_handler.rb +12 -0
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/websocket_backplane.rb +73 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6d46cd66e041da43c47eef3869b8fd66455270fe1937d4c86ee839836dd6419
|
|
4
|
+
data.tar.gz: bf4853aa8715cc865c25fa2693b10d50168d1fd48bc9a9371e8368a963e142bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21b443f74d40f46d610be7d039be8158b6a3a8be044476730ece168baaa2f8bb9b65a1b6e82f8a8c2d1cf50b9aafa614d99c3229b0c75b64b696973853cd1a21
|
|
7
|
+
data.tar.gz: 93a487f54bcbf48995dc61915326b8826cac1a7a4250275ab0ec25fd3a47af55b39da60186dbeac86eb87db2c2d2b6b5f2a92f73f6ce46bf44db9c724cec8fb8
|
data/lib/tina4/orm.rb
CHANGED
|
@@ -315,6 +315,7 @@ module Tina4
|
|
|
315
315
|
|
|
316
316
|
sql = "CREATE TABLE IF NOT EXISTS #{table_name} (#{col_defs.join(', ')})"
|
|
317
317
|
db.execute(sql)
|
|
318
|
+
db.commit
|
|
318
319
|
true
|
|
319
320
|
end
|
|
320
321
|
|
|
@@ -401,6 +402,7 @@ module Tina4
|
|
|
401
402
|
end
|
|
402
403
|
@persisted = true
|
|
403
404
|
end
|
|
405
|
+
self.class.db.commit
|
|
404
406
|
true
|
|
405
407
|
rescue => e
|
|
406
408
|
@errors << e.message
|
|
@@ -422,6 +424,7 @@ module Tina4
|
|
|
422
424
|
else
|
|
423
425
|
self.class.db.delete(self.class.table_name, { pk => pk_value })
|
|
424
426
|
end
|
|
427
|
+
self.class.db.commit
|
|
425
428
|
@persisted = false
|
|
426
429
|
true
|
|
427
430
|
end
|
|
@@ -432,6 +435,7 @@ module Tina4
|
|
|
432
435
|
raise "Cannot delete: no primary key value" unless pk_value
|
|
433
436
|
|
|
434
437
|
self.class.db.delete(self.class.table_name, { pk => pk_value })
|
|
438
|
+
self.class.db.commit
|
|
435
439
|
@persisted = false
|
|
436
440
|
true
|
|
437
441
|
end
|
|
@@ -448,6 +452,7 @@ module Tina4
|
|
|
448
452
|
{ self.class.soft_delete_field => 0 },
|
|
449
453
|
{ pk => pk_value }
|
|
450
454
|
)
|
|
455
|
+
self.class.db.commit
|
|
451
456
|
__send__("#{self.class.soft_delete_field}=", 0) if respond_to?("#{self.class.soft_delete_field}=")
|
|
452
457
|
true
|
|
453
458
|
end
|
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -107,6 +107,16 @@ module Tina4
|
|
|
107
107
|
if request_obj&.instance_variable_get(:@session)
|
|
108
108
|
sess = request_obj.session
|
|
109
109
|
sess.save
|
|
110
|
+
|
|
111
|
+
# Probabilistic garbage collection (~1% of requests)
|
|
112
|
+
if rand(1..100) == 1
|
|
113
|
+
begin
|
|
114
|
+
sess.gc
|
|
115
|
+
rescue StandardError
|
|
116
|
+
# GC failure is non-critical — silently ignore
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
110
120
|
sid = sess.id
|
|
111
121
|
cookie_val = (env["HTTP_COOKIE"] || "")[/tina4_session=([^;]+)/, 1]
|
|
112
122
|
if sid && sid != cookie_val
|
|
@@ -56,6 +56,12 @@ module Tina4
|
|
|
56
56
|
@db.execute("DELETE FROM #{TABLE_NAME} WHERE expires_at > 0 AND expires_at < ?", [Time.now.to_f])
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# Garbage-collect expired sessions. Matches the Python interface.
|
|
60
|
+
# @param max_age [Integer] maximum session age in seconds (unused — expiry is absolute)
|
|
61
|
+
def gc(max_age)
|
|
62
|
+
@db.execute("DELETE FROM #{TABLE_NAME} WHERE expires_at > 0 AND expires_at < ?", [Time.now.to_f])
|
|
63
|
+
end
|
|
64
|
+
|
|
59
65
|
private
|
|
60
66
|
|
|
61
67
|
def ensure_table
|
|
@@ -44,6 +44,18 @@ module Tina4
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Garbage-collect expired sessions. Matches the Python interface.
|
|
48
|
+
# @param max_age [Integer] maximum session age in seconds
|
|
49
|
+
def gc(max_age)
|
|
50
|
+
return unless Dir.exist?(@dir)
|
|
51
|
+
now = Time.now
|
|
52
|
+
Dir.glob(File.join(@dir, "sess_*")).each do |file|
|
|
53
|
+
File.delete(file) if File.mtime(file) + max_age < now
|
|
54
|
+
rescue StandardError
|
|
55
|
+
# Corrupt or locked file — skip
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
47
59
|
private
|
|
48
60
|
|
|
49
61
|
def session_path(session_id)
|
data/lib/tina4/version.rb
CHANGED
|
@@ -56,7 +56,7 @@ module Tina4
|
|
|
56
56
|
when "redis"
|
|
57
57
|
RedisBackplane.new(url: url)
|
|
58
58
|
when "nats"
|
|
59
|
-
|
|
59
|
+
NATSBackplane.new(url: url)
|
|
60
60
|
when ""
|
|
61
61
|
nil
|
|
62
62
|
else
|
|
@@ -115,4 +115,76 @@ module Tina4
|
|
|
115
115
|
@redis.close
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
|
+
|
|
119
|
+
# NATS pub/sub backplane.
|
|
120
|
+
#
|
|
121
|
+
# Requires the +nats-pure+ gem (+gem install nats-pure+). The require is
|
|
122
|
+
# deferred so the rest of Tina4 works fine without it installed — an error
|
|
123
|
+
# is raised only when this class is actually instantiated.
|
|
124
|
+
#
|
|
125
|
+
# NATS is async-native, so we run a background thread with an event
|
|
126
|
+
# machine for the subscription listener.
|
|
127
|
+
class NATSBackplane < WebSocketBackplane
|
|
128
|
+
def initialize(url: nil)
|
|
129
|
+
begin
|
|
130
|
+
require "nats/client"
|
|
131
|
+
rescue LoadError
|
|
132
|
+
raise LoadError,
|
|
133
|
+
"The 'nats-pure' gem is required for NATSBackplane. " \
|
|
134
|
+
"Install it with: gem install nats-pure"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@url = url || ENV.fetch("TINA4_WS_BACKPLANE_URL", "nats://localhost:4222")
|
|
138
|
+
@subs = {}
|
|
139
|
+
@threads = {}
|
|
140
|
+
@running = true
|
|
141
|
+
@mutex = Mutex.new
|
|
142
|
+
|
|
143
|
+
# Connect to NATS in a background thread with its own event loop
|
|
144
|
+
@nats = NATS::IO::Client.new
|
|
145
|
+
@nats.connect(@url)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def publish(channel, message)
|
|
149
|
+
@nats.publish(channel, message)
|
|
150
|
+
@nats.flush
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def subscribe(channel, &block)
|
|
154
|
+
@mutex.synchronize do
|
|
155
|
+
sid = @nats.subscribe(channel) do |msg|
|
|
156
|
+
block.call(msg.data) if @running
|
|
157
|
+
end
|
|
158
|
+
@subs[channel] = sid
|
|
159
|
+
|
|
160
|
+
# Run NATS event processing in a background thread
|
|
161
|
+
@threads[channel] ||= Thread.new do
|
|
162
|
+
loop do
|
|
163
|
+
break unless @running
|
|
164
|
+
sleep 0.01
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def unsubscribe(channel)
|
|
171
|
+
@mutex.synchronize do
|
|
172
|
+
sid = @subs.delete(channel)
|
|
173
|
+
@nats.unsubscribe(sid) if sid
|
|
174
|
+
thread = @threads.delete(channel)
|
|
175
|
+
thread&.kill
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def close
|
|
180
|
+
@running = false
|
|
181
|
+
@mutex.synchronize do
|
|
182
|
+
@subs.each_value { |sid| @nats.unsubscribe(sid) rescue nil }
|
|
183
|
+
@subs.clear
|
|
184
|
+
@threads.each_value { |t| t.kill }
|
|
185
|
+
@threads.clear
|
|
186
|
+
end
|
|
187
|
+
@nats.close
|
|
188
|
+
end
|
|
189
|
+
end
|
|
118
190
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.10.
|
|
4
|
+
version: 3.10.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-03-28 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: rack
|
|
@@ -399,6 +400,7 @@ licenses:
|
|
|
399
400
|
- MIT
|
|
400
401
|
metadata:
|
|
401
402
|
homepage_uri: https://tina4.com
|
|
403
|
+
post_install_message:
|
|
402
404
|
rdoc_options: []
|
|
403
405
|
require_paths:
|
|
404
406
|
- lib
|
|
@@ -413,7 +415,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
413
415
|
- !ruby/object:Gem::Version
|
|
414
416
|
version: '0'
|
|
415
417
|
requirements: []
|
|
416
|
-
rubygems_version: 4.
|
|
418
|
+
rubygems_version: 3.4.19
|
|
419
|
+
signing_key:
|
|
417
420
|
specification_version: 4
|
|
418
421
|
summary: Simple. Fast. Human. This is not a framework.
|
|
419
422
|
test_files: []
|