+12
-2
app/firehose_client.rb
+12
-2
app/firehose_client.rb
···
1
1
require 'skyfall'
2
2
3
3
require_relative 'init'
4
+
require_relative 'models/import_job'
4
5
require_relative 'models/post'
5
6
require_relative 'models/subscription'
6
7
require_relative 'models/user'
···
48
49
@replaying = true
49
50
@last_update = Time.now
50
51
51
-
@timer ||= EM::PeriodicTimer.new(20) do
52
+
@live_check_timer ||= EM::PeriodicTimer.new(20) do
52
53
now = Time.now
53
54
diff = now - @last_update
54
55
···
56
57
log "Timer: last update #{sprintf('%.1f', diff)}s ago"
57
58
end
58
59
end
60
+
61
+
@jobs_timer ||= EM::PeriodicTimer.new(3) do
62
+
ImportJob.all.each do |job|
63
+
@active_users[job.user.did] = job.user
64
+
job.create_imports
65
+
job.destroy
66
+
end
67
+
end
59
68
}
60
69
61
70
@sky.on_disconnect {
···
135
144
if msg.status == :deleted
136
145
if user = User.find_by(did: msg.repo)
137
146
user.destroy
147
+
@active_users.delete_if { |u| u.id == user.id }
138
148
end
139
149
end
140
150
end
···
182
192
end
183
193
184
194
def inspect
185
-
vars = instance_variables - [:@timer]
195
+
vars = instance_variables - [:@jobs_timer, :@live_check_timer]
186
196
values = vars.map { |v| "#{v}=#{instance_variable_get(v).inspect}" }.join(", ")
187
197
"#<#{self.class}:0x#{object_id} #{values}>"
188
198
end
+42
app/import_worker.rb
+42
app/import_worker.rb
···
1
+
require 'active_record'
2
+
3
+
require_relative 'init'
4
+
require_relative 'import_manager'
5
+
require_relative 'models/import'
6
+
7
+
class ImportWorker
8
+
class UserThread < Thread
9
+
def initialize(user, collections)
10
+
@user = user
11
+
super { run(collections) }
12
+
end
13
+
14
+
def user_id
15
+
@user.id
16
+
end
17
+
18
+
def run(collections)
19
+
import = ImportManager.new(@user)
20
+
import.start(collections, false)
21
+
end
22
+
end
23
+
24
+
def run
25
+
@user_threads = []
26
+
27
+
loop do
28
+
@user_threads.delete_if { |t| !t.alive? }
29
+
30
+
ActiveRecord::Base.transaction do
31
+
if user = User.with_unfinished_imports.where.not(id: @user_threads.map(&:user_id)).first
32
+
collections = user.imports.unfinished.map(&:collection)
33
+
thread = UserThread.new(user, collections)
34
+
@user_threads << thread
35
+
end
36
+
end
37
+
38
+
# possible future enhancement: use LISTEN/UNLISTEN/NOTIFY and wait_for_notify
39
+
sleep 5
40
+
end
41
+
end
42
+
end
+2
app/models/import.rb
+2
app/models/import.rb
+13
app/models/import_job.rb
+13
app/models/import_job.rb
+10
app/models/user.rb
+10
app/models/user.rb
···
1
1
require 'active_record'
2
2
3
3
require_relative 'import'
4
+
require_relative 'import_job'
4
5
require_relative 'like'
5
6
require_relative 'quote'
6
7
require_relative 'pin'
···
13
14
14
15
has_many :posts
15
16
has_many :imports, dependent: :delete_all
17
+
has_one :import_job, dependent: :delete
16
18
17
19
before_destroy :delete_posts_cascading
18
20
···
54
56
55
57
def self.active
56
58
self.joins(:imports).distinct
59
+
end
60
+
61
+
def self.with_unfinished_imports
62
+
self.joins(:imports).merge(Import.unfinished).distinct
63
+
end
64
+
65
+
def active?
66
+
imports.exists?
57
67
end
58
68
59
69
def all_pending_items
+26
bin/worker
+26
bin/worker
···
1
+
#!/usr/bin/env ruby
2
+
3
+
require 'bundler/setup'
4
+
require_relative '../app/import_worker'
5
+
6
+
$stdout.sync = true
7
+
8
+
if ENV['ARLOG'] == '1'
9
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
10
+
else
11
+
ActiveRecord::Base.logger = nil
12
+
end
13
+
14
+
worker = ImportWorker.new
15
+
16
+
trap("SIGINT") {
17
+
puts "Stopping..."
18
+
exit
19
+
}
20
+
21
+
trap("SIGTERM") {
22
+
puts "Shutting down the service..."
23
+
exit
24
+
}
25
+
26
+
worker.run
+9
db/migrate/20250920182018_add_import_jobs.rb
+9
db/migrate/20250920182018_add_import_jobs.rb
+6
-1
db/schema.rb
+6
-1
db/schema.rb
···
10
10
#
11
11
# It's strongly recommended that you check this file into your version control system.
12
12
13
-
ActiveRecord::Schema[7.2].define(version: 2025_09_18_024627) do
13
+
ActiveRecord::Schema[7.2].define(version: 2025_09_20_182018) do
14
14
# These are extensions that must be enabled in order to support this database
15
15
enable_extension "plpgsql"
16
+
17
+
create_table "import_jobs", force: :cascade do |t|
18
+
t.integer "user_id", null: false
19
+
t.index ["user_id"], name: "index_import_jobs_on_user_id", unique: true
20
+
end
16
21
17
22
create_table "imports", force: :cascade do |t|
18
23
t.integer "user_id", null: false
+17
lib/tasks/import.rake
+17
lib/tasks/import.rake
···
4
4
require_relative '../../app/models/user'
5
5
require_relative '../../app/post_downloader'
6
6
7
+
task :enqueue_user do
8
+
unless ENV['DID']
9
+
raise "Required DID parameter missing"
10
+
end
11
+
12
+
user = User.find_or_create_by!(did: ENV['DID'])
13
+
14
+
if user.import_job
15
+
puts "Import for #{user.did} is already scheduled."
16
+
elsif user.active?
17
+
puts "Import for #{user.did} has already started."
18
+
else
19
+
user.create_import_job!
20
+
puts "Import for #{user.did} scheduled ✓"
21
+
end
22
+
end
23
+
7
24
task :import_user do
8
25
unless ENV['DID']
9
26
raise "Required DID parameter missing"