diff --git a/lib/api.rb b/lib/api.rb index 3318c5d..dbe4a4e 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -3,7 +3,7 @@ class W3DHub LOG_TAG = "W3DHub::Api".freeze - API_TIMEOUT = 30 # seconds + API_TIMEOUT = 10 # seconds USER_AGENT = "Cyberarm's Linux Friendly W3D Hub Launcher v#{W3DHub::VERSION}".freeze DEFAULT_HEADERS = [ ["user-agent", USER_AGENT], @@ -25,7 +25,7 @@ class W3DHub HTTP_CLIENTS = {} - def self.async_http(method, path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub, &callback) + def self.async_http(method:, path:, headers:, body:, backend:, async:, &callback) raise "NO CALLBACK DEFINED!" unless callback case backend @@ -56,35 +56,20 @@ class W3DHub headers << ["authorization", "Bearer #{Store.account.access_token}"] end - Store.network_manager.request(method, url, headers, body, nil, &callback) + Store.network_manager.request(method, url, headers, body, async, &callback) end - def self.provision_http_client(hostname) - # Pin http clients to their host Thread so the fiber scheduler doesn't get upset and raise an error - HTTP_CLIENTS[Thread.current] ||= {} - return HTTP_CLIENTS[Thread.current][hostname.downcase] if HTTP_CLIENTS[Thread.current][hostname.downcase] - - ssl_context = W3DHub.ca_bundle_path ? OpenSSL::SSL::SSLContext.new : nil - ssl_context&.set_params( - ca_file: W3DHub.ca_bundle_path, - verify_mode: OpenSSL::SSL::VERIFY_PEER - ) - - endpoint = Async::HTTP::Endpoint.parse(hostname, ssl_context: ssl_context) - HTTP_CLIENTS[Thread.current][hostname.downcase] = Async::HTTP::Client.new(endpoint) + def self.post(path:, headers: DEFAULT_HEADERS, body: nil, backend: :w3dhub, async: true, &callback) + async_http(method: :post, path: path, headers: headers, body: body, backend: backend, async: async, &callback) end - def self.post(path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub, &callback) - async_http(:post, path, headers, body, backend, &callback) - end - - def self.get(path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub, &callback) - async_http(:get, path, headers, body, backend, &callback) + def self.get(path:, headers: DEFAULT_HEADERS, body: nil, backend: :w3dhub, async: true, &callback) + async_http(method: :get, path: path, headers: headers, body: body, backend: backend, async: async, &callback) end # Api.get but handles any URL instead of known hosts - def self.fetch(path, headers = DEFAULT_HEADERS, body = nil, backend = nil, &callback) - async_http(:get, path, headers, body, backend, &callback) + def self.fetch(path:, headers: DEFAULT_HEADERS, body: nil, backend: :w3dhub, async: true, &callback) + async_http(method: :get, path: path, headers: headers, body: body, backend: backend, async: async, &callback) end # Method: POST @@ -103,73 +88,76 @@ class W3DHub # On a failed login the service responds with: # {"error":"login-failed"} def self.refresh_user_login(refresh_token, backend = :w3dhub, &callback) + body = URI.encode_www_form("data": JSON.dump({ refreshToken: refresh_token })) + handler = lambda do |result| if result.okay? user_data = JSON.parse(result.data, symbolize_names: true) if user_data[:error] - callback.call(false) + callback.call(CyberarmEngine::Result.new(data: false)) next end user_details_data = user_details(user_data[:userid]) || {} - callback.call(Account.new(user_data, user_details_data)) + callback.call(CyberarmEngine::Result.new(data: Account.new(user_data, user_details_data))) else logger.error(LOG_TAG) { "Failed to fetch refresh user login:" } logger.error(LOG_TAG) { result.error } - callback.call(false) + callback.call(result) end end - body = URI.encode_www_form("data": JSON.dump({ refreshToken: refresh_token })) - post("/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body, backend, &handler) + post(path: "/apis/launcher/1/user-login", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler) end # See #user_refresh_token def self.user_login(username, password, backend = :w3dhub, &callback) + body = URI.encode_www_form("data": JSON.dump({ username: username, password: password })) + handler = lambda do |result| if result.okay? user_data = JSON.parse(result.data, symbolize_names: true) if user_data[:error] - callback.call(false) + callback.call(CyberarmEngine::Result.new(data: false)) next end user_details_data = user_details(user_data[:userid]) || {} - callback.call(Account.new(user_data, user_details_data)) + callback.call(CyberarmEngine::Result.new(data: Account.new(user_data, user_details_data))) else logger.error(LOG_TAG) { "Failed to fetch user login:" } logger.error(LOG_TAG) { result.error } - callback.call(false) + callback.call(result) end end - body = URI.encode_www_form("data": JSON.dump({ username: username, password: password })) - post("/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body, backend, &handler) + post(path: "/apis/launcher/1/user-login", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler) end # /apis/w3dhub/1/get-user-details # # Response: avatar-uri (Image download uri), id, username def self.user_details(id, backend = :w3dhub, &callback) + body = URI.encode_www_form("data": JSON.dump({ id: id })) + handler = lambda do |result| if result.okay? - callback.call(JSON.parse(result.data, symbolize_names: true)) + callback.call(CyberarmEngine::Result.new(data: JSON.parse(result.data, symbolize_names: true))) else logger.error(LOG_TAG) { "Failed to fetch user details:" } logger.error(LOG_TAG) { result.error } - callback.call(false) + callback.call(result) end end - body = URI.encode_www_form("data": JSON.dump({ id: id })) - post("/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body, backend, &handler) + post(path: "/apis/w3dhub/1/get-user-details", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler) end # /apis/w3dhub/1/get-service-status @@ -178,16 +166,16 @@ class W3DHub def self.service_status(backend = :w3dhub, &callback) handler = lambda do |result| if result.okay? - callback.call(ServiceStatus.new(result.data)) + callback.call(CyberarmEngine::Result.new(data: ServiceStatus.new(result.data))) else logger.error(LOG_TAG) { "Failed to fetch service status:" } logger.error(LOG_TAG) { result.error } - callback.call(false) + callback.call(result) end end - post("/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS, nil, backend, &handler) + post(path: "/apis/w3dhub/1/get-service-status", backend: backend, &handler) end # /apis/launcher/1/get-applications @@ -195,87 +183,104 @@ class W3DHub # Launcher sends an empty data request: data={} # Response is a list of applications/games def self.applications(backend = :w3dhub, &callback) + async = !callback.nil? + + # Complicated why to "return" direct value + callback = ->(result) { result } + handler = lambda do |result| if result.okay? - callback.call(Applications.new(result.data, backend)) + callback.call(CyberarmEngine::Result.new(data: Applications.new(result.data, backend))) else logger.error(LOG_TAG) { "Failed to fetch applications list:" } logger.error(LOG_TAG) { result.error } - callback.call(false) + callback.call(result) end end - post("/apis/launcher/1/get-applications", DEFAULT_HEADERS, nil, backend, &handler) + post(path: "/apis/launcher/1/get-applications", async: async, backend: backend, &handler) end # Populate applications list from primary and alternate backends # (alternate only has latest public builds of _most_ games) - def self._applications - applications_primary = Store.account ? Api.applications(:w3dhub) : false - applications_alternate = Api.applications(:alt_w3dhub) + def self._applications(&callback) + handler = lambda do |result| + # nothing special on offer if we're not logged in + applications_primary = Store.account ? Api.applications(:w3dhub).data : false + applications_alternate = Api.applications(:alt_w3dhub).data - # Fail if we fail to fetch applications list from either backend - return false unless applications_primary || applications_alternate + # Fail if we fail to fetch applications list from either backend + unless applications_primary || applications_alternate + callback.call(CyberarmEngine::Result.new) + next + end - return applications_alternate unless applications_primary + unless applications_primary + callback.call(CyberarmEngine::Result.new(data: applications_alternate)) + next + end - # Merge the two app lists together - apps = applications_alternate - if applications_primary - applications_primary.games.each do |game| - # Check if game exists in alternate list - _game = apps.games.find { |g| g.id == game.id } - unless _game - apps.games << game + # Merge the two app lists together + apps = applications_alternate + if applications_primary + applications_primary.games.each do |game| + # Check if game exists in alternate list + _game = apps.games.find { |g| g.id == game.id } + unless _game + apps.games << game - # App didn't exist in alternates list - # comparing channels isn't useful - next - end - - # If it does, check that all of its channels also exist in alternate list - # and that the primary versions are the same as the alternates list - game.channels.each do |channel| - _channel = _game.channels.find { |c| c.id == channel.id } - - unless _channel - _game.channels << channel - - # App didn't have channel in alternates list - # comparing channel isn't useful + # App didn't exist in alternates list + # comparing channels isn't useful next end - # If channel versions and access levels match then all's well - if channel.current_version == _channel.current_version && - channel.user_level == _channel.user_level + # If it does, check that all of its channels also exist in alternate list + # and that the primary versions are the same as the alternates list + game.channels.each do |channel| + _channel = _game.channels.find { |c| c.id == channel.id } - # All's Well! - next - end + unless _channel + _game.channels << channel - # If the access levels don't match then overwrite alternate's channel with primary's channel - if channel.user_level != _channel.user_level - # Replace alternate's channel with primary's channel - _game.channels[_game.channels.index(_channel)] = channel + # App didn't have channel in alternates list + # comparing channel isn't useful + next + end - # Replaced, continue. - next - end + # If channel versions and access levels match then all's well + if channel.current_version == _channel.current_version && + channel.user_level == _channel.user_level - # If versions don't match then pick whichever one is higher - if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version) - # Replace alternate's channel with primary's channel - _game.channels[_game.channels.index(_channel)] = channel - else - # Do nothing, alternate backend version is greater. + # All's Well! + next + end + + # If the access levels don't match then overwrite alternate's channel with primary's channel + if channel.user_level != _channel.user_level + # Replace alternate's channel with primary's channel + _game.channels[_game.channels.index(_channel)] = channel + + # Replaced, continue. + next + end + + # If versions don't match then pick whichever one is higher + if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version) + # Replace alternate's channel with primary's channel + _game.channels[_game.channels.index(_channel)] = channel + else + # Do nothing, alternate backend version is greater. + end end end end + + callback.call(CyberarmEngine::Result.new(data: apps)) end - apps + # Bit hacky but we just need to run this handler from the networking thread and async reactor + get(path: "", backend: nil, &handler) end # /apis/w3dhub/1/get-news @@ -285,18 +290,18 @@ class W3DHub def self.news(category, backend = :w3dhub, &callback) handler = lambda do |result| if result.okay? - callback.call(News.new(result.data)) + callback.call(CyberarmEngine::Result.new(data: News.new(result.data))) else logger.error(LOG_TAG) { "Failed to fetch news for:" } logger.error(LOG_TAG) { category } logger.error(LOG_TAG) { result.error } - callback.call(false) + callback.call(result) end end body = URI.encode_www_form("data": JSON.dump({ category: category })) - post("/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body, backend, &handler) + post(path: "/apis/w3dhub/1/get-news", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler) end # Downloading games @@ -308,18 +313,18 @@ class W3DHub if result.okay? hash = JSON.parse(result.data, symbolize_names: true) - callback.call(hash[:packages].map { |pkg| Package.new(pkg) }) + callback.call(CyberarmEngine::Result.new(data: hash[:packages].map { |pkg| Package.new(pkg) })) else logger.error(LOG_TAG) { "Failed to fetch package details for:" } logger.error(LOG_TAG) { packages } logger.error(LOG_TAG) { result.error } - callback.call(false) + callback.call(result) end end body = URI.encode_www_form("data": JSON.dump({ packages: packages })) - post("/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body, backend, &handler) + post(path: "/apis/launcher/1/get-package-details", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler) end # /apis/launcher/1/get-package @@ -337,15 +342,15 @@ class W3DHub def self.events(app_id, backend = :w3dhub, &callback) handler = lambda do |result| if result.okay? - array = JSON.parse(response.body, symbolize_names: true) - callback.call(array.map { |e| Event.new(e) }) + array = JSON.parse(result.data, symbolize_names: true) + callback.call(CyberarmEngine::Result.new(data: array.map { |e| Event.new(e) })) else - callback.call(false) + callback.call(result) end end body = URI.encode_www_form("data": JSON.dump({ serverPath: app_id })) - post("/apis/w3dhub/1/get-server-events", FORM_ENCODED_HEADERS, body, backend, &handler) + post(path: "/apis/w3dhub/1/get-server-events", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler) end #! === Server List API === !# @@ -375,13 +380,13 @@ class W3DHub handler = lambda do |result| if result.okay? data = JSON.parse(result.data, symbolize_names: true) - callback.call(data.map { |hash| ServerListServer.new(hash) }) + callback.call(CyberarmEngine::Result.new(data: data.map { |hash| ServerListServer.new(hash) })) else - callback.call(false) + callback.call(result) end end - get("/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS, nil, backend, &handler) + get(path: "/listings/getAll/v2?statusLevel=#{level}", backend: backend, &handler) end # /listings/getStatus/v2/:id?statusLevel=#{0-2} @@ -400,13 +405,13 @@ class W3DHub handler = lambda do |result| if result.okay? - callback.call(JSON.parse(response.body, symbolize_names: true)) + callback.call(CyberarmEngine::Result.new(data: JSON.parse(result.data, symbolize_names: true))) else - callback.call(false) + callback.call(result) end end - get("/listings/getStatus/v2/#{id}?statusLevel=#{level}", DEFAULT_HEADERS, nil, backend, &handler) + get(path: "/listings/getStatus/v2/#{id}?statusLevel=#{level}", backend: backend, &handler) end # /listings/push/v2/negotiate?negotiateVersion=1 diff --git a/lib/cache.rb b/lib/cache.rb index 0e265e8..89b8ff6 100644 --- a/lib/cache.rb +++ b/lib/cache.rb @@ -15,7 +15,7 @@ class W3DHub if !force_fetch && File.exist?(path) path else - Api.fetch(uri, W3DHub::Api::DEFAULT_HEADERS, nil, backend) do |result| + Api.fetch(path: uri, backend: backend) do |result| if result.okay? File.open(path, "wb") { |f| f.write result.data } end diff --git a/lib/network_manager.rb b/lib/network_manager.rb index 7e77e96..49e5fa2 100644 --- a/lib/network_manager.rb +++ b/lib/network_manager.rb @@ -2,22 +2,21 @@ class W3DHub # all http(s) requests for API calls and downloading images run through here class NetworkManager NetworkEvent = Data.define(:context, :result) - Request = Struct.new(:active, :context, :callback) + Request = Struct.new(:active, :context, :async, :callback) Context = Data.define( :request_id, :method, :url, :headers, - :body, - :bearer_token + :body ) def initialize @requests = [] @running = true - Thread.new do - http_client = HttpClient.new + @thread = Thread.new do + @http_client = HttpClient.new Sync do while @running @@ -33,18 +32,25 @@ class W3DHub Async do |task| assigned_request = request - result = http_client.handle(task, assigned_request) + result = if assigned_request.context.url.empty? + assigned_request.callback.call(nil) + else + @http_client.handle(task, assigned_request) + end @requests.delete(assigned_request) - Store.main_thread_queue << -> { assigned_request.callback.call(result) } + # callback for this is already handled! + unless assigned_request.context.url.empty? + Store.main_thread_queue << -> { assigned_request.callback.call(result) } + end end end end end end - def request(method, url, headers, body, bearer_token, &block) + def request(method, url, headers, body, async, &block) request_id = SecureRandom.hex request = Request.new( @@ -54,15 +60,29 @@ class W3DHub method, url, headers, - body, - bearer_token + body ), + async, block ) @requests << request - request_id + + if async + request_id + else # Not async, process immediately. + raise "WTF? This should NOT happen!" unless Async::Task.current? + + Sync do |task| + assigned_request = request + result = @http_client.handle(task, assigned_request) + + @requests.delete(assigned_request) + # "return" callback "value" + assigned_request.callback.call(result) + end + end end def busy? diff --git a/lib/network_manager/http_client.rb b/lib/network_manager/http_client.rb index a891d1c..46139e9 100644 --- a/lib/network_manager/http_client.rb +++ b/lib/network_manager/http_client.rb @@ -10,22 +10,18 @@ class W3DHub result = CyberarmEngine::Result.new context = request.context - task.with_timeout(30) do + task.with_timeout(W3DHub::Api::API_TIMEOUT) do uri = URI(context.url) - pp uri - response = provision_http_client(uri.origin).send( context.method, - uri.path, + uri.request_uri, context.headers, context.body ) - pp response - if response.success? - result.data = response.body.read + result.data = response.read else result.error = response end diff --git a/lib/pages/community.rb b/lib/pages/community.rb index 4ae3fdd..19cbe9f 100644 --- a/lib/pages/community.rb +++ b/lib/pages/community.rb @@ -65,15 +65,7 @@ class W3DHub para I18n.t(:"games.fetching_news"), padding: 8 end - BackgroundWorker.foreground_job( - -> { fetch_w3dhub_news }, - lambda do |result| - if result - populate_w3dhub_news - Cache.release_net_lock(result) - end - end - ) + fetch_w3dhub_news end end @@ -89,15 +81,7 @@ class W3DHub title I18n.t(:"games.fetching_news"), padding: 8 end - BackgroundWorker.foreground_job( - -> { fetch_w3dhub_news }, - lambda do |result| - if result - populate_w3dhub_news - Cache.release_net_lock(result) - end - end - ) + fetch_w3dhub_news end end @@ -105,19 +89,24 @@ class W3DHub lock = Cache.acquire_net_lock("w3dhub_news") return false unless lock - news = Api.news("launcher-home") - Cache.release_net_lock("w3dhub_news") unless news + Api.news("launcher-home") do |result| + news = result.data - return unless news + Cache.release_net_lock("w3dhub_news") unless news - news.items[0..15].each do |item| - Cache.fetch(uri: item.image, async: false, backend: :w3dhub) + next false unless news + + news.items[0..15].each do |item| + Cache.fetch(uri: item.image, backend: :w3dhub) + end + + @w3dhub_news = news + @w3dhub_news_expires = Gosu.milliseconds + (60 * 60 * 1000) # 1 hour (in ms) + + populate_w3dhub_news + ensure + Cache.release_net_lock("w3dhub_news") end - - @w3dhub_news = news - @w3dhub_news_expires = Gosu.milliseconds + (60 * 60 * 1000) # 1 hour (in ms) - - "w3dhub_news" end def populate_w3dhub_news @@ -125,37 +114,7 @@ class W3DHub if (feed = @w3dhub_news) @wd3hub_news_container.clear do - # feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item| - # flow(width: 0.5, max_width: 312, height: 128, margin: 4) do - # # background 0x88_000000 - - # path = Cache.path(item.image) - - # if File.exist?(path) - # image path, height: 1.0, padding: 4 - # else - # image BLACK_IMAGE, height: 1.0, padding: 4 - # end - - # stack(width: 0.6, height: 1.0) do - # stack(width: 1.0, height: 112) do - # link "#{item.title}", text_size: 22 do - # W3DHub.url(item.uri) - # end - # para item.blurb.gsub(/\n+/, "\n").strip[0..180] - # end - - # flow(width: 1.0) do - # para item.timestamp.strftime("%Y-%m-%d"), width: 0.499 - # link I18n.t(:"games.read_more"), width: 0.5, text_align: :right, text_size: 22 do - # W3DHub.url(item.uri) - # end - # end - # end - # end - # end - - feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item| + feed.items.sort_by(&:timestamp).reverse[0..9].each do |item| image_path = Cache.path(item.image) flow(width: 1.0, max_width: 1230, height: 200, margin: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(0xff_252525))) do diff --git a/lib/pages/games.rb b/lib/pages/games.rb index 81389ed..d551a45 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -31,30 +31,16 @@ class W3DHub def update super - @game_news.each do |key, value| - next if key.end_with?("_expires") + next unless key.end_with?("_expires") - if Gosu.milliseconds >= @game_news["#{key}_expires"] - @game_news.delete(key) - @game_news["#{key}_expires"] = Gosu.milliseconds + 30_000 # seconds + next unless Gosu.milliseconds >= value - if @focused_game && @focused_game.id == key - @game_news_container.clear do - title I18n.t(:"games.fetching_news"), padding: 8 - end + # try to refresh game news after last data 'expired', every 30 seconds until success + @game_news[key] = Gosu.milliseconds + 30_000 # seconds - BackgroundWorker.foreground_job( - -> { fetch_game_news(@focused_game) }, - lambda do |result| - if result - populate_game_news(@focused_game) - Cache.release_net_lock(result) - end - end - ) - end - end + game = Store.applications.games.find { |g| g.id == key.split("_").first } + fetch_game_news(game) end end @@ -292,22 +278,6 @@ class W3DHub return if Store.offline_mode unless Cache.net_lock?("game_news_#{game.id}") - if @game_events[game.id] - populate_game_events(game) - else - # BackgroundWorker.foreground_job( - # -> { fetch_game_events(game) }, - # lambda do |result| - # if result - # populate_game_events(game) - # Cache.release_net_lock(result) - # end - # end - # ) - end - end - - unless Cache.net_lock?("game_events_#{game.id}") if @game_news[game.id] populate_game_news(game) else @@ -315,15 +285,15 @@ class W3DHub title I18n.t(:"games.fetching_news"), padding: 8 end - # BackgroundWorker.foreground_job( - # -> { fetch_game_news(game) }, - # lambda do |result| - # if result - # populate_game_news(game) - # Cache.release_net_lock(result) - # end - # end - # ) + fetch_game_news(game) + end + end + + unless Cache.net_lock?("game_events_#{game.id}") + if @game_events[game.id] + populate_game_events(game) + else + fetch_game_events(game) end end end @@ -413,19 +383,25 @@ class W3DHub lock = Cache.acquire_net_lock("game_news_#{game.id}") return false unless lock - news = Api.news(game.id) - Cache.release_net_lock("game_news_#{game.id}") unless news + Api.news(game.id) do |result| + news = result.data - return false unless news + unless news + @game_news["#{game.id}_expires"] = Gosu.milliseconds + 30_000 # retry in 30 seconds + next false + end - news.items[0..15].each do |item| - Cache.fetch(uri: item.image, async: false, backend: :w3dhub) + news.items[0..15].each do |item| + Cache.fetch(uri: item.image, backend: :w3dhub) + end + + @game_news[game.id] = news + @game_news["#{game.id}_expires"] = Gosu.milliseconds + (60 * 60 * 1000) # 1 hour (in ms) + + populate_game_news(@focused_game) + ensure + Cache.release_net_lock("game_news_#{game.id}") end - - @game_news[game.id] = news - @game_news["#{game.id}_expires"] = Gosu.milliseconds + (60 * 60 * 1000) # 1 hour (in ms) - - "game_news_#{game.id}" end def populate_game_news(game) @@ -436,38 +412,11 @@ class W3DHub game_color.alpha = 0xaa @game_news_container.clear do - # Patch Notes - if false # Patch notes - flow(width: 1.0, max_width: 346 * 3 + (8 * 4), height: 346, margin: 8, margin_right: 32, border_thickness: 1, border_color: darken(Gosu::Color.new(game.color))) do - background darken(Gosu::Color.new(game.color), 10) - - stack(width: 346, height: 1.0, padding: 8) do - background 0xff_181d22 - - para "Patch Notes" - - tagline "Patch 2.0 is now out!" - - para "words go here " * 20 - - flow(fill: true) - - button "Read More", width: 1.0 - end - - flow(fill: true) - - title "Eye Candy Banner Goes Here." - end - end - - feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item| + feed.items.sort_by(&:timestamp).reverse[0..9].each do |item| image_path = Cache.path(item.image) flow(width: 1.0, max_width: 869, height: 200, margin: 8, background: game_color, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do - if File.file?(image_path) - image image_path, height: 1.0 - end + image image_path, height: 1.0 if File.file?(image_path) stack(fill: true, height: 1.0, padding: 4, border_thickness_left: 1, border_color_left: lighten(Gosu::Color.new(game.color))) do tagline "#{item.title}", width: 1.0 @@ -494,14 +443,14 @@ class W3DHub lock = Cache.acquire_net_lock("game_events_#{game.id}") return false unless lock - events = Api.events(game.id) - Cache.release_net_lock("game_events_#{game.id}") unless events + Api.events(game.id) do |result| + next unless result.okay? - return false unless events - - @game_events[game.id] = events - - "game_events_#{game.id}" + @game_events[game.id] = result.data + populate_game_events(game) + ensure + Cache.release_net_lock("game_events_#{game.id}") + end end def populate_game_events(game) diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb index cd2232f..d842407 100644 --- a/lib/pages/server_browser.rb +++ b/lib/pages/server_browser.rb @@ -145,15 +145,7 @@ class W3DHub reorder_server_list if @selected_server&.id == @refresh_server&.id - if @refresh_server - BackgroundWorker.foreground_job( - -> { fetch_server_details(@refresh_server) }, - ->(result) { - populate_server_info(@refresh_server) if @refresh_server == @selected_server - @refresh_server = nil - } - ) - end + fetch_server_details(@refresh_server) if @refresh_server end end end @@ -353,10 +345,7 @@ class W3DHub reorder_server_list if @selected_server_container - BackgroundWorker.foreground_job( - -> { fetch_server_details(server) }, - ->(result) { populate_server_info(server) if server == @selected_server } - ) + fetch_server_details(server) end stylize_selected_server(server_container) if server.id == @selected_server&.id @@ -523,10 +512,13 @@ class W3DHub end def fetch_server_details(server) - BackgroundWorker.foreground_job( - -> { Api.server_details(server.id, 2) }, - ->(server_data) { server.update(server_data) if server_data } - ) + Api.server_details(server.id, 2) do |result| + if result.okay? + server.update(result.data) + populate_server_info(server) if server == @selected_server + @refresh_server = nil + end + end end def game_icon(server) diff --git a/lib/states/boot.rb b/lib/states/boot.rb index 04f686a..269b205 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -130,7 +130,7 @@ class W3DHub Store.settings[:account][:data] = account - Cache.fetch(uri: account.avatar_uri, force_fetch: true, async: false, backend: :w3dhub) + Cache.fetch(uri: account.avatar_uri, force_fetch: true, backend: :w3dhub) else Store.settings[:account] = {} end @@ -176,10 +176,8 @@ class W3DHub def service_status @status_label.value = "Checking service status..." #I18n.t(:"server_browser.fetching_server_list") - Api.on_thread(:service_status) do |service_status| - pp service_status - - @service_status = service_status + Api.on_thread(:service_status) do |result| + @service_status = result.okay? ? result.data : nil if @service_status Store.service_status = @service_status @@ -190,9 +188,7 @@ class W3DHub @tasks[:service_status][:complete] = true else - BackgroundWorker.foreground_job(-> {}, lambda { |_| - @status_label.value = I18n.t(:"boot.w3dhub_service_is_down") - }) + Store.main_thread_queue << -> { @status_label.value = I18n.t(:"boot.w3dhub_service_is_down") } @tasks[:service_status][:complete] = true @offline_mode = true @@ -204,9 +200,9 @@ class W3DHub def launcher_updater @status_label.value = "Checking for Launcher updates..." # I18n.t(:"boot.checking_for_updates") - Api.on_thread(:fetch, "https://api.github.com/repos/cyberarm/w3d_hub_linux_launcher/releases/latest") do |response| - if response.status == 200 - hash = JSON.parse(response.body, symbolize_names: true) + Api.on_thread(:fetch, "https://api.github.com/repos/cyberarm/w3d_hub_linux_launcher/releases/latest") do |result| + if result.okay? + hash = JSON.parse(result.data, symbolize_names: true) available_version = hash[:tag_name].downcase.sub("v", "") pp Gem::Version.new(available_version) > Gem::Version.new(W3DHub::VERSION) @@ -230,14 +226,13 @@ class W3DHub def applications @status_label.value = I18n.t(:"boot.checking_for_updates") - # Api.on_thread(:_applications) do |applications| - Api.on_thread(:applications, :alt_w3dhub) do |applications| - if applications - Store.applications = applications - Store.settings.save_application_cache(applications.data.to_json) + Api.on_thread(:_applications) do |result| + if result.okay? + Store.applications = result.data + Store.settings.save_application_cache(Store.applications.data.to_json) @tasks[:applications][:complete] = true else - @status_label.value = "FAILED TO RETREIVE APPS LIST" + @status_label.value = "FAILED TO RETRIEVE APPS LIST" @offline_mode = true Store.offline_mode = true @@ -256,40 +251,39 @@ class W3DHub packages << { category: app.category, subcategory: app.id, name: "#{app.id}.ico", version: "" } end - Api.on_thread(:package_details, packages, :alt_w3dhub) do |package_details| - package_details ||= nil + Api.on_thread(:package_details, packages, :alt_w3dhub) do |result| + if result.okay? + result.data.each do |package| + next if package.error? - package_details&.each do |package| - next if package.error? + path = Cache.package_path(package.category, package.subcategory, package.name, package.version) + generated_icon_path = "#{CACHE_PATH}/#{package.subcategory}.png" - path = Cache.package_path(package.category, package.subcategory, package.name, package.version) - generated_icon_path = "#{CACHE_PATH}/#{package.subcategory}.png" + regenerate = false - regenerate = false - - if File.exist?(path) - broken_or_out_dated_icon = Digest::SHA256.new.hexdigest(File.binread(path)).upcase != package.checksum.upcase - end - - if File.exist?(path) && !broken_or_out_dated_icon - regenerate = !File.exist?(generated_icon_path) - else - begin - Cache.fetch_package(package, proc {}) - regenerate = true - rescue Errno::EACCES => e - failure = true - push_state(MessageDialog, title: "Fatal Error", - message: "Directory Permission Error (#{e.class}):\n#{e}.\n\nIs the required drive mounted?", - accept_callback: -> { window.close }) + if File.exist?(path) + broken_or_out_dated_icon = Digest::SHA256.new.hexdigest(File.binread(path)).upcase != package.checksum.upcase end + + if File.exist?(path) && !broken_or_out_dated_icon + regenerate = !File.exist?(generated_icon_path) + else + begin + Cache.fetch_package(package, proc {}) + regenerate = true + rescue Errno::EACCES => e + failure = true + push_state(MessageDialog, title: "Fatal Error", + message: "Directory Permission Error (#{e.class}):\n#{e}.\n\nIs the required drive mounted?", + accept_callback: -> { window.close }) + end + end + + next unless regenerate + + icon = ICO.new(file: path) + icon.save(icon.images.max_by(&:width), generated_icon_path) end - - next unless regenerate - - BackgroundWorker.foreground_job(-> { ICO.new(file: path) }, lambda { |result| - result.save(result.images.max_by(&:width), generated_icon_path) - }) end @tasks[:app_icons][:complete] = true unless failure @@ -307,18 +301,18 @@ class W3DHub packages << { category: app.category, subcategory: app.id, name: "background.png", version: "" } end - Api.on_thread(:package_details, packages, :alt_w3dhub) do |package_details| - package_details ||= nil + Api.on_thread(:package_details, packages, :alt_w3dhub) do |result| + if result.okay? + result.data.each do |package| + next if package.error? - package_details&.each do |package| - next if package.error? + package_cache_path = Cache.package_path(package.category, package.subcategory, package.name, + package.version) - package_cache_path = Cache.package_path(package.category, package.subcategory, package.name, - package.version) + missing_or_broken_image = File.exist?(package_cache_path) ? Digest::SHA256.new.hexdigest(File.binread(package_cache_path)).upcase != package.checksum.upcase : true - missing_or_broken_image = File.exist?(package_cache_path) ? Digest::SHA256.new.hexdigest(File.binread(package_cache_path)).upcase != package.checksum.upcase : true - - Cache.fetch_package(package, proc {}) if missing_or_broken_image + Cache.fetch_package(package, proc {}) if missing_or_broken_image + end end @tasks[:app_logos_and_backgrounds][:complete] = true @@ -328,15 +322,15 @@ class W3DHub def server_list @status_label.value = I18n.t(:"server_browser.fetching_server_list") - Api.on_thread(:server_list, 2) do |list| - if list - Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse + Api.on_thread(:server_list, 2) do |result| + if result.okay? + Store.server_list = result.data.sort_by! { |s| s&.status&.players&.size }.reverse Store.server_list_last_fetch = Gosu.milliseconds Api::ServerListUpdater.instance - list.each do |server| + Store.server_list.each do |server| server.send_ping(true) end else diff --git a/lib/states/interface.rb b/lib/states/interface.rb index a0b461e..fd864c5 100644 --- a/lib/states/interface.rb +++ b/lib/states/interface.rb @@ -164,15 +164,15 @@ class W3DHub if Gosu.milliseconds >= @server_list_expire @server_list_expire = Gosu.milliseconds + 30_000 - Api.on_thread(:server_list, 2) do |list| - if list + Api.on_thread(:server_list, 2) do |result| + if result.okay? @server_list_expire = Gosu.milliseconds + SERVER_LIST_UPDATE_INTERVAL # five minutes Store.server_list_last_fetch = Gosu.milliseconds - Api::ServerListUpdater.instance.refresh_server_list(list) + Api::ServerListUpdater.instance.refresh_server_list(result.data) - BackgroundWorker.foreground_job(-> {}, ->(_) { States::Interface.instance&.update_server_browser(nil, :refresh_all) }) + Store.main_thread_queue << -> { States::Interface.instance&.update_server_browser(nil, :refresh_all) } end end end