mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2026-03-22 04:06:18 +00:00
Compare commits
2 Commits
202966fd08
...
f2edc30bbb
| Author | SHA1 | Date | |
|---|---|---|---|
| f2edc30bbb | |||
| 232ed2032f |
18
lib/api.rb
18
lib/api.rb
@@ -9,6 +9,10 @@ class W3DHub
|
|||||||
DEFAULT_HEADERS + [["Content-Type", "application/x-www-form-urlencoded"]]
|
DEFAULT_HEADERS + [["Content-Type", "application/x-www-form-urlencoded"]]
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
|
def self.on_fiber(method, *args, &callback)
|
||||||
|
BackgroundWorker.job(-> { Api.send(method, *args) }, callback)
|
||||||
|
end
|
||||||
|
|
||||||
#! === W3D Hub API === !#
|
#! === W3D Hub API === !#
|
||||||
|
|
||||||
ENDPOINT = "https://secure.w3dhub.com".freeze
|
ENDPOINT = "https://secure.w3dhub.com".freeze
|
||||||
@@ -165,6 +169,12 @@ class W3DHub
|
|||||||
|
|
||||||
SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com".freeze
|
SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com".freeze
|
||||||
|
|
||||||
|
def self.get(url, headers = DEFAULT_HEADERS, body = nil)
|
||||||
|
@client ||= Async::HTTP::Client.new(Async::HTTP::Endpoint.parse(SERVER_LIST_ENDPOINT, protocol: Async::HTTP::Protocol::HTTP10))
|
||||||
|
|
||||||
|
@client.get(url, headers, body)
|
||||||
|
end
|
||||||
|
|
||||||
# Method: GET
|
# Method: GET
|
||||||
# FORMAT: JSON
|
# FORMAT: JSON
|
||||||
|
|
||||||
@@ -182,8 +192,8 @@ class W3DHub
|
|||||||
# id, name, score, kills, deaths
|
# id, name, score, kills, deaths
|
||||||
# ...players[]:
|
# ...players[]:
|
||||||
# nick, team (index of teams array), score, kills, deaths
|
# nick, team (index of teams array), score, kills, deaths
|
||||||
def self.server_list(internet, level = 1)
|
def self.server_list(level = 1)
|
||||||
response = internet.get("#{SERVER_LIST_ENDPOINT}/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS)
|
response = get("#{SERVER_LIST_ENDPOINT}/listings/getAll/v2?statusLevel=#{level}")
|
||||||
|
|
||||||
if response.success?
|
if response.success?
|
||||||
data = JSON.parse(response.read, symbolize_names: true)
|
data = JSON.parse(response.read, symbolize_names: true)
|
||||||
@@ -204,8 +214,8 @@ class W3DHub
|
|||||||
# id, name, score, kills, deaths
|
# id, name, score, kills, deaths
|
||||||
# ...players[]:
|
# ...players[]:
|
||||||
# nick, team (index of teams array), score, kills, deaths
|
# nick, team (index of teams array), score, kills, deaths
|
||||||
def self.server_details(internet, id, level)
|
def self.server_details(id, level)
|
||||||
response = internet.get("#{SERVER_LIST_ENDPOINT}/listings/getStatus/v2/#{id}?statusLevel=#{level}", DEFAULT_HEADERS)
|
response = get("#{SERVER_LIST_ENDPOINT}/listings/getStatus/v2/#{id}?statusLevel=#{level}")
|
||||||
|
|
||||||
if response.success?
|
if response.success?
|
||||||
hash = JSON.parse(response.read, symbolize_names: true)
|
hash = JSON.parse(response.read, symbolize_names: true)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
Thread.new do
|
||||||
Async do |task|
|
Async do |task|
|
||||||
internet = Async::HTTP::Internet.instance
|
internet = Async::HTTP::Internet.instance
|
||||||
|
|
||||||
@@ -118,3 +119,4 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ class W3DHub
|
|||||||
@task_state
|
@task_state
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log(string)
|
||||||
|
log string if W3DHUB_DEBUG
|
||||||
|
end
|
||||||
|
|
||||||
# Start task, inside its own thread
|
# Start task, inside its own thread
|
||||||
# FIXME: Ruby 3 has parallelism now: Use a Ractor to do work on a seperate core to
|
# FIXME: Ruby 3 has parallelism now: Use a Ractor to do work on a seperate core to
|
||||||
# prevent the UI from locking up while doing computation heavy work, i.e building
|
# prevent the UI from locking up while doing computation heavy work, i.e building
|
||||||
@@ -201,7 +205,7 @@ class W3DHub
|
|||||||
packages = []
|
packages = []
|
||||||
|
|
||||||
manifests.reverse.each do |manifest|
|
manifests.reverse.each do |manifest|
|
||||||
puts "#{manifest.game}-#{manifest.type}: #{manifest.version} (#{manifest.base_version})"
|
log "#{manifest.game}-#{manifest.type}: #{manifest.version} (#{manifest.base_version})"
|
||||||
|
|
||||||
manifest.files.each do |file|
|
manifest.files.each do |file|
|
||||||
@files["#{file.name}:#{manifest.version}"] = file
|
@files["#{file.name}:#{manifest.version}"] = file
|
||||||
@@ -250,7 +254,11 @@ class W3DHub
|
|||||||
file_count = manifests.map { |m| m.files.count }.sum
|
file_count = manifests.map { |m| m.files.count }.sum
|
||||||
processed_files = 0
|
processed_files = 0
|
||||||
|
|
||||||
|
folder_exists = File.directory?(path)
|
||||||
|
|
||||||
manifests.each do |manifest|
|
manifests.each do |manifest|
|
||||||
|
break unless folder_exists
|
||||||
|
|
||||||
manifest.files.each do |file|
|
manifest.files.each do |file|
|
||||||
safe_file_name = file.name.gsub("\\", "/")
|
safe_file_name = file.name.gsub("\\", "/")
|
||||||
# Fix borked data -> Data 'cause Windows don't care about capitalization
|
# Fix borked data -> Data 'cause Windows don't care about capitalization
|
||||||
@@ -266,7 +274,7 @@ class W3DHub
|
|||||||
|
|
||||||
unless File.exist?(file_path)
|
unless File.exist?(file_path)
|
||||||
rejected_files << { file: file, manifest_version: manifest.version }
|
rejected_files << { file: file, manifest_version: manifest.version }
|
||||||
puts "[#{manifest.version}] File missing: #{file_path}"
|
log "[#{manifest.version}] File missing: #{file_path}"
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -279,21 +287,20 @@ class W3DHub
|
|||||||
|
|
||||||
f.close
|
f.close
|
||||||
|
|
||||||
pp file if file.checksum.nil?
|
log file.inspect if file.checksum.nil?
|
||||||
|
|
||||||
if digest.hexdigest.upcase == file.checksum.upcase
|
if digest.hexdigest.upcase == file.checksum.upcase
|
||||||
accepted_files[safe_file_name] = manifest.version
|
accepted_files[safe_file_name] = manifest.version
|
||||||
# puts "[#{manifest.version}] Verified file: #{file_path}"
|
log "[#{manifest.version}] Verified file: #{file_path}"
|
||||||
else
|
else
|
||||||
rejected_files << { file: file, manifest_version: manifest.version }
|
rejected_files << { file: file, manifest_version: manifest.version }
|
||||||
puts "[#{manifest.version}] File failed Verification: #{file_path}"
|
log "[#{manifest.version}] File failed Verification: #{file_path}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "#{rejected_files.count} missing or corrupt files"
|
log "#{rejected_files.count} missing or corrupt files"
|
||||||
|
|
||||||
# TODO: Filter packages to only the required ones
|
|
||||||
selected_packages = []
|
selected_packages = []
|
||||||
selected_packages_hash = {}
|
selected_packages_hash = {}
|
||||||
|
|
||||||
@@ -310,10 +317,8 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# FIXME: Order `selected_packages` like `packages`
|
|
||||||
|
|
||||||
# Removed packages that don't need to be fetched or processed
|
# Removed packages that don't need to be fetched or processed
|
||||||
packages.delete_if { |package| !selected_packages.find { |pkg| pkg == package } }
|
packages.delete_if { |package| !selected_packages.find { |pkg| pkg == package } } if folder_exists
|
||||||
|
|
||||||
packages
|
packages
|
||||||
end
|
end
|
||||||
@@ -421,7 +426,7 @@ class W3DHub
|
|||||||
|
|
||||||
def unpack_packages(packages)
|
def unpack_packages(packages)
|
||||||
path = Cache.install_path(@application, @channel)
|
path = Cache.install_path(@application, @channel)
|
||||||
puts "Unpacking packages in '#{path}'..."
|
log "Unpacking packages in '#{path}'..."
|
||||||
Cache.create_directories(path, true)
|
Cache.create_directories(path, true)
|
||||||
|
|
||||||
@status.operations.clear
|
@status.operations.clear
|
||||||
@@ -468,7 +473,7 @@ class W3DHub
|
|||||||
|
|
||||||
update_interface_task_status
|
update_interface_task_status
|
||||||
else
|
else
|
||||||
puts "COMMAND FAILED!"
|
log "COMMAND FAILED!"
|
||||||
fail!("Failed to unpack #{package.name}")
|
fail!("Failed to unpack #{package.name}")
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -508,7 +513,7 @@ class W3DHub
|
|||||||
|
|
||||||
@status.step = :mark_application_installed
|
@status.step = :mark_application_installed
|
||||||
|
|
||||||
puts "#{@app_id} has been installed."
|
log "#{@app_id} has been installed."
|
||||||
end
|
end
|
||||||
|
|
||||||
#############
|
#############
|
||||||
@@ -540,7 +545,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def package_fetch(package, &block)
|
def package_fetch(package, &block)
|
||||||
puts "Downloading: #{package.category}:#{package.subcategory}:#{package.name}-#{package.version}"
|
log "Downloading: #{package.category}:#{package.subcategory}:#{package.name}-#{package.version}"
|
||||||
|
|
||||||
Api.package(package) do |chunk, remaining_bytes, total_bytes|
|
Api.package(package) do |chunk, remaining_bytes, total_bytes|
|
||||||
block&.call(chunk, remaining_bytes, total_bytes)
|
block&.call(chunk, remaining_bytes, total_bytes)
|
||||||
@@ -548,7 +553,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def verify_package(package, &block)
|
def verify_package(package, &block)
|
||||||
puts "Verifying: #{package.category}:#{package.subcategory}:#{package.name}-#{package.version}"
|
log "Verifying: #{package.category}:#{package.subcategory}:#{package.name}-#{package.version}"
|
||||||
|
|
||||||
digest = Digest::SHA256.new
|
digest = Digest::SHA256.new
|
||||||
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
@@ -559,7 +564,7 @@ class W3DHub
|
|||||||
operation&.value = "Verifying..."
|
operation&.value = "Verifying..."
|
||||||
|
|
||||||
file_size = File.size(path)
|
file_size = File.size(path)
|
||||||
puts " File size: #{file_size}"
|
log " File size: #{file_size}"
|
||||||
chunk_size = package.checksum_chunk_size
|
chunk_size = package.checksum_chunk_size
|
||||||
chunks = package.checksum_chunks.size
|
chunks = package.checksum_chunks.size
|
||||||
|
|
||||||
@@ -584,11 +589,11 @@ class W3DHub
|
|||||||
|
|
||||||
if Digest::SHA256.new.hexdigest(chunk).upcase == checksum.upcase
|
if Digest::SHA256.new.hexdigest(chunk).upcase == checksum.upcase
|
||||||
valid_at = chunk_start + read_length
|
valid_at = chunk_start + read_length
|
||||||
# puts " Passed chunk: #{chunk_start}"
|
# log " Passed chunk: #{chunk_start}"
|
||||||
# package.partially_valid_at_bytes = valid_at
|
# package.partially_valid_at_bytes = valid_at
|
||||||
package.partially_valid_at_bytes = chunk_start
|
package.partially_valid_at_bytes = chunk_start
|
||||||
else
|
else
|
||||||
puts " FAILED chunk: #{chunk_start}"
|
log " FAILED chunk: #{chunk_start}"
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -602,25 +607,25 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def unpack_package(package, path)
|
def unpack_package(package, path)
|
||||||
puts " #{package.name}:#{package.version}"
|
log " #{package.name}:#{package.version}"
|
||||||
package_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
package_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
|
|
||||||
puts " Running #{W3DHub.tar_command} command: #{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{path}\""
|
log " Running #{W3DHub.tar_command} command: #{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{path}\""
|
||||||
return system("#{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{path}\"")
|
return system("#{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{path}\"")
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_patch(package, path)
|
def apply_patch(package, path)
|
||||||
puts " #{package.name}:#{package.version}"
|
log " #{package.name}:#{package.version}"
|
||||||
package_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
package_path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
temp_path = "#{Store.settings[:package_cache_dir]}/temp"
|
temp_path = "#{Store.settings[:package_cache_dir]}/temp"
|
||||||
manifest_file = package.custom_is_patch
|
manifest_file = package.custom_is_patch
|
||||||
|
|
||||||
Cache.create_directories(temp_path, true)
|
Cache.create_directories(temp_path, true)
|
||||||
|
|
||||||
puts " Running #{W3DHub.tar_command} command: #{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{temp_path}\""
|
log " Running #{W3DHub.tar_command} command: #{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{temp_path}\""
|
||||||
system("#{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{temp_path}\"")
|
system("#{W3DHub.tar_command} -xf \"#{package_path}\" -C \"#{temp_path}\"")
|
||||||
|
|
||||||
puts " Loading #{temp_path}/#{manifest_file.name}.patch..."
|
log " Loading #{temp_path}/#{manifest_file.name}.patch..."
|
||||||
patch_mix = W3DHub::Mixer::Reader.new(file_path: "#{temp_path}/#{manifest_file.name}.patch", ignore_crc_mismatches: false)
|
patch_mix = W3DHub::Mixer::Reader.new(file_path: "#{temp_path}/#{manifest_file.name}.patch", ignore_crc_mismatches: false)
|
||||||
patch_info = JSON.parse(patch_mix.package.files.find { |f| f.name == ".w3dhub.patch" || f.name == ".bhppatch" }.data, symbolize_names: true)
|
patch_info = JSON.parse(patch_mix.package.files.find { |f| f.name == ".w3dhub.patch" || f.name == ".bhppatch" }.data, symbolize_names: true)
|
||||||
|
|
||||||
@@ -628,15 +633,15 @@ class W3DHub
|
|||||||
# Fix borked data -> Data 'cause Windows don't care about capitalization
|
# Fix borked data -> Data 'cause Windows don't care about capitalization
|
||||||
repaired_path = "#{path}/#{manifest_file.name.sub('data', 'Data')}" unless File.exist?(repaired_path) && path
|
repaired_path = "#{path}/#{manifest_file.name.sub('data', 'Data')}" unless File.exist?(repaired_path) && path
|
||||||
|
|
||||||
puts " Loading #{repaired_path}..."
|
log " Loading #{repaired_path}..."
|
||||||
target_mix = W3DHub::Mixer::Reader.new(file_path: repaired_path, ignore_crc_mismatches: false)
|
target_mix = W3DHub::Mixer::Reader.new(file_path: repaired_path, ignore_crc_mismatches: false)
|
||||||
|
|
||||||
puts " Removing files..." if patch_info[:removedFiles].size.positive?
|
log " Removing files..." if patch_info[:removedFiles].size.positive?
|
||||||
patch_info[:removedFiles].each do |file|
|
patch_info[:removedFiles].each do |file|
|
||||||
target_mix.package.files.delete_if { |f| f.name == file }
|
target_mix.package.files.delete_if { |f| f.name == file }
|
||||||
end
|
end
|
||||||
|
|
||||||
puts " Adding/Updating files..." if patch_info[:updatedFiles].size.positive?
|
log " Adding/Updating files..." if patch_info[:updatedFiles].size.positive?
|
||||||
patch_info[:updatedFiles].each do |file|
|
patch_info[:updatedFiles].each do |file|
|
||||||
patch = patch_mix.package.files.find { |f| f.name == file }
|
patch = patch_mix.package.files.find { |f| f.name == file }
|
||||||
target = target_mix.package.files.find { |f| f.name == file }
|
target = target_mix.package.files.find { |f| f.name == file }
|
||||||
@@ -649,7 +654,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
puts " Writing updated #{repaired_path}..." if patch_info[:updatedFiles].size.positive?
|
log " Writing updated #{repaired_path}..." if patch_info[:updatedFiles].size.positive?
|
||||||
W3DHub::Mixer::Writer.new(file_path: repaired_path, package: target_mix.package, memory_buffer: true)
|
W3DHub::Mixer::Writer.new(file_path: repaired_path, package: target_mix.package, memory_buffer: true)
|
||||||
|
|
||||||
FileUtils.remove_dir(temp_path)
|
FileUtils.remove_dir(temp_path)
|
||||||
@@ -666,7 +671,7 @@ class W3DHub
|
|||||||
# Force data/ to Data/
|
# Force data/ to Data/
|
||||||
return true unless File.exist?("#{path}/data") && File.directory?("#{path}/data")
|
return true unless File.exist?("#{path}/data") && File.directory?("#{path}/data")
|
||||||
|
|
||||||
puts " Moving #{path}/data/ to #{path}/Data/"
|
log " Moving #{path}/data/ to #{path}/Data/"
|
||||||
|
|
||||||
FileUtils.mv(Dir.glob("#{path}/data/**"), "#{path}/Data", force: true)
|
FileUtils.mv(Dir.glob("#{path}/data/**"), "#{path}/Data", force: true)
|
||||||
FileUtils.remove_dir("#{path}/data", force: true)
|
FileUtils.remove_dir("#{path}/data", force: true)
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ class W3DHub
|
|||||||
@status.value = "Purging installation folder..."
|
@status.value = "Purging installation folder..."
|
||||||
@status.progress = Float::INFINITY
|
@status.progress = Float::INFINITY
|
||||||
|
|
||||||
|
@status.step = :uninstalling_application
|
||||||
|
|
||||||
path = Cache.install_path(@application, @channel)
|
path = Cache.install_path(@application, @channel)
|
||||||
|
|
||||||
puts path
|
log path
|
||||||
# TODO: Do some sanity checking, i.e. DO NOT start launcher if `whoami` returns root, path makes sense,
|
# TODO: Do some sanity checking, i.e. DO NOT start launcher if `whoami` returns root, path makes sense,
|
||||||
# we're not on Windows trying to uninstall a game likely installed by the official launcher
|
# we're not on Windows trying to uninstall a game likely installed by the official launcher
|
||||||
FileUtils.remove_dir(path)
|
FileUtils.remove_dir(path)
|
||||||
@@ -51,7 +53,7 @@ class W3DHub
|
|||||||
|
|
||||||
@status.step = :mark_application_uninstalled
|
@status.step = :mark_application_uninstalled
|
||||||
|
|
||||||
puts "#{@app_id} has been uninstalled."
|
log "#{@app_id} has been uninstalled."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
81
lib/background_worker.rb
Normal file
81
lib/background_worker.rb
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
class W3DHub
|
||||||
|
class BackgroundWorker
|
||||||
|
@@instance = nil
|
||||||
|
@@alive = false
|
||||||
|
|
||||||
|
def self.create
|
||||||
|
raise "BackgroundWorker instance already exists!" if @@instance
|
||||||
|
|
||||||
|
@@alive = true
|
||||||
|
@@instance = self.new
|
||||||
|
|
||||||
|
Async do
|
||||||
|
@@instance.handle_jobs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.instance
|
||||||
|
@@instance
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.alive?
|
||||||
|
@@alive
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.shutdown!
|
||||||
|
@@alive = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.job(job, callback)
|
||||||
|
@@instance.add_job(Job.new(job, callback))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.foreground_job(job, callback)
|
||||||
|
@@instance.add_job(Job.new(job, callback, true))
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@jobs = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_jobs
|
||||||
|
while BackgroundWorker.alive?
|
||||||
|
job = @jobs.shift
|
||||||
|
|
||||||
|
begin
|
||||||
|
job&.do
|
||||||
|
rescue => e
|
||||||
|
pp e
|
||||||
|
end
|
||||||
|
|
||||||
|
sleep 0.1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_job(job)
|
||||||
|
@jobs << job
|
||||||
|
end
|
||||||
|
|
||||||
|
class Job
|
||||||
|
def initialize(job, callback, deliver_to_queue = false)
|
||||||
|
@job = job
|
||||||
|
@callback = callback
|
||||||
|
|
||||||
|
@deliver_to_queue = deliver_to_queue
|
||||||
|
end
|
||||||
|
|
||||||
|
def do
|
||||||
|
result = @job.call
|
||||||
|
deliver(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
def deliver(result)
|
||||||
|
if @deliver_to_queue
|
||||||
|
$window.main_thread_queue << -> { @callback.call(result) }
|
||||||
|
else
|
||||||
|
@callback.call(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
17
lib/cache.rb
17
lib/cache.rb
@@ -13,17 +13,10 @@ class W3DHub
|
|||||||
if !force_fetch && File.exist?(path)
|
if !force_fetch && File.exist?(path)
|
||||||
path
|
path
|
||||||
else
|
else
|
||||||
internet = Async::HTTP::Internet.instance
|
BackgroundWorker.job(
|
||||||
|
-> { Async::HTTP::Internet.instance.get(uri, W3DHub::Api::DEFAULT_HEADERS) },
|
||||||
response = internet.get(uri, W3DHub::Api::DEFAULT_HEADERS)
|
->(response) { response.save(path, "wb") if response.success? }
|
||||||
|
)
|
||||||
if response.success?
|
|
||||||
response.save(path, "wb")
|
|
||||||
|
|
||||||
return path
|
|
||||||
end
|
|
||||||
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -119,7 +112,7 @@ class W3DHub
|
|||||||
tcp_nodelay: true,
|
tcp_nodelay: true,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: "data=#{JSON.dump({ category: package.category, subcategory: package.subcategory, name: package.name, version: package.version })}",
|
body: "data=#{JSON.dump({ category: package.category, subcategory: package.subcategory, name: package.name, version: package.version })}",
|
||||||
chunk_size: 4_000_000,
|
chunk_size: 50_000,
|
||||||
response_block: streamer
|
response_block: streamer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -60,26 +60,21 @@ class W3DHub
|
|||||||
para I18n.t(:"games.fetching_news"), padding: 8
|
para I18n.t(:"games.fetching_news"), padding: 8
|
||||||
end
|
end
|
||||||
|
|
||||||
Async do
|
BackgroundWorker.foreground_job(-> { fetch_w3dhub_news }, ->(result) { populate_w3dhub_news })
|
||||||
internet = Async::HTTP::Internet.instance
|
|
||||||
|
|
||||||
fetch_w3dhub_news
|
|
||||||
populate_w3dhub_news
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_w3dhub_news
|
def fetch_w3dhub_news
|
||||||
news = Api.news("launcher-home")
|
news = Api.news("launcher-home")
|
||||||
|
|
||||||
if news
|
return unless news
|
||||||
|
|
||||||
news.items[0..9].each do |item|
|
news.items[0..9].each do |item|
|
||||||
Cache.fetch(item.image)
|
Cache.fetch(item.image)
|
||||||
end
|
end
|
||||||
|
|
||||||
@w3dhub_news = news
|
@w3dhub_news = news
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def populate_w3dhub_news
|
def populate_w3dhub_news
|
||||||
return unless @w3dhub_news
|
return unless @w3dhub_news
|
||||||
|
|||||||
@@ -171,24 +171,21 @@ class W3DHub
|
|||||||
title I18n.t(:"games.fetching_news"), padding: 8
|
title I18n.t(:"games.fetching_news"), padding: 8
|
||||||
end
|
end
|
||||||
|
|
||||||
Async do
|
BackgroundWorker.foreground_job(-> { fetch_game_news(game) }, ->(result) { populate_game_news(game) })
|
||||||
fetch_game_news(game)
|
|
||||||
populate_game_news(game)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_game_news(game)
|
def fetch_game_news(game)
|
||||||
news = Api.news(game.id)
|
news = Api.news(game.id)
|
||||||
|
|
||||||
if news
|
return unless news
|
||||||
|
|
||||||
news.items[0..9].each do |item|
|
news.items[0..9].each do |item|
|
||||||
Cache.fetch(item.image)
|
Cache.fetch(item.image)
|
||||||
end
|
end
|
||||||
|
|
||||||
@game_news[game.id] = news
|
@game_news[game.id] = news
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def populate_game_news(game)
|
def populate_game_news(game)
|
||||||
return unless @focused_game == game
|
return unless @focused_game == game
|
||||||
|
|||||||
@@ -36,19 +36,27 @@ class W3DHub
|
|||||||
|
|
||||||
# Do network stuff
|
# Do network stuff
|
||||||
|
|
||||||
Async do
|
BackgroundWorker.foreground_job(
|
||||||
|
lambda do
|
||||||
account = Api.user_login(@username.value, @password.value)
|
account = Api.user_login(@username.value, @password.value)
|
||||||
|
applications = nil
|
||||||
|
|
||||||
if account
|
if account
|
||||||
Store.account = account
|
Store.account = account
|
||||||
Store.settings[:account][:data] = account
|
Store.settings[:account][:data] = account
|
||||||
Store.settings.save_settings
|
Store.settings.save_settings
|
||||||
|
|
||||||
internet = Async::HTTP::Internet.instance
|
Cache.fetch(account.avatar_uri, true) if account
|
||||||
Cache.fetch(account.avatar_uri, true)
|
applications = Api.applications if account
|
||||||
|
end
|
||||||
|
|
||||||
|
[account, applications]
|
||||||
|
end,
|
||||||
|
lambda do |result|
|
||||||
|
account, applications = result
|
||||||
|
|
||||||
|
if account
|
||||||
populate_account_info
|
populate_account_info
|
||||||
applications = Api.applications
|
|
||||||
Store.applications = applications if applications
|
Store.applications = applications if applications
|
||||||
|
|
||||||
page(W3DHub::Pages::Games)
|
page(W3DHub::Pages::Games)
|
||||||
@@ -62,6 +70,7 @@ class W3DHub
|
|||||||
@error_label.value = "Incorrect username or password.\nOr too many failed login attempts, try again in a few minutes."
|
@error_label.value = "Incorrect username or password.\nOr too many failed login attempts, try again in a few minutes."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@error_label = caption "", width: 1.0, text_align: :center, color: 0xff_800000
|
@error_label = caption "", width: 1.0, text_align: :center, color: 0xff_800000
|
||||||
@@ -71,12 +80,13 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
if Store.account
|
if Store.account
|
||||||
Async do
|
BackgroundWorker.foreground_job(
|
||||||
Cache.fetch(Store.account.avatar_uri)
|
-> { Cache.fetch(Store.account.avatar_uri) },
|
||||||
|
->(result) {
|
||||||
populate_account_info
|
populate_account_info
|
||||||
page(W3DHub::Pages::Games)
|
page(W3DHub::Pages::Games)
|
||||||
end
|
}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -103,8 +113,14 @@ class W3DHub
|
|||||||
Store.settings.save_settings
|
Store.settings.save_settings
|
||||||
Store.account = nil
|
Store.account = nil
|
||||||
|
|
||||||
applications = Api.applications
|
BackgroundWorker.foreground_job(
|
||||||
Store.applications if applications
|
-> { Api.applications },
|
||||||
|
lambda do |applications|
|
||||||
|
if applications
|
||||||
|
Store.applications = applications
|
||||||
|
page(W3DHub::Pages::Games) if @host.current_page.is_a?(W3DHub::Pages::Games)
|
||||||
|
page(W3DHub::Pages::ServerBrowser) if @host.current_page.is_a?(W3DHub::Pages::ServerBrowser)
|
||||||
|
end
|
||||||
|
|
||||||
@host.instance_variable_get(:"@account_container").clear do
|
@host.instance_variable_get(:"@account_container").clear do
|
||||||
stack(width: 0.7, height: 1.0) do
|
stack(width: 0.7, height: 1.0) do
|
||||||
@@ -120,6 +136,8 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class W3DHub
|
|||||||
flow(width: 0.75, height: 1.0) do
|
flow(width: 0.75, height: 1.0) do
|
||||||
@filters.each do |app_id, enabled|
|
@filters.each do |app_id, enabled|
|
||||||
app = Store.applications.games.find { |a| a.id == app_id.to_s }
|
app = Store.applications.games.find { |a| a.id == app_id.to_s }
|
||||||
|
next unless app
|
||||||
|
|
||||||
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{app_id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{app_id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png"
|
||||||
|
|
||||||
@@ -130,11 +131,14 @@ class W3DHub
|
|||||||
populate_server_list
|
populate_server_list
|
||||||
|
|
||||||
if @selected_server&.id == @refresh_server&.id
|
if @selected_server&.id == @refresh_server&.id
|
||||||
Async do
|
if @refresh_server
|
||||||
fetch_server_details(@refresh_server) if @refresh_server
|
BackgroundWorker.foreground_job(
|
||||||
populate_server_info(@refresh_server) if @refresh_server && @refresh_server == @selected_server
|
-> { fetch_server_details(@refresh_server) },
|
||||||
|
->(result) {
|
||||||
|
populate_server_info(@refresh_server) if @refresh_server == @selected_server
|
||||||
@refresh_server = nil
|
@refresh_server = nil
|
||||||
|
}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -222,10 +226,10 @@ class W3DHub
|
|||||||
|
|
||||||
@selected_server = server
|
@selected_server = server
|
||||||
|
|
||||||
Async do
|
BackgroundWorker.foreground_job(
|
||||||
fetch_server_details(server)
|
-> { fetch_server_details(server) },
|
||||||
populate_server_info(server) if server == @selected_server
|
->(result) { populate_server_info(server) if server == @selected_server }
|
||||||
end
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
stylize_selected_server(server_container) if server.id == @selected_server&.id
|
stylize_selected_server(server_container) if server.id == @selected_server&.id
|
||||||
@@ -352,12 +356,10 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fetch_server_details(server)
|
def fetch_server_details(server)
|
||||||
Async do
|
BackgroundWorker.foreground_job(
|
||||||
internet = Async::HTTP::Internet.instance
|
-> { Api.server_details(server.id, 2) },
|
||||||
|
->(server_data) { server.update(server_data) if server_data }
|
||||||
server_data = Api.server_details(internet, server.id, 2)
|
)
|
||||||
server.update(server_data) if server_data
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def game_icon(server)
|
def game_icon(server)
|
||||||
|
|||||||
@@ -31,17 +31,10 @@ class W3DHub
|
|||||||
inscription "#{I18n.t(:app_name)} #{W3DHub::VERSION}", width: 0.5, text_align: :right
|
inscription "#{I18n.t(:app_name)} #{W3DHub::VERSION}", width: 0.5, text_align: :right
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Async do
|
|
||||||
@tasks.keys.each do |key|
|
|
||||||
Sync do
|
|
||||||
send(key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw
|
def draw
|
||||||
|
Gosu.draw_circle(window.width / 2, window.height / 2, @w3dhub_logo.width * (0.6 + Math.cos(Gosu.milliseconds / 1000.0 * Math::PI).abs * 0.05), 128, 0x44_000000, 32)
|
||||||
@w3dhub_logo.draw_rot(window.width / 2, window.height / 2, 32)
|
@w3dhub_logo.draw_rot(window.width / 2, window.height / 2, 32)
|
||||||
|
|
||||||
super
|
super
|
||||||
@@ -50,13 +43,19 @@ class W3DHub
|
|||||||
def update
|
def update
|
||||||
super
|
super
|
||||||
|
|
||||||
# @fraction += 1.0 * window.dt
|
|
||||||
@fraction = 1.0 / (@tasks.size / @task_index.to_f)
|
@fraction = 1.0 / (@tasks.size / @task_index.to_f)
|
||||||
|
|
||||||
@progressbar.value = @fraction
|
@progressbar.value = @fraction
|
||||||
|
|
||||||
push_state(States::Interface) if @progressbar.value >= 1.0 && @task_index == @tasks.size
|
push_state(States::Interface) if @progressbar.value >= 1.0 && @task_index == @tasks.size
|
||||||
|
|
||||||
|
task = @tasks[@tasks.keys[@task_index]]
|
||||||
|
|
||||||
|
if task && !task[:started]
|
||||||
|
task[:started] = true
|
||||||
|
send(@tasks.keys[@task_index])
|
||||||
|
end
|
||||||
|
|
||||||
@task_index += 1 if @tasks.dig(@tasks.keys[@task_index], :complete)
|
@task_index += 1 if @tasks.dig(@tasks.keys[@task_index], :complete)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -66,17 +65,27 @@ class W3DHub
|
|||||||
|
|
||||||
if (account.access_token_expiry - Time.now) / 60 <= 60 * 3 # Refresh if token expires within 3 hours
|
if (account.access_token_expiry - Time.now) / 60 <= 60 * 3 # Refresh if token expires within 3 hours
|
||||||
puts "Refreshing user login..."
|
puts "Refreshing user login..."
|
||||||
@account = Api.refresh_user_login(account.refresh_token)
|
|
||||||
else
|
Api.on_fiber(:refresh_user_login, account.refresh_token) do |refreshed_account|
|
||||||
@account = account
|
update_account_data(refreshed_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
if @account
|
else
|
||||||
Store.account = @account
|
update_account_data(account)
|
||||||
|
end
|
||||||
|
|
||||||
Store.settings[:account][:data] = @account
|
else
|
||||||
|
@tasks[:refresh_user_token][:complete] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Cache.fetch(@account.avatar_uri, true)
|
def update_account_data(account)
|
||||||
|
if account
|
||||||
|
Store.account = account
|
||||||
|
|
||||||
|
Store.settings[:account][:data] = account
|
||||||
|
|
||||||
|
Cache.fetch(account.avatar_uri, true)
|
||||||
else
|
else
|
||||||
Store.settings[:account] = {}
|
Store.settings[:account] = {}
|
||||||
end
|
end
|
||||||
@@ -84,13 +93,11 @@ class W3DHub
|
|||||||
Store.settings.save_settings
|
Store.settings.save_settings
|
||||||
|
|
||||||
@tasks[:refresh_user_token][:complete] = true
|
@tasks[:refresh_user_token][:complete] = true
|
||||||
else
|
|
||||||
@tasks[:refresh_user_token][:complete] = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def service_status
|
def service_status
|
||||||
@service_status = Api.service_status
|
Api.on_fiber(:service_status) do |service_status|
|
||||||
|
@service_status = service_status
|
||||||
|
|
||||||
if @service_status
|
if @service_status
|
||||||
Store.service_status = @service_status
|
Store.service_status = @service_status
|
||||||
@@ -104,18 +111,20 @@ class W3DHub
|
|||||||
@status_label.value = I18n.t(:"boot.w3dhub_service_is_down")
|
@status_label.value = I18n.t(:"boot.w3dhub_service_is_down")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def applications
|
def applications
|
||||||
@status_label.value = I18n.t(:"boot.checking_for_updates")
|
@status_label.value = I18n.t(:"boot.checking_for_updates")
|
||||||
|
|
||||||
@applications = Api.applications
|
Api.on_fiber(:applications) do |applications|
|
||||||
|
if applications
|
||||||
if @applications
|
Store.applications = applications
|
||||||
Store.applications = @applications
|
|
||||||
|
|
||||||
@tasks[:applications][:complete] = true
|
@tasks[:applications][:complete] = true
|
||||||
else
|
else
|
||||||
# FIXME: Failed to retreive!
|
# FIXME: Failed to retreive!
|
||||||
|
@status_label.value = "FAILED TO RETREIVE APPS LIST"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -127,8 +136,8 @@ class W3DHub
|
|||||||
packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" }
|
packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" }
|
||||||
end
|
end
|
||||||
|
|
||||||
if (package_details = Api.package_details(packages))
|
Api.on_fiber(:package_details, packages) do |package_details|
|
||||||
package_details.each do |package|
|
package_details&.each do |package|
|
||||||
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
generated_icon_path = "#{GAME_ROOT_PATH}/media/icons/#{package.subcategory}.png"
|
generated_icon_path = "#{GAME_ROOT_PATH}/media/icons/#{package.subcategory}.png"
|
||||||
|
|
||||||
@@ -145,37 +154,27 @@ class W3DHub
|
|||||||
|
|
||||||
if regenerate
|
if regenerate
|
||||||
ico = ICO.new(file: path)
|
ico = ICO.new(file: path)
|
||||||
image = ico.images.sort_by(&:width).last
|
image = ico.images.max_by(&:width)
|
||||||
|
|
||||||
ico.save(image, generated_icon_path)
|
ico.save(image, generated_icon_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
@tasks[:app_icons][:complete] = true
|
@tasks[:app_icons][:complete] = true
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def server_list
|
def server_list
|
||||||
@status_label.value = I18n.t(:"server_browser.fetching_server_list")
|
@status_label.value = I18n.t(:"server_browser.fetching_server_list")
|
||||||
|
|
||||||
begin
|
Api.on_fiber(:server_list, 2) do |list|
|
||||||
internet = Async::HTTP::Internet.instance
|
Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse if list
|
||||||
|
|
||||||
list = Api.server_list(internet, 2)
|
|
||||||
|
|
||||||
if list
|
|
||||||
Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse
|
|
||||||
end
|
|
||||||
|
|
||||||
Store.server_list_last_fetch = Gosu.milliseconds
|
Store.server_list_last_fetch = Gosu.milliseconds
|
||||||
|
|
||||||
Api::ServerListUpdater.instance
|
Api::ServerListUpdater.instance
|
||||||
|
|
||||||
@tasks[:server_list][:complete] = true
|
@tasks[:server_list][:complete] = true
|
||||||
rescue => e
|
|
||||||
# Something went wrong!
|
|
||||||
pp e
|
|
||||||
Store.server_list = []
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -158,6 +158,10 @@ class W3DHub
|
|||||||
@page.focus
|
@page.focus
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def current_page
|
||||||
|
@page
|
||||||
|
end
|
||||||
|
|
||||||
def update_server_browser(server)
|
def update_server_browser(server)
|
||||||
return unless @page.is_a?(Pages::ServerBrowser)
|
return unless @page.is_a?(Pages::ServerBrowser)
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ class W3DHub
|
|||||||
super
|
super
|
||||||
|
|
||||||
Store.application_manager.start_next_available_task if Store.application_manager.idle?
|
Store.application_manager.start_next_available_task if Store.application_manager.idle?
|
||||||
|
|
||||||
current = Async::Task.current?
|
|
||||||
current&.yield
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def gain_focus
|
def gain_focus
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ I18n.load_path << Dir[File.expand_path("locales") + "/*.yml"]
|
|||||||
I18n.default_locale = :en
|
I18n.default_locale = :en
|
||||||
|
|
||||||
class W3DHub
|
class W3DHub
|
||||||
|
W3DHUB_DEBUG = ARGV.join.include?("--debug")
|
||||||
|
|
||||||
GAME_ROOT_PATH = File.expand_path(".", __dir__)
|
GAME_ROOT_PATH = File.expand_path(".", __dir__)
|
||||||
CACHE_PATH = "#{GAME_ROOT_PATH}/data/cache"
|
CACHE_PATH = "#{GAME_ROOT_PATH}/data/cache"
|
||||||
SETTINGS_FILE_PATH = "#{GAME_ROOT_PATH}/data/settings.json"
|
SETTINGS_FILE_PATH = "#{GAME_ROOT_PATH}/data/settings.json"
|
||||||
@@ -45,6 +47,7 @@ require_relative "lib/settings"
|
|||||||
require_relative "lib/mixer"
|
require_relative "lib/mixer"
|
||||||
require_relative "lib/ico"
|
require_relative "lib/ico"
|
||||||
require_relative "lib/multicast_server"
|
require_relative "lib/multicast_server"
|
||||||
|
require_relative "lib/background_worker"
|
||||||
require_relative "lib/application_manager"
|
require_relative "lib/application_manager"
|
||||||
require_relative "lib/application_manager/manifest"
|
require_relative "lib/application_manager/manifest"
|
||||||
require_relative "lib/application_manager/status"
|
require_relative "lib/application_manager/status"
|
||||||
@@ -79,8 +82,8 @@ require_relative "lib/pages/login"
|
|||||||
require_relative "lib/pages/settings"
|
require_relative "lib/pages/settings"
|
||||||
require_relative "lib/pages/download_manager"
|
require_relative "lib/pages/download_manager"
|
||||||
|
|
||||||
Async do
|
Thread.new do
|
||||||
W3DHub::Window.new(width: 980, height: 720, borderless: false).show
|
W3DHub::BackgroundWorker.create
|
||||||
|
|
||||||
exit # ensure reactor is shutdown when window is closed
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
W3DHub::Window.new(width: 980, height: 720, borderless: false).show
|
||||||
|
|||||||
Reference in New Issue
Block a user