Replaced most Excon + Thread calls with Async

This commit is contained in:
2021-12-26 09:20:25 -06:00
parent f1e7d430b6
commit 2bbb2acc6e
11 changed files with 191 additions and 159 deletions

View File

@@ -6,4 +6,7 @@ gem "i18n"
gem "rexml" gem "rexml"
gem "digest-crc" gem "digest-crc"
gem "ffi" gem "ffi"
# gem "async-websocket" gem "async"
gem "async-http"
gem "async-websocket"
gem "thread-local"

View File

@@ -3,8 +3,29 @@ GEM
specs: specs:
addressable (2.8.0) addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
async (1.30.1)
console (~> 1.10)
nio4r (~> 2.3)
timers (~> 4.1)
async-http (0.56.5)
async (>= 1.25)
async-io (>= 1.28)
async-pool (>= 0.2)
protocol-http (~> 0.22.0)
protocol-http1 (~> 0.14.0)
protocol-http2 (~> 0.14.0)
async-io (1.32.2)
async
async-pool (0.3.9)
async (>= 1.25)
async-websocket (0.19.0)
async-http (~> 0.54)
async-io (~> 1.23)
protocol-websocket (~> 0.7.0)
clipboard (1.3.6) clipboard (1.3.6)
concurrent-ruby (1.1.9) concurrent-ruby (1.1.9)
console (1.14.0)
fiber-local
cyberarm_engine (0.19.1) cyberarm_engine (0.19.1)
clipboard (~> 1.3.5) clipboard (~> 1.3.5)
excon (~> 0.78.0) excon (~> 0.78.0)
@@ -14,27 +35,46 @@ GEM
rake (>= 12.0.0, < 14.0.0) rake (>= 12.0.0, < 14.0.0)
excon (0.78.1) excon (0.78.1)
ffi (1.15.4) ffi (1.15.4)
ffi (1.15.4-x64-mingw32)
fiber-local (1.0.0)
gosu (1.2.0) gosu (1.2.0)
gosu_more_drawables (0.3.1) gosu_more_drawables (0.3.1)
i18n (1.8.11) i18n (1.8.11)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
launchy (2.5.0) launchy (2.5.0)
addressable (~> 2.7) addressable (~> 2.7)
nio4r (2.5.8)
protocol-hpack (1.4.2)
protocol-http (0.22.5)
protocol-http1 (0.14.2)
protocol-http (~> 0.22)
protocol-http2 (0.14.2)
protocol-hpack (~> 1.4)
protocol-http (~> 0.18)
protocol-websocket (0.7.5)
protocol-http (~> 0.2)
protocol-http1 (~> 0.2)
public_suffix (4.0.6) public_suffix (4.0.6)
rake (13.0.6) rake (13.0.6)
rexml (3.2.5) rexml (3.2.5)
thread-local (1.1.0)
timers (4.3.3)
PLATFORMS PLATFORMS
x64-mingw32 x64-mingw32
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
async
async-http
async-websocket
cyberarm_engine cyberarm_engine
digest-crc digest-crc
ffi ffi
i18n i18n
launchy launchy
rexml rexml
thread-local
BUNDLED WITH BUNDLED WITH
2.2.28 2.2.28

View File

@@ -1,14 +1,18 @@
class W3DHub class W3DHub
class Api class Api
USER_AGENT = "Cyberarm's Linux Friendly W3D Hub Launcher v#{W3DHub::VERSION}" USER_AGENT = "Cyberarm's Linux Friendly W3D Hub Launcher v#{W3DHub::VERSION}".freeze
DEFAULT_HEADERS = { DEFAULT_HEADERS = [
"User-Agent": USER_AGENT ["User-Agent", USER_AGENT]
} ].freeze
FORM_ENCODED_HEADERS = (
DEFAULT_HEADERS + [["Content-Type", "application/x-www-form-urlencoded"]]
).freeze
#! === W3D Hub API === !# #! === W3D Hub API === !#
ENDPOINT = "https://secure.w3dhub.com" ENDPOINT = "https://secure.w3dhub.com".freeze
W3DHUB_API_CONNECTION = Excon.new(ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true) # W3DHUB_API_CONNECTION = Excon.new(ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true)
# Method: POST # Method: POST
# FORMAT: JSON # FORMAT: JSON
@@ -24,26 +28,20 @@ class W3DHub
# #
# On a failed login the service responds with: # On a failed login the service responds with:
# {"error":"login-failed"} # {"error":"login-failed"}
def self.refresh_user_login(refresh_token) def self.refresh_user_login(internet, refresh_token)
response = W3DHUB_API_CONNECTION.post( body = "data=#{JSON.dump({refreshToken: refresh_token})}"
path: "apis/launcher/1/user-login", response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body)
headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}),
body: "data=#{JSON.dump({refreshToken: refresh_token})}"
)
if response.status == 200 if response.success?#status == 200
user_data = JSON.parse(response.body, symbolize_names: true) user_data = JSON.parse(response.read, symbolize_names: true)
return false if user_data[:error] return false if user_data[:error]
user_details = W3DHUB_API_CONNECTION.post( body = "data=#{JSON.dump({ id: user_data[:userid] })}"
path: "apis/w3dhub/1/get-user-details", user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body)
headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}),
body: "data=#{JSON.dump({ id: user_data[:userid] })}"
)
if user_details.status == 200 if user_details.success?
user_details_data = JSON.parse(user_details.body, symbolize_names: true) user_details_data = JSON.parse(user_details.read, symbolize_names: true)
end end
return Account.new(user_data, user_details_data) return Account.new(user_data, user_details_data)
@@ -53,26 +51,20 @@ class W3DHub
end end
# See #user_refresh_token # See #user_refresh_token
def self.user_login(username, password) def self.user_login(internet, username, password)
response = W3DHUB_API_CONNECTION.post( body = "data=#{JSON.dump({username: username, password: password})}"
path: "apis/launcher/1/user-login", response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body)
headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}),
body: "data=#{JSON.dump({username: username, password: password})}"
)
if response.status == 200 if response.success?
user_data = JSON.parse(response.body, symbolize_names: true) user_data = JSON.parse(response.read, symbolize_names: true)
return false if user_data[:error] return false if user_data[:error]
user_details = W3DHUB_API_CONNECTION.post( body = "data=#{JSON.dump({ id: user_data[:userid] })}"
path: "apis/w3dhub/1/get-user-details", user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body)
headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}),
body: "data=#{JSON.dump({ id: user_data[:userid] })}"
)
if user_details.status == 200 if user_details.success?
user_details_data = JSON.parse(user_details.body, symbolize_names: true) user_details_data = JSON.parse(user_details.read, symbolize_names: true)
end end
return Account.new(user_data, user_details_data) return Account.new(user_data, user_details_data)
@@ -85,20 +77,17 @@ class W3DHub
# Client sends an Authorization header bearer token which is received from logging in (Required?) # Client sends an Authorization header bearer token which is received from logging in (Required?)
# #
# Response: avatar-uri (Image download uri), id, username # Response: avatar-uri (Image download uri), id, username
def self.user_details(id) def self.user_details(internetn, id)
end end
# /apis/w3dhub/1/get-service-status # /apis/w3dhub/1/get-service-status
# Service response: # Service response:
# {"services":{"authentication":true,"packageDownload":true}} # {"services":{"authentication":true,"packageDownload":true}}
def self.service_status def self.service_status(internet)
response = W3DHUB_API_CONNECTION.post( response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS)
path: "apis/w3dhub/1/get-service-status",
headers: DEFAULT_HEADERS
)
if response.status == 200 if response.success?
ServiceStatus.new(response.body) ServiceStatus.new(response.read)
else else
false false
end end
@@ -108,14 +97,11 @@ class W3DHub
# Client sends an Authorization header bearer token which is received from logging in (Optional) # Client sends an Authorization header bearer token which is received from logging in (Optional)
# Launcher sends an empty data request: data={} # Launcher sends an empty data request: data={}
# Response is a list of applications/games # Response is a list of applications/games
def self.applications def self.applications(internet)
response = W3DHUB_API_CONNECTION.post( response = internet.post("#{ENDPOINT}/apis/launcher/1/get-applications", DEFAULT_HEADERS)
path: "apis/launcher/1/get-applications",
headers: DEFAULT_HEADERS
)
if response.status == 200 if response.success?
Applications.new(response.body) Applications.new(response.read)
else else
false false
end end
@@ -125,15 +111,12 @@ class W3DHub
# Client sends an Authorization header bearer token which is received from logging in (Optional) # Client sends an Authorization header bearer token which is received from logging in (Optional)
# Client requests news for a specific application/game e.g.: data={"category":"ia"} ("launcher-home" retrieves the weekly hub updates) # Client requests news for a specific application/game e.g.: data={"category":"ia"} ("launcher-home" retrieves the weekly hub updates)
# Response is a JSON hash with a "highlighted" and "news" keys; the "news" one seems to be the desired one # Response is a JSON hash with a "highlighted" and "news" keys; the "news" one seems to be the desired one
def self.news(category) def self.news(internet, category)
response = W3DHUB_API_CONNECTION.post( body = "data=#{JSON.dump({category: category})}"
path: "apis/w3dhub/1/get-news", response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body)
headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}),
body: "data=#{JSON.dump({category: category})}"
)
if response.status == 200 if response.success?#status == 200
News.new(response.body) News.new(response.read)
else else
false false
end end
@@ -170,8 +153,9 @@ class W3DHub
#! === Server List API === !# #! === Server List API === !#
SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com" SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com".freeze
SERVER_LIST_CONNECTION = Excon.new(SERVER_LIST_ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true) # SERVER_LIST_CONNECTION = Excon.new(SERVER_LIST_ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true)
# Method: GET # Method: GET
# FORMAT: JSON # FORMAT: JSON
@@ -189,14 +173,11 @@ 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(level = 1) def self.server_list(internet, level = 1)
response = SERVER_LIST_CONNECTION.get( response = internet.get("#{SERVER_LIST_ENDPOINT}/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS)
path: "listings/getAll/v2?statusLevel=#{level}",
headers: DEFAULT_HEADERS
)
if response.status == 200 if response.success?
data = JSON.parse(response.body, symbolize_names: true) data = JSON.parse(response.read, symbolize_names: true)
return data.map { |hash| ServerListServer.new(hash) } return data.map { |hash| ServerListServer.new(hash) }
end end
@@ -214,7 +195,7 @@ 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(id, level) def self.server_details(internet, id, level)
end end
# /listings/push/v2/negotiate?negotiateVersion=1 # /listings/push/v2/negotiate?negotiateVersion=1

View File

@@ -7,18 +7,16 @@ class W3DHub
end end
# Fetch a generic uri # Fetch a generic uri
def self.fetch(uri) def self.fetch(internet, uri)
path = path(uri) path = path(uri)
if File.exist?(path) if File.exist?(path)
path path
else else
response = Excon.get(uri, tcp_nodelay: true) response = internet.get(uri, [["user-agent", W3DHub::Api::USER_AGENT]])
if response.status == 200 if response.success?#status == 200
File.open(path, "wb") do |f| response.save(path)
f.write(response.body)
end
path path
end end

View File

@@ -56,23 +56,25 @@ class W3DHub
if @w3dhub_news if @w3dhub_news
populate_w3dhub_news populate_w3dhub_news
else else
Thread.new do
fetch_w3dhub_news
main_thread_queue << proc { populate_w3dhub_news }
end
@wd3hub_news_container.clear do @wd3hub_news_container.clear do
para I18n.t(:"games.fetching_news"), padding: 8 para I18n.t(:"games.fetching_news"), padding: 8
end end
Async do
internet = Async::HTTP::Internet.instance
fetch_w3dhub_news(internet)
populate_w3dhub_news
end
end end
end end
def fetch_w3dhub_news def fetch_w3dhub_news(internet)
news = Api.news("launcher-home") news = Api.news(internet, "launcher-home")
if news if news
news.items[0..9].each do |item| news.items[0..9].each do |item|
Cache.fetch(item.image) Cache.fetch(internet, item.image)
end end
@w3dhub_news = news @w3dhub_news = news

View File

@@ -159,23 +159,25 @@ class W3DHub
if @game_news[game.id] if @game_news[game.id]
populate_game_news(game) populate_game_news(game)
else else
Thread.new do
fetch_game_news(game)
main_thread_queue << proc { populate_game_news(game) }
end
@game_news_container.clear do @game_news_container.clear do
title I18n.t(:"games.fetching_news"), padding: 8 title I18n.t(:"games.fetching_news"), padding: 8
end end
Async do
internet = Async::HTTP::Internet.instance
fetch_game_news(internet, game)
populate_game_news(game)
end
end end
end end
def fetch_game_news(game) def fetch_game_news(internet, game)
news = Api.news(game.id) news = Api.news(internet, game.id)
if news if news
news.items[0..9].each do |item| news.items[0..9].each do |item|
Cache.fetch(item.image) Cache.fetch(internet, item.image)
end end
@game_news[game.id] = news @game_news[game.id] = news

View File

@@ -36,27 +36,27 @@ class W3DHub
# Do network stuff # Do network stuff
Thread.new do Async do
account = Api.user_login(@username.value, @password.value) internet = Async::HTTP::Internet.instance
account = Api.user_login(internet, @username.value, @password.value)
if account if account
Store.account = account Store.account = account
Store.settings[:account][:refresh_token] = account.refresh_token Store.settings[:account][:refresh_token] = account.refresh_token
Store.settings.save_settings Store.settings.save_settings
Cache.fetch(account.avatar_uri) Cache.fetch(internet, account.avatar_uri)
main_thread_queue << proc { populate_account_info; page(W3DHub::Pages::Games) } populate_account_info; page(W3DHub::Pages::Games)
else else
# An error occurred, enable account entry # An error occurred, enable account entry
# NOTE: Too many incorrect entries causes lock out (Unknown duration) # NOTE: Too many incorrect entries causes lock out (Unknown duration)
main_thread_queue << proc do @username.enabled = true
@username.enabled = true @password.enabled = true
@password.enabled = true btn.enabled = true
btn.enabled = true
@error_label.value = "Incorrect username or password.\nOr too many failed login attempts." @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 end

View File

@@ -318,9 +318,11 @@ class W3DHub
return return
end end
Thread.new do Async do
internet = Async::HTTP::Internet.instance
begin begin
list = Api.server_list(2) list = Api.server_list(internet, 2)
if list if list
Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse

View File

@@ -28,6 +28,16 @@ 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
internet = Async::HTTP::Internet.instance
@tasks.keys.each do |key|
Sync do
send(key, internet)
end
end
end
end end
def draw def draw
@@ -52,86 +62,70 @@ class W3DHub
push_state(States::Interface) push_state(States::Interface)
end end
if @tasks.dig(@tasks.keys[@task_index], :started) == false
@tasks[@tasks.keys[@task_index]][: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
def refresh_user_token def refresh_user_token(internet)
if Store.settings[:account, :refresh_token] if Store.settings[:account, :refresh_token]
Thread.new do @account = Api.refresh_user_login(internet, Store.settings[:account, :refresh_token])
@account = Api.refresh_user_login(Store.settings[:account, :refresh_token])
if @account if @account
Store.settings[:account][:refresh_token] = @account.refresh_token Store.settings[:account][:refresh_token] = @account.refresh_token
else else
Store.settings[:account][:refresh_token] = nil Store.settings[:account][:refresh_token] = nil
end
Store.settings.save_settings
@tasks[:refresh_user_token][:complete] = true
end end
Store.settings.save_settings
@tasks[:refresh_user_token][:complete] = true
else else
@tasks[:refresh_user_token][:complete] = true @tasks[:refresh_user_token][:complete] = true
end end
end end
def service_status def service_status(internet)
Thread.new do @service_status = Api.service_status(internet)
@service_status = Api.service_status
if @service_status if @service_status
if !@service_status.authentication? || !@service_status.package_download? if !@service_status.authentication? || !@service_status.package_download?
# FIXME: MAIN THREAD! @status_label.value = "Authentication is #{@service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{@service_status.package_download? ? 'Okay' : 'Down'}."
@status_label.value = "Authentication is #{@service_status.authentication? ? 'Okay' : 'Down'}. Package Download is #{@service_status.package_download? ? 'Okay' : 'Down'}."
end
@tasks[:service_status][:complete] = true
else
# FIXME: MAIN THREAD!
@status_label.value = I18n.t(:"boot.w3dhub_service_is_down")
end end
@tasks[:service_status][:complete] = true
else
@status_label.value = I18n.t(:"boot.w3dhub_service_is_down")
end end
end end
def applications def applications(internet)
@status_label.value = I18n.t(:"boot.checking_for_updates") @status_label.value = I18n.t(:"boot.checking_for_updates")
Thread.new do @applications = Api.applications(internet)
@applications = Api.applications
if @applications if @applications
@tasks[:applications][:complete] = true @tasks[:applications][:complete] = true
else else
# FIXME: Failed to retreive! # FIXME: Failed to retreive!
end
end end
end end
def server_list def server_list(internet)
@status_label.value = I18n.t(:"server_browser.fetching_server_list") @status_label.value = I18n.t(:"server_browser.fetching_server_list")
Thread.new do begin
begin list = Api.server_list(internet, 2)
list = Api.server_list(2)
if list if list
Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse
end
Store.server_list_last_fetch = Gosu.milliseconds
@tasks[:server_list][:complete] = true
rescue => e
# Something went wrong!
pp e
Store.server_list = []
end end
Store.server_list_last_fetch = Gosu.milliseconds
@tasks[:server_list][:complete] = true
rescue => e
# Something went wrong!
pp e
Store.server_list = []
end end
end end
end end

View File

@@ -27,6 +27,9 @@ class W3DHub
Store.application_manager.start_next_available_task if Store.application_manager.idle? Store.application_manager.start_next_available_task if Store.application_manager.idle?
manage_update_interval manage_update_interval
current = Async::Task.current?
current&.yield
end end
def button_down(id) def button_down(id)

View File

@@ -13,6 +13,11 @@ require "rexml"
require "i18n" require "i18n"
require "launchy" require "launchy"
require "async"
require "async/barrier"
require "async/semaphore"
require "async/http/internet/instance"
I18n.load_path << Dir[File.expand_path("locales") + "/*.yml"] I18n.load_path << Dir[File.expand_path("locales") + "/*.yml"]
I18n.default_locale = :en I18n.default_locale = :en
@@ -66,4 +71,6 @@ 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"
W3DHub::Window.new(width: 980, height: 720, borderless: false).show Async do
W3DHub::Window.new(width: 980, height: 720, borderless: false).show
end