From 2bbb2acc6eea36d1043dc534143db9be2a3cf8da Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Sun, 26 Dec 2021 09:20:25 -0600 Subject: [PATCH] Replaced most Excon + Thread calls with Async --- Gemfile | 5 +- Gemfile.lock | 40 ++++++++++++ lib/api.rb | 119 +++++++++++++++--------------------- lib/cache.rb | 10 ++- lib/pages/community.rb | 18 +++--- lib/pages/games.rb | 18 +++--- lib/pages/login.rb | 20 +++--- lib/pages/server_browser.rb | 6 +- lib/states/boot.rb | 102 +++++++++++++++---------------- lib/window.rb | 3 + w3d_hub_linux_launcher.rb | 9 ++- 11 files changed, 191 insertions(+), 159 deletions(-) diff --git a/Gemfile b/Gemfile index e464be1..111bc4e 100644 --- a/Gemfile +++ b/Gemfile @@ -6,4 +6,7 @@ gem "i18n" gem "rexml" gem "digest-crc" gem "ffi" -# gem "async-websocket" +gem "async" +gem "async-http" +gem "async-websocket" +gem "thread-local" diff --git a/Gemfile.lock b/Gemfile.lock index e35b99e..9b522aa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,8 +3,29 @@ GEM specs: addressable (2.8.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) concurrent-ruby (1.1.9) + console (1.14.0) + fiber-local cyberarm_engine (0.19.1) clipboard (~> 1.3.5) excon (~> 0.78.0) @@ -14,27 +35,46 @@ GEM rake (>= 12.0.0, < 14.0.0) excon (0.78.1) ffi (1.15.4) + ffi (1.15.4-x64-mingw32) + fiber-local (1.0.0) gosu (1.2.0) gosu_more_drawables (0.3.1) i18n (1.8.11) concurrent-ruby (~> 1.0) launchy (2.5.0) 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) rake (13.0.6) rexml (3.2.5) + thread-local (1.1.0) + timers (4.3.3) PLATFORMS x64-mingw32 x86_64-linux DEPENDENCIES + async + async-http + async-websocket cyberarm_engine digest-crc ffi i18n launchy rexml + thread-local BUNDLED WITH 2.2.28 diff --git a/lib/api.rb b/lib/api.rb index 0574c60..eda836f 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -1,14 +1,18 @@ class W3DHub class Api - USER_AGENT = "Cyberarm's Linux Friendly W3D Hub Launcher v#{W3DHub::VERSION}" - DEFAULT_HEADERS = { - "User-Agent": USER_AGENT - } + USER_AGENT = "Cyberarm's Linux Friendly W3D Hub Launcher v#{W3DHub::VERSION}".freeze + DEFAULT_HEADERS = [ + ["User-Agent", USER_AGENT] + ].freeze + FORM_ENCODED_HEADERS = ( + DEFAULT_HEADERS + [["Content-Type", "application/x-www-form-urlencoded"]] + ).freeze #! === W3D Hub API === !# - ENDPOINT = "https://secure.w3dhub.com" - W3DHUB_API_CONNECTION = Excon.new(ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true) + ENDPOINT = "https://secure.w3dhub.com".freeze + # W3DHUB_API_CONNECTION = Excon.new(ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true) + # Method: POST # FORMAT: JSON @@ -24,26 +28,20 @@ class W3DHub # # On a failed login the service responds with: # {"error":"login-failed"} - def self.refresh_user_login(refresh_token) - response = W3DHUB_API_CONNECTION.post( - path: "apis/launcher/1/user-login", - headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}), - body: "data=#{JSON.dump({refreshToken: refresh_token})}" - ) + def self.refresh_user_login(internet, refresh_token) + body = "data=#{JSON.dump({refreshToken: refresh_token})}" + response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) - if response.status == 200 - user_data = JSON.parse(response.body, symbolize_names: true) + if response.success?#status == 200 + user_data = JSON.parse(response.read, symbolize_names: true) return false if user_data[:error] - user_details = W3DHUB_API_CONNECTION.post( - path: "apis/w3dhub/1/get-user-details", - headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}), - body: "data=#{JSON.dump({ id: user_data[:userid] })}" - ) + body = "data=#{JSON.dump({ id: user_data[:userid] })}" + user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) - if user_details.status == 200 - user_details_data = JSON.parse(user_details.body, symbolize_names: true) + if user_details.success? + user_details_data = JSON.parse(user_details.read, symbolize_names: true) end return Account.new(user_data, user_details_data) @@ -53,26 +51,20 @@ class W3DHub end # See #user_refresh_token - def self.user_login(username, password) - response = W3DHUB_API_CONNECTION.post( - path: "apis/launcher/1/user-login", - headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}), - body: "data=#{JSON.dump({username: username, password: password})}" - ) + def self.user_login(internet, username, password) + body = "data=#{JSON.dump({username: username, password: password})}" + response = internet.post("#{ENDPOINT}/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body) - if response.status == 200 - user_data = JSON.parse(response.body, symbolize_names: true) + if response.success? + user_data = JSON.parse(response.read, symbolize_names: true) return false if user_data[:error] - user_details = W3DHUB_API_CONNECTION.post( - path: "apis/w3dhub/1/get-user-details", - headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}), - body: "data=#{JSON.dump({ id: user_data[:userid] })}" - ) + body = "data=#{JSON.dump({ id: user_data[:userid] })}" + user_details = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body) - if user_details.status == 200 - user_details_data = JSON.parse(user_details.body, symbolize_names: true) + if user_details.success? + user_details_data = JSON.parse(user_details.read, symbolize_names: true) end 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?) # # Response: avatar-uri (Image download uri), id, username - def self.user_details(id) + def self.user_details(internetn, id) end # /apis/w3dhub/1/get-service-status # Service response: # {"services":{"authentication":true,"packageDownload":true}} - def self.service_status - response = W3DHUB_API_CONNECTION.post( - path: "apis/w3dhub/1/get-service-status", - headers: DEFAULT_HEADERS - ) + def self.service_status(internet) + response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS) - if response.status == 200 - ServiceStatus.new(response.body) + if response.success? + ServiceStatus.new(response.read) else false end @@ -108,14 +97,11 @@ class W3DHub # Client sends an Authorization header bearer token which is received from logging in (Optional) # Launcher sends an empty data request: data={} # Response is a list of applications/games - def self.applications - response = W3DHUB_API_CONNECTION.post( - path: "apis/launcher/1/get-applications", - headers: DEFAULT_HEADERS - ) + def self.applications(internet) + response = internet.post("#{ENDPOINT}/apis/launcher/1/get-applications", DEFAULT_HEADERS) - if response.status == 200 - Applications.new(response.body) + if response.success? + Applications.new(response.read) else false end @@ -125,15 +111,12 @@ class W3DHub # 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) # Response is a JSON hash with a "highlighted" and "news" keys; the "news" one seems to be the desired one - def self.news(category) - response = W3DHUB_API_CONNECTION.post( - path: "apis/w3dhub/1/get-news", - headers: DEFAULT_HEADERS.merge({"Content-Type": "application/x-www-form-urlencoded"}), - body: "data=#{JSON.dump({category: category})}" - ) + def self.news(internet, category) + body = "data=#{JSON.dump({category: category})}" + response = internet.post("#{ENDPOINT}/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body) - if response.status == 200 - News.new(response.body) + if response.success?#status == 200 + News.new(response.read) else false end @@ -170,8 +153,9 @@ class W3DHub #! === Server List API === !# - SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com" - SERVER_LIST_CONNECTION = Excon.new(SERVER_LIST_ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true) + SERVER_LIST_ENDPOINT = "https://gsh.w3dhub.com".freeze + # SERVER_LIST_CONNECTION = Excon.new(SERVER_LIST_ENDPOINT, persistent: true, connect_timeout: 15, tcp_nodelay: true) + # Method: GET # FORMAT: JSON @@ -189,14 +173,11 @@ class W3DHub # id, name, score, kills, deaths # ...players[]: # nick, team (index of teams array), score, kills, deaths - def self.server_list(level = 1) - response = SERVER_LIST_CONNECTION.get( - path: "listings/getAll/v2?statusLevel=#{level}", - headers: DEFAULT_HEADERS - ) + def self.server_list(internet, level = 1) + response = internet.get("#{SERVER_LIST_ENDPOINT}/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS) - if response.status == 200 - data = JSON.parse(response.body, symbolize_names: true) + if response.success? + data = JSON.parse(response.read, symbolize_names: true) return data.map { |hash| ServerListServer.new(hash) } end @@ -214,7 +195,7 @@ class W3DHub # id, name, score, kills, deaths # ...players[]: # nick, team (index of teams array), score, kills, deaths - def self.server_details(id, level) + def self.server_details(internet, id, level) end # /listings/push/v2/negotiate?negotiateVersion=1 diff --git a/lib/cache.rb b/lib/cache.rb index 72473fb..a6d5423 100644 --- a/lib/cache.rb +++ b/lib/cache.rb @@ -7,18 +7,16 @@ class W3DHub end # Fetch a generic uri - def self.fetch(uri) + def self.fetch(internet, uri) path = path(uri) if File.exist?(path) path else - response = Excon.get(uri, tcp_nodelay: true) + response = internet.get(uri, [["user-agent", W3DHub::Api::USER_AGENT]]) - if response.status == 200 - File.open(path, "wb") do |f| - f.write(response.body) - end + if response.success?#status == 200 + response.save(path) path end diff --git a/lib/pages/community.rb b/lib/pages/community.rb index 9b917ea..5e81158 100644 --- a/lib/pages/community.rb +++ b/lib/pages/community.rb @@ -56,23 +56,25 @@ class W3DHub if @w3dhub_news populate_w3dhub_news else - Thread.new do - fetch_w3dhub_news - main_thread_queue << proc { populate_w3dhub_news } - end - @wd3hub_news_container.clear do para I18n.t(:"games.fetching_news"), padding: 8 end + + Async do + internet = Async::HTTP::Internet.instance + + fetch_w3dhub_news(internet) + populate_w3dhub_news + end end end - def fetch_w3dhub_news - news = Api.news("launcher-home") + def fetch_w3dhub_news(internet) + news = Api.news(internet, "launcher-home") if news news.items[0..9].each do |item| - Cache.fetch(item.image) + Cache.fetch(internet, item.image) end @w3dhub_news = news diff --git a/lib/pages/games.rb b/lib/pages/games.rb index 1d72dc0..73d781f 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -159,23 +159,25 @@ class W3DHub if @game_news[game.id] populate_game_news(game) else - Thread.new do - fetch_game_news(game) - main_thread_queue << proc { populate_game_news(game) } - end - @game_news_container.clear do title I18n.t(:"games.fetching_news"), padding: 8 end + + Async do + internet = Async::HTTP::Internet.instance + + fetch_game_news(internet, game) + populate_game_news(game) + end end end - def fetch_game_news(game) - news = Api.news(game.id) + def fetch_game_news(internet, game) + news = Api.news(internet, game.id) if news news.items[0..9].each do |item| - Cache.fetch(item.image) + Cache.fetch(internet, item.image) end @game_news[game.id] = news diff --git a/lib/pages/login.rb b/lib/pages/login.rb index 68826a8..4fc3d79 100644 --- a/lib/pages/login.rb +++ b/lib/pages/login.rb @@ -36,27 +36,27 @@ class W3DHub # Do network stuff - Thread.new do - account = Api.user_login(@username.value, @password.value) + Async do + internet = Async::HTTP::Internet.instance + + account = Api.user_login(internet, @username.value, @password.value) if account Store.account = account Store.settings[:account][:refresh_token] = account.refresh_token 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 # An error occurred, enable account entry # NOTE: Too many incorrect entries causes lock out (Unknown duration) - main_thread_queue << proc do - @username.enabled = true - @password.enabled = true - btn.enabled = true + @username.enabled = true + @password.enabled = true + btn.enabled = true - @error_label.value = "Incorrect username or password.\nOr too many failed login attempts." - end + @error_label.value = "Incorrect username or password.\nOr too many failed login attempts, try again in a few minutes." end end end diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb index f1fbecc..0741b64 100644 --- a/lib/pages/server_browser.rb +++ b/lib/pages/server_browser.rb @@ -318,9 +318,11 @@ class W3DHub return end - Thread.new do + Async do + internet = Async::HTTP::Internet.instance + begin - list = Api.server_list(2) + list = Api.server_list(internet, 2) if list Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse diff --git a/lib/states/boot.rb b/lib/states/boot.rb index 130c985..d9c80b9 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -28,6 +28,16 @@ class W3DHub inscription "#{I18n.t(:app_name)} #{W3DHub::VERSION}", width: 0.5, text_align: :right end end + + Async do + internet = Async::HTTP::Internet.instance + + @tasks.keys.each do |key| + Sync do + send(key, internet) + end + end + end end def draw @@ -52,86 +62,70 @@ class W3DHub push_state(States::Interface) 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) end - def refresh_user_token + def refresh_user_token(internet) if Store.settings[:account, :refresh_token] - Thread.new do - @account = Api.refresh_user_login(Store.settings[:account, :refresh_token]) + @account = Api.refresh_user_login(internet, Store.settings[:account, :refresh_token]) - if @account - Store.settings[:account][:refresh_token] = @account.refresh_token - else - Store.settings[:account][:refresh_token] = nil - end - - Store.settings.save_settings - - @tasks[:refresh_user_token][:complete] = true + if @account + Store.settings[:account][:refresh_token] = @account.refresh_token + else + Store.settings[:account][:refresh_token] = nil end + + Store.settings.save_settings + + @tasks[:refresh_user_token][:complete] = true else @tasks[:refresh_user_token][:complete] = true end end - def service_status - Thread.new do - @service_status = Api.service_status + def service_status(internet) + @service_status = Api.service_status(internet) - if @service_status - 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'}." - end - - @tasks[:service_status][:complete] = true - else - # FIXME: MAIN THREAD! - @status_label.value = I18n.t(:"boot.w3dhub_service_is_down") + if @service_status + if !@service_status.authentication? || !@service_status.package_download? + @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 + @status_label.value = I18n.t(:"boot.w3dhub_service_is_down") end end - def applications + def applications(internet) @status_label.value = I18n.t(:"boot.checking_for_updates") - Thread.new do - @applications = Api.applications + @applications = Api.applications(internet) - if @applications - @tasks[:applications][:complete] = true - else - # FIXME: Failed to retreive! - end + if @applications + @tasks[:applications][:complete] = true + else + # FIXME: Failed to retreive! end end - def server_list + def server_list(internet) @status_label.value = I18n.t(:"server_browser.fetching_server_list") - Thread.new do - begin - list = Api.server_list(2) + begin + 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 - - @tasks[:server_list][:complete] = true - rescue => e - # Something went wrong! - pp e - Store.server_list = [] + if list + 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 end diff --git a/lib/window.rb b/lib/window.rb index f66b0a7..e486692 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -27,6 +27,9 @@ class W3DHub Store.application_manager.start_next_available_task if Store.application_manager.idle? manage_update_interval + + current = Async::Task.current? + current&.yield end def button_down(id) diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index 59084f0..26ba98a 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -13,6 +13,11 @@ require "rexml" require "i18n" 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.default_locale = :en @@ -66,4 +71,6 @@ require_relative "lib/pages/login" require_relative "lib/pages/settings" 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