mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2026-03-22 04:06:18 +00:00
Compare commits
16 Commits
4146debc4c
...
refactor-b
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e4b25f0d4 | |||
| ebc045019a | |||
| 79858a02ce | |||
| 68df923bea | |||
| ddbec8d72c | |||
| 70d4e0c40f | |||
| f651143937 | |||
| 1425225eef | |||
| f98d8c3394 | |||
| 6e79c4639d | |||
| 4a8457e233 | |||
| 46dece0479 | |||
| 68af00bd2f | |||
| 208f2b8a39 | |||
| b5d975761c | |||
| 44483117d8 |
14
Gemfile.lock
14
Gemfile.lock
@@ -1,13 +1,13 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
async (2.35.2)
|
async (2.36.0)
|
||||||
console (~> 1.29)
|
console (~> 1.29)
|
||||||
fiber-annotation
|
fiber-annotation
|
||||||
io-event (~> 1.11)
|
io-event (~> 1.11)
|
||||||
metrics (~> 0.12)
|
metrics (~> 0.12)
|
||||||
traces (~> 0.18)
|
traces (~> 0.18)
|
||||||
async-http (0.94.0)
|
async-http (0.94.2)
|
||||||
async (>= 2.10.2)
|
async (>= 2.10.2)
|
||||||
async-pool (~> 0.11)
|
async-pool (~> 0.11)
|
||||||
io-endpoint (~> 0.14)
|
io-endpoint (~> 0.14)
|
||||||
@@ -30,7 +30,7 @@ GEM
|
|||||||
fiber-annotation
|
fiber-annotation
|
||||||
fiber-local (~> 1.1)
|
fiber-local (~> 1.1)
|
||||||
json
|
json
|
||||||
cyberarm_engine (0.25.0)
|
cyberarm_engine (0.25.1)
|
||||||
gosu (~> 1.1)
|
gosu (~> 1.1)
|
||||||
digest-crc (0.7.0)
|
digest-crc (0.7.0)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
@@ -43,7 +43,7 @@ GEM
|
|||||||
fiber-storage (1.0.1)
|
fiber-storage (1.0.1)
|
||||||
fiddle (1.1.8)
|
fiddle (1.1.8)
|
||||||
gosu (1.4.6)
|
gosu (1.4.6)
|
||||||
io-endpoint (0.16.0)
|
io-endpoint (0.17.2)
|
||||||
io-event (1.14.2)
|
io-event (1.14.2)
|
||||||
io-stream (0.11.1)
|
io-stream (0.11.1)
|
||||||
ircparser (1.0.0)
|
ircparser (1.0.0)
|
||||||
@@ -52,8 +52,8 @@ GEM
|
|||||||
fiddle
|
fiddle
|
||||||
metrics (0.15.0)
|
metrics (0.15.0)
|
||||||
protocol-hpack (1.5.1)
|
protocol-hpack (1.5.1)
|
||||||
protocol-http (0.58.0)
|
protocol-http (0.58.1)
|
||||||
protocol-http1 (0.36.0)
|
protocol-http1 (0.37.0)
|
||||||
protocol-http (~> 0.58)
|
protocol-http (~> 0.58)
|
||||||
protocol-http2 (0.24.0)
|
protocol-http2 (0.24.0)
|
||||||
protocol-hpack (~> 1.4)
|
protocol-hpack (~> 1.4)
|
||||||
@@ -97,4 +97,4 @@ DEPENDENCIES
|
|||||||
win32-security
|
win32-security
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.6.8
|
4.0.3
|
||||||
|
|||||||
419
lib/api.rb
419
lib/api.rb
@@ -3,7 +3,7 @@ class W3DHub
|
|||||||
|
|
||||||
LOG_TAG = "W3DHub::Api".freeze
|
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
|
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],
|
||||||
@@ -16,31 +16,7 @@ class W3DHub
|
|||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
def self.on_thread(method, *args, &callback)
|
def self.on_thread(method, *args, &callback)
|
||||||
BackgroundWorker.foreground_job(-> { Api.send(method, *args) }, callback)
|
Api.send(method, *args, &callback)
|
||||||
end
|
|
||||||
|
|
||||||
class Response
|
|
||||||
def initialize(error: nil, status: -1, body: "")
|
|
||||||
@status = status
|
|
||||||
@body = body
|
|
||||||
@error = error
|
|
||||||
end
|
|
||||||
|
|
||||||
def success?
|
|
||||||
@status == 200
|
|
||||||
end
|
|
||||||
|
|
||||||
def status
|
|
||||||
@status
|
|
||||||
end
|
|
||||||
|
|
||||||
def body
|
|
||||||
@body
|
|
||||||
end
|
|
||||||
|
|
||||||
def error
|
|
||||||
@error
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#! === W3D Hub API === !#
|
#! === W3D Hub API === !#
|
||||||
@@ -49,7 +25,9 @@ class W3DHub
|
|||||||
|
|
||||||
HTTP_CLIENTS = {}
|
HTTP_CLIENTS = {}
|
||||||
|
|
||||||
def self.async_http(method, path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
def self.async_http(method:, path:, headers:, body:, backend:, async:, &callback)
|
||||||
|
raise "NO CALLBACK DEFINED!" unless callback
|
||||||
|
|
||||||
case backend
|
case backend
|
||||||
when :w3dhub
|
when :w3dhub
|
||||||
endpoint = W3DHUB_API_ENDPOINT
|
endpoint = W3DHUB_API_ENDPOINT
|
||||||
@@ -78,52 +56,20 @@ class W3DHub
|
|||||||
headers << ["authorization", "Bearer #{Store.account.access_token}"]
|
headers << ["authorization", "Bearer #{Store.account.access_token}"]
|
||||||
end
|
end
|
||||||
|
|
||||||
Sync do
|
Store.network_manager.request(method, url, headers, body, async, &callback)
|
||||||
begin
|
|
||||||
response = provision_http_client(endpoint).send(method, path, headers, body)
|
|
||||||
|
|
||||||
Response.new(status: response.status, body: response.read)
|
|
||||||
rescue Async::TimeoutError => e
|
|
||||||
logger.error(LOG_TAG) { "Connection to \"#{url}\" timed out after: #{API_TIMEOUT} seconds" }
|
|
||||||
|
|
||||||
Response.new(error: e)
|
|
||||||
rescue StandardError => e
|
|
||||||
logger.error(LOG_TAG) { "Connection to \"#{url}\" errored:" }
|
|
||||||
logger.error(LOG_TAG) { e }
|
|
||||||
|
|
||||||
Response.new(error: e)
|
|
||||||
ensure
|
|
||||||
response&.close
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.provision_http_client(hostname)
|
def self.post(path:, headers: DEFAULT_HEADERS, body: nil, backend: :w3dhub, async: true, &callback)
|
||||||
# Pin http clients to their host Thread so the fiber scheduler doesn't get upset and raise an error
|
async_http(method: :post, path: path, headers: headers, body: body, backend: backend, async: async, &callback)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.post(path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
def self.get(path:, headers: DEFAULT_HEADERS, body: nil, backend: :w3dhub, async: true, &callback)
|
||||||
async_http(:post, path, headers, body, backend)
|
async_http(method: :get, path: path, headers: headers, body: body, backend: backend, async: async, &callback)
|
||||||
end
|
|
||||||
|
|
||||||
def self.get(path, headers = DEFAULT_HEADERS, body = nil, backend = :w3dhub)
|
|
||||||
async_http(:get, path, headers, body, backend)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Api.get but handles any URL instead of known hosts
|
# Api.get but handles any URL instead of known hosts
|
||||||
def self.fetch(path, headers = DEFAULT_HEADERS, body = nil, backend = nil)
|
def self.fetch(path:, headers: DEFAULT_HEADERS, body: nil, backend: :w3dhub, async: true, &callback)
|
||||||
async_http(:get, path, headers, body, backend)
|
async_http(method: :get, path: path, headers: headers, body: body, backend: backend, async: async, &callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Method: POST
|
# Method: POST
|
||||||
@@ -141,220 +87,270 @@ 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, backend = :w3dhub)
|
def self.refresh_user_login(refresh_token, backend = :w3dhub, &callback)
|
||||||
body = URI.encode_www_form("data": JSON.dump({refreshToken: refresh_token}))
|
body = URI.encode_www_form("data": JSON.dump({ refreshToken: refresh_token }))
|
||||||
response = post("/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body, backend)
|
|
||||||
|
|
||||||
if response.status == 200
|
handler = lambda do |result|
|
||||||
user_data = JSON.parse(response.body, symbolize_names: true)
|
if result.okay?
|
||||||
|
user_data = JSON.parse(result.data, symbolize_names: true)
|
||||||
|
|
||||||
return false if user_data[:error]
|
if user_data[:error]
|
||||||
|
callback.call(CyberarmEngine::Result.new(data: false))
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
user_details_data = user_details(user_data[:userid]) || {}
|
user_details_data = user_details(user_data[:userid]) || {}
|
||||||
|
|
||||||
Account.new(user_data, user_details_data)
|
callback.call(CyberarmEngine::Result.new(data: Account.new(user_data, user_details_data)))
|
||||||
else
|
else
|
||||||
logger.error(LOG_TAG) { "Failed to fetch refresh user login:" }
|
logger.error(LOG_TAG) { "Failed to fetch refresh user login:" }
|
||||||
logger.error(LOG_TAG) { response }
|
logger.error(LOG_TAG) { result.error }
|
||||||
false
|
|
||||||
|
callback.call(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post(path: "/apis/launcher/1/user-login", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# See #user_refresh_token
|
# See #user_refresh_token
|
||||||
def self.user_login(username, password, backend = :w3dhub)
|
def self.user_login(username, password, backend = :w3dhub, &callback)
|
||||||
body = URI.encode_www_form("data": JSON.dump({username: username, password: password}))
|
body = URI.encode_www_form("data": JSON.dump({ username: username, password: password }))
|
||||||
response = post("/apis/launcher/1/user-login", FORM_ENCODED_HEADERS, body, backend)
|
|
||||||
|
|
||||||
if response.status == 200
|
handler = lambda do |result|
|
||||||
user_data = JSON.parse(response.body, symbolize_names: true)
|
if result.okay?
|
||||||
|
user_data = JSON.parse(result.data, symbolize_names: true)
|
||||||
|
|
||||||
return false if user_data[:error]
|
if user_data[:error]
|
||||||
|
callback.call(CyberarmEngine::Result.new(data: false))
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
user_details_data = user_details(user_data[:userid]) || {}
|
user_details_data = user_details(user_data[:userid]) || {}
|
||||||
|
|
||||||
Account.new(user_data, user_details_data)
|
callback.call(CyberarmEngine::Result.new(data: Account.new(user_data, user_details_data)))
|
||||||
else
|
else
|
||||||
logger.error(LOG_TAG) { "Failed to fetch user login:" }
|
logger.error(LOG_TAG) { "Failed to fetch user login:" }
|
||||||
logger.error(LOG_TAG) { response }
|
logger.error(LOG_TAG) { result.error }
|
||||||
false
|
|
||||||
|
callback.call(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post(path: "/apis/launcher/1/user-login", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# /apis/w3dhub/1/get-user-details
|
# /apis/w3dhub/1/get-user-details
|
||||||
#
|
#
|
||||||
# Response: avatar-uri (Image download uri), id, username
|
# Response: avatar-uri (Image download uri), id, username
|
||||||
def self.user_details(id, backend = :w3dhub)
|
def self.user_details(id, backend = :w3dhub, &callback)
|
||||||
body = URI.encode_www_form("data": JSON.dump({ id: id }))
|
body = URI.encode_www_form("data": JSON.dump({ id: id }))
|
||||||
user_details = post("/apis/w3dhub/1/get-user-details", FORM_ENCODED_HEADERS, body, backend)
|
|
||||||
|
|
||||||
if user_details.status == 200
|
handler = lambda do |result|
|
||||||
JSON.parse(user_details.body, symbolize_names: true)
|
if result.okay?
|
||||||
else
|
callback.call(CyberarmEngine::Result.new(data: JSON.parse(result.data, symbolize_names: true)))
|
||||||
logger.error(LOG_TAG) { "Failed to fetch user details:" }
|
else
|
||||||
logger.error(LOG_TAG) { user_details }
|
logger.error(LOG_TAG) { "Failed to fetch user details:" }
|
||||||
false
|
logger.error(LOG_TAG) { result.error }
|
||||||
|
|
||||||
|
callback.call(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post(path: "/apis/w3dhub/1/get-user-details", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler)
|
||||||
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(backend = :w3dhub)
|
def self.service_status(backend = :w3dhub, &callback)
|
||||||
response = post("/apis/w3dhub/1/get-service-status", DEFAULT_HEADERS, nil, backend)
|
handler = lambda do |result|
|
||||||
|
if result.okay?
|
||||||
|
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 }
|
||||||
|
|
||||||
if response.status == 200
|
callback.call(result)
|
||||||
ServiceStatus.new(response.body)
|
end
|
||||||
else
|
|
||||||
logger.error(LOG_TAG) { "Failed to fetch service status:" }
|
|
||||||
logger.error(LOG_TAG) { response }
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post(path: "/apis/w3dhub/1/get-service-status", backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# /apis/launcher/1/get-applications
|
# /apis/launcher/1/get-applications
|
||||||
# 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(backend = :w3dhub)
|
def self.applications(backend = :w3dhub, &callback)
|
||||||
response = post("/apis/launcher/1/get-applications", DEFAULT_HEADERS, nil, backend)
|
async = !callback.nil?
|
||||||
|
|
||||||
if response.status == 200
|
# Complicated why to "return" direct value
|
||||||
Applications.new(response.body, backend)
|
callback = ->(result) { result }
|
||||||
else
|
|
||||||
logger.error(LOG_TAG) { "Failed to fetch applications list:" }
|
handler = lambda do |result|
|
||||||
logger.error(LOG_TAG) { response }
|
if result.okay?
|
||||||
false
|
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(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post(path: "/apis/launcher/1/get-applications", async: async, backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Populate applications list from primary and alternate backends
|
# Populate applications list from primary and alternate backends
|
||||||
# (alternate only has latest public builds of _most_ games)
|
# (alternate only has latest public builds of _most_ games)
|
||||||
def self._applications
|
def self._applications(&callback)
|
||||||
applications_primary = Store.account ? Api.applications(:w3dhub) : false
|
handler = lambda do |result|
|
||||||
applications_alternate = Api.applications(:alt_w3dhub)
|
# 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
|
# Fail if we fail to fetch applications list from either backend
|
||||||
return false unless applications_primary || applications_alternate
|
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
|
# Merge the two app lists together
|
||||||
apps = applications_alternate
|
apps = applications_alternate
|
||||||
if applications_primary
|
if applications_primary
|
||||||
applications_primary.games.each do |game|
|
applications_primary.games.each do |game|
|
||||||
# Check if game exists in alternate list
|
# Check if game exists in alternate list
|
||||||
_game = apps.games.find { |g| g.id == game.id }
|
_game = apps.games.find { |g| g.id == game.id }
|
||||||
unless _game
|
unless _game
|
||||||
apps.games << game
|
apps.games << game
|
||||||
|
|
||||||
# App didn't exist in alternates list
|
# App didn't exist in alternates list
|
||||||
# comparing channels isn't useful
|
# 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
|
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
# If channel versions and access levels match then all's well
|
# If it does, check that all of its channels also exist in alternate list
|
||||||
if channel.current_version == _channel.current_version &&
|
# and that the primary versions are the same as the alternates list
|
||||||
channel.user_level == _channel.user_level
|
game.channels.each do |channel|
|
||||||
|
_channel = _game.channels.find { |c| c.id == channel.id }
|
||||||
|
|
||||||
# All's Well!
|
unless _channel
|
||||||
next
|
_game.channels << channel
|
||||||
end
|
|
||||||
|
|
||||||
# If the access levels don't match then overwrite alternate's channel with primary's channel
|
# App didn't have channel in alternates list
|
||||||
if channel.user_level != _channel.user_level
|
# comparing channel isn't useful
|
||||||
# Replace alternate's channel with primary's channel
|
next
|
||||||
_game.channels[_game.channels.index(_channel)] = channel
|
end
|
||||||
|
|
||||||
# Replaced, continue.
|
# If channel versions and access levels match then all's well
|
||||||
next
|
if channel.current_version == _channel.current_version &&
|
||||||
end
|
channel.user_level == _channel.user_level
|
||||||
|
|
||||||
# If versions don't match then pick whichever one is higher
|
# All's Well!
|
||||||
if Gem::Version.new(channel.current_version) > Gem::Version.new(_channel.current_version)
|
next
|
||||||
# Replace alternate's channel with primary's channel
|
end
|
||||||
_game.channels[_game.channels.index(_channel)] = channel
|
|
||||||
else
|
# If the access levels don't match then overwrite alternate's channel with primary's channel
|
||||||
# Do nothing, alternate backend version is greater.
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
callback.call(CyberarmEngine::Result.new(data: apps))
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
# /apis/w3dhub/1/get-news
|
# /apis/w3dhub/1/get-news
|
||||||
# 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, backend = :w3dhub)
|
def self.news(category, backend = :w3dhub, &callback)
|
||||||
body = URI.encode_www_form("data": JSON.dump({category: category}))
|
handler = lambda do |result|
|
||||||
response = post("/apis/w3dhub/1/get-news", FORM_ENCODED_HEADERS, body, backend)
|
if result.okay?
|
||||||
|
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 }
|
||||||
|
|
||||||
if response.status == 200
|
callback.call(result)
|
||||||
News.new(response.body)
|
end
|
||||||
else
|
|
||||||
logger.error(LOG_TAG) { "Failed to fetch news for:" }
|
|
||||||
logger.error(LOG_TAG) { category }
|
|
||||||
logger.error(LOG_TAG) { response }
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
body = URI.encode_www_form("data": JSON.dump({ category: category }))
|
||||||
|
post(path: "/apis/w3dhub/1/get-news", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Downloading games
|
# Downloading games
|
||||||
|
|
||||||
# /apis/launcher/1/get-package-details
|
# /apis/launcher/1/get-package-details
|
||||||
# client requests package details: data={"packages":[{"category":"games","name":"apb.ico","subcategory":"apb","version":""}]}
|
# client requests package details: data={"packages":[{"category":"games","name":"apb.ico","subcategory":"apb","version":""}]}
|
||||||
def self.package_details(packages, backend = :w3dhub)
|
def self.package_details(packages, backend = :w3dhub, &callback)
|
||||||
body = URI.encode_www_form("data": JSON.dump({ packages: packages }))
|
handler = lambda do |result|
|
||||||
response = post("/apis/launcher/1/get-package-details", FORM_ENCODED_HEADERS, body, backend)
|
if result.okay?
|
||||||
|
hash = JSON.parse(result.data, symbolize_names: true)
|
||||||
|
|
||||||
if response.status == 200
|
callback.call(CyberarmEngine::Result.new(data: hash[:packages].map { |pkg| Package.new(pkg) }))
|
||||||
hash = JSON.parse(response.body, symbolize_names: true)
|
else
|
||||||
|
logger.error(LOG_TAG) { "Failed to fetch package details for:" }
|
||||||
|
logger.error(LOG_TAG) { packages }
|
||||||
|
logger.error(LOG_TAG) { result.error }
|
||||||
|
|
||||||
hash[:packages].map { |pkg| Package.new(pkg) }
|
callback.call(result)
|
||||||
else
|
end
|
||||||
logger.error(LOG_TAG) { "Failed to fetch package details for:" }
|
|
||||||
logger.error(LOG_TAG) { packages }
|
|
||||||
logger.error(LOG_TAG) { response }
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
body = URI.encode_www_form("data": JSON.dump({ packages: packages }))
|
||||||
|
post(path: "/apis/launcher/1/get-package-details", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# /apis/launcher/1/get-package
|
# /apis/launcher/1/get-package
|
||||||
# client requests package: data={"category":"games","name":"ECW_Asteroids.zip","subcategory":"ecw","version":"1.0.0.0"}
|
# client requests package: data={"category":"games","name":"ECW_Asteroids.zip","subcategory":"ecw","version":"1.0.0.0"}
|
||||||
#
|
#
|
||||||
# server responds with download bytes, probably supports chunked download and resume
|
# server responds with download bytes, probably supports chunked download and resume
|
||||||
def self.package(package, &block)
|
# FIXME: REFACTOR Cache.fetch_package to use HttpClient
|
||||||
Cache.fetch_package(package, block)
|
def self.package(package, &callback)
|
||||||
|
Cache.fetch_package(package, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
# /apis/w3dhub/1/get-events
|
# /apis/w3dhub/1/get-events
|
||||||
#
|
#
|
||||||
# clients requests events: data={"serverPath":"apb"}
|
# clients requests events: data={"serverPath":"apb"}
|
||||||
def self.events(app_id, backend = :w3dhub)
|
def self.events(app_id, backend = :w3dhub, &callback)
|
||||||
body = URI.encode_www_form("data": JSON.dump({ serverPath: app_id }))
|
handler = lambda do |result|
|
||||||
response = post("/apis/w3dhub/1/get-server-events", FORM_ENCODED_HEADERS, body, backend)
|
if result.okay?
|
||||||
|
array = JSON.parse(result.data, symbolize_names: true)
|
||||||
if response.status == 200
|
callback.call(CyberarmEngine::Result.new(data: array.map { |e| Event.new(e) }))
|
||||||
array = JSON.parse(response.body, symbolize_names: true)
|
else
|
||||||
array.map { |e| Event.new(e) }
|
callback.call(result)
|
||||||
else
|
end
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
body = URI.encode_www_form("data": JSON.dump({ serverPath: app_id }))
|
||||||
|
post(path: "/apis/w3dhub/1/get-server-events", headers: FORM_ENCODED_HEADERS, body: body, backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
#! === Server List API === !#
|
#! === Server List API === !#
|
||||||
@@ -380,15 +376,17 @@ 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, backend = :gsh)
|
def self.server_list(level = 1, backend = :gsh, &callback)
|
||||||
response = get("/listings/getAll/v2?statusLevel=#{level}", DEFAULT_HEADERS, nil, backend)
|
handler = lambda do |result|
|
||||||
|
if result.okay?
|
||||||
if response.status == 200
|
data = JSON.parse(result.data, symbolize_names: true)
|
||||||
data = JSON.parse(response.body, symbolize_names: true)
|
callback.call(CyberarmEngine::Result.new(data: data.map { |hash| ServerListServer.new(hash) }))
|
||||||
return data.map { |hash| ServerListServer.new(hash) }
|
else
|
||||||
|
callback.call(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
false
|
get(path: "/listings/getAll/v2?statusLevel=#{level}", backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# /listings/getStatus/v2/:id?statusLevel=#{0-2}
|
# /listings/getStatus/v2/:id?statusLevel=#{0-2}
|
||||||
@@ -402,23 +400,24 @@ 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, backend = :gsh)
|
def self.server_details(id, level, backend = :gsh, &callback)
|
||||||
return false unless id && level
|
return false unless id && level
|
||||||
|
|
||||||
response = get("/listings/getStatus/v2/#{id}?statusLevel=#{level}", DEFAULT_HEADERS, nil, backend)
|
handler = lambda do |result|
|
||||||
|
if result.okay?
|
||||||
if response.status == 200
|
callback.call(CyberarmEngine::Result.new(data: JSON.parse(result.data, symbolize_names: true)))
|
||||||
hash = JSON.parse(response.body, symbolize_names: true)
|
else
|
||||||
return hash
|
callback.call(result)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
false
|
get(path: "/listings/getStatus/v2/#{id}?statusLevel=#{level}", backend: backend, &handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
# /listings/push/v2/negotiate?negotiateVersion=1
|
# /listings/push/v2/negotiate?negotiateVersion=1
|
||||||
##? /listings/push/v2/?id=#{websocket token?}
|
##? /listings/push/v2/?id=#{websocket token?}
|
||||||
## Websocket server list listener
|
## Websocket server list listener
|
||||||
def self.server_list_push(id)
|
def self.server_list_push(id, &callback)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class W3DHub
|
|||||||
States::Interface.instance&.update_server_ping(self)
|
States::Interface.instance&.update_server_ping(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
return unless W3DHub.windows?
|
return #unless W3DHub.windows?
|
||||||
|
|
||||||
W3DHub::BackgroundWorker.foreground_parallel_job(
|
W3DHub::BackgroundWorker.foreground_parallel_job(
|
||||||
lambda do
|
lambda do
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class W3DHub
|
|||||||
@invocation_id = 0
|
@invocation_id = 0
|
||||||
|
|
||||||
logger.info(LOG_TAG) { "Starting emulated SignalR Server List Updater..." }
|
logger.info(LOG_TAG) { "Starting emulated SignalR Server List Updater..." }
|
||||||
run
|
# run
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
@@ -32,7 +32,8 @@ class W3DHub
|
|||||||
begin
|
begin
|
||||||
@auto_reconnect = true
|
@auto_reconnect = true
|
||||||
|
|
||||||
while W3DHub::BackgroundWorker.alive?
|
# FIXME
|
||||||
|
while true #W3DHub::BackgroundWorker.alive?
|
||||||
connect if @auto_reconnect
|
connect if @auto_reconnect
|
||||||
sleep @reconnection_delay
|
sleep @reconnection_delay
|
||||||
end
|
end
|
||||||
@@ -54,19 +55,31 @@ class W3DHub
|
|||||||
@auto_reconnect = false
|
@auto_reconnect = false
|
||||||
|
|
||||||
logger.debug(LOG_TAG) { "Requesting connection token..." }
|
logger.debug(LOG_TAG) { "Requesting connection token..." }
|
||||||
response = Api.post("/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, "", :gsh)
|
|
||||||
|
|
||||||
if response.status != 200
|
result = nil
|
||||||
|
Api.post("/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, "", :gsh) do |callback_result|
|
||||||
|
result = callback_result
|
||||||
|
end
|
||||||
|
|
||||||
|
# FIXME: we've introduced ourselves to callback hell, yay!
|
||||||
|
while result.nil?
|
||||||
|
sleep 0.1
|
||||||
|
end
|
||||||
|
|
||||||
|
if result.error?
|
||||||
@auto_reconnect = true
|
@auto_reconnect = true
|
||||||
@reconnection_delay = @reconnection_delay * 2
|
@reconnection_delay *= 2
|
||||||
@reconnection_delay = 60 if @reconnection_delay > 60
|
@reconnection_delay = 60 if @reconnection_delay > 60
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@reconnection_delay = 1
|
@reconnection_delay = 1
|
||||||
|
|
||||||
data = JSON.parse(response.body, symbolize_names: true)
|
connect_websocket(JSON.parse(result.data, symbolize_names: true))
|
||||||
|
end
|
||||||
|
|
||||||
|
def connect_websocket(data)
|
||||||
@invocation_id = 0 if @invocation_id > 9095
|
@invocation_id = 0 if @invocation_id > 9095
|
||||||
id = data[:connectionToken]
|
id = data[:connectionToken]
|
||||||
endpoint = "#{Api::SERVER_LIST_ENDPOINT}/listings/push/v2?id=#{id}"
|
endpoint = "#{Api::SERVER_LIST_ENDPOINT}/listings/push/v2?id=#{id}"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class W3DHub
|
|||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@tasks = [] # :installer, :importer, :repairer, :uninstaller
|
@tasks = [] # :installer, :importer, :repairer, :uninstaller
|
||||||
|
@running_applications = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def install(app_id, channel)
|
def install(app_id, channel)
|
||||||
@@ -22,9 +23,7 @@ class W3DHub
|
|||||||
# unpack packages
|
# unpack packages
|
||||||
# install dependencies (e.g. visual C runtime)
|
# install dependencies (e.g. visual C runtime)
|
||||||
|
|
||||||
installer = Installer.new(app_id, channel)
|
@tasks.push(Installer.new(context: task_context(app_id, channel, "version")))
|
||||||
|
|
||||||
@tasks.push(installer)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(app_id, channel)
|
def update(app_id, channel)
|
||||||
@@ -32,7 +31,7 @@ class W3DHub
|
|||||||
|
|
||||||
return false unless installed?(app_id, channel)
|
return false unless installed?(app_id, channel)
|
||||||
|
|
||||||
updater = Updater.new(app_id, channel)
|
updater = Updater.new(Installer.new(context: task_context(app_id, channel, "version")))
|
||||||
|
|
||||||
@tasks.push(updater)
|
@tasks.push(updater)
|
||||||
end
|
end
|
||||||
@@ -97,6 +96,18 @@ class W3DHub
|
|||||||
Process.spawn(exe)
|
Process.spawn(exe)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def task_context(app_id, channel, version)
|
||||||
|
Task::Context.new(
|
||||||
|
SecureRandom.hex,
|
||||||
|
"games",
|
||||||
|
app_id,
|
||||||
|
channel,
|
||||||
|
version,
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def repair(app_id, channel)
|
def repair(app_id, channel)
|
||||||
logger.info(LOG_TAG) { "Repair Installation Request: #{app_id}-#{channel}" }
|
logger.info(LOG_TAG) { "Repair Installation Request: #{app_id}-#{channel}" }
|
||||||
|
|
||||||
@@ -109,7 +120,7 @@ class W3DHub
|
|||||||
# unpack packages
|
# unpack packages
|
||||||
# install dependencies (e.g. visual C runtime) if appropriate
|
# install dependencies (e.g. visual C runtime) if appropriate
|
||||||
|
|
||||||
@tasks.push(Repairer.new(app_id, channel))
|
@tasks.push(Repairer.new(context: task_context(app_id, channel, "version")))
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall(app_id, channel)
|
def uninstall(app_id, channel)
|
||||||
@@ -124,7 +135,7 @@ class W3DHub
|
|||||||
title: "Uninstall #{game.name}?",
|
title: "Uninstall #{game.name}?",
|
||||||
message: "Are you sure you want to uninstall #{game.name} (#{channel})?",
|
message: "Are you sure you want to uninstall #{game.name} (#{channel})?",
|
||||||
accept_callback: proc {
|
accept_callback: proc {
|
||||||
@tasks.push(Uninstaller.new(app_id, channel))
|
@tasks.push(Uninstaller.new(context: task_context(app_id, channel, "version")))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -169,11 +180,17 @@ class W3DHub
|
|||||||
def wine_command(app_id, channel)
|
def wine_command(app_id, channel)
|
||||||
return "" if W3DHub.windows?
|
return "" if W3DHub.windows?
|
||||||
|
|
||||||
if !Store.settings[:wine_prefix].to_s.empty?
|
"\"#{Store.settings[:wine_command]}\" "
|
||||||
"WINEPREFIX=\"#{Store.settings[:wine_prefix]}\" \"#{Store.settings[:wine_command]}\" "
|
end
|
||||||
else
|
|
||||||
"#{Store.settings[:wine_command]} "
|
def wine_enviroment_variables(app_id, channel)
|
||||||
end
|
vars = {}
|
||||||
|
return vars if W3DHub.windows?
|
||||||
|
|
||||||
|
vars["WINEPREFIX"] = Store.settings[:wine_prefix] unless Store.settings[:wine_prefix].to_s.empty?
|
||||||
|
# vars["WINEDEBUG"] = "-all" if true # TODO make this an option. wine debug interferences with pid returned from Process.spawn
|
||||||
|
|
||||||
|
vars
|
||||||
end
|
end
|
||||||
|
|
||||||
def mangohud_command(app_id, channel)
|
def mangohud_command(app_id, channel)
|
||||||
@@ -188,6 +205,13 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mangohud_enviroment_variables(app_id, channel)
|
||||||
|
vars = {}
|
||||||
|
return vars if W3DHub.windows?
|
||||||
|
|
||||||
|
vars
|
||||||
|
end
|
||||||
|
|
||||||
def dxvk_command(app_id, channel)
|
def dxvk_command(app_id, channel)
|
||||||
return "" if W3DHub.windows?
|
return "" if W3DHub.windows?
|
||||||
|
|
||||||
@@ -201,6 +225,13 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dxvk_enviroment_variables(app_id, channel)
|
||||||
|
vars = {}
|
||||||
|
return vars if W3DHub.windows?
|
||||||
|
|
||||||
|
vars
|
||||||
|
end
|
||||||
|
|
||||||
def start_command(path, exe)
|
def start_command(path, exe)
|
||||||
if W3DHub.windows?
|
if W3DHub.windows?
|
||||||
"start /D \"#{path}\" /B #{exe}"
|
"start /D \"#{path}\" /B #{exe}"
|
||||||
@@ -212,17 +243,33 @@ class W3DHub
|
|||||||
def run(app_id, channel, *args)
|
def run(app_id, channel, *args)
|
||||||
if (app_data = installed?(app_id, channel))
|
if (app_data = installed?(app_id, channel))
|
||||||
install_directory = app_data[:install_directory]
|
install_directory = app_data[:install_directory]
|
||||||
exe_path = app_id == "ecw" ? "#{install_directory}/game500.exe" : "#{install_directory}/game.exe"
|
exe_path = app_id == "ecw" ? "#{install_directory}/game500.exe" : app_data[:install_path]
|
||||||
exe_path.gsub!("/", "\\") if W3DHub.windows?
|
exe_path.gsub!("/", "\\") if W3DHub.windows?
|
||||||
exe_path.gsub!("\\", "/") if W3DHub.unix?
|
exe_path.gsub!("\\", "/") if W3DHub.unix?
|
||||||
|
|
||||||
exe = File.basename(exe_path)
|
exe = File.basename(exe_path)
|
||||||
path = File.dirname(exe_path)
|
path = File.dirname(exe_path)
|
||||||
|
|
||||||
|
env = {}
|
||||||
|
if W3DHub.unix?
|
||||||
|
env.merge!(
|
||||||
|
dxvk_enviroment_variables(app_id, channel),
|
||||||
|
mangohud_enviroment_variables(app_id, channel),
|
||||||
|
wine_enviroment_variables(app_id, channel)
|
||||||
|
)
|
||||||
|
end
|
||||||
attempted = false
|
attempted = false
|
||||||
begin
|
begin
|
||||||
pid = Process.spawn("#{dxvk_command(app_id, channel)}#{mangohud_command(app_id, channel)}#{wine_command(app_id, channel)}#{attempted ? start_command(path, exe) : "\"#{exe_path}\""} -launcher #{args.join(' ')}")
|
pid = Process.spawn(
|
||||||
|
env,
|
||||||
|
"#{dxvk_command(app_id, channel)}"\
|
||||||
|
"#{mangohud_command(app_id, channel)}"\
|
||||||
|
"#{wine_command(app_id, channel)}"\
|
||||||
|
"#{attempted ? start_command(path, exe) : "\"#{exe_path}\""} "\
|
||||||
|
"-launcher #{args.join(' ')}"
|
||||||
|
)
|
||||||
Process.detach(pid)
|
Process.detach(pid)
|
||||||
|
BackgroundWorker.foreground_parallel_job(-> { monitor_process(app_id, channel, pid) }, ->(result) { handle_process_result(app_id, channel, result) })
|
||||||
rescue Errno::EINVAL => e
|
rescue Errno::EINVAL => e
|
||||||
retryable = !attempted
|
retryable = !attempted
|
||||||
attempted = true
|
attempted = true
|
||||||
@@ -235,13 +282,52 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def monitor_process(app_id, channel, pid)
|
||||||
|
key = "#{app_id}-#{channel}"
|
||||||
|
@running_applications[key] = pid
|
||||||
|
|
||||||
|
status = Process::Status.wait(pid)
|
||||||
|
pp [pid, status]
|
||||||
|
|
||||||
|
@running_applications.delete(key)
|
||||||
|
|
||||||
|
status
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_process_result(app_id, channel, status)
|
||||||
|
pp [app_id, channel, status]
|
||||||
|
|
||||||
|
# Everything's fine
|
||||||
|
return if status.pid >= 0 && status.success?
|
||||||
|
|
||||||
|
# Everything's not fine
|
||||||
|
reason = status.pid.positive? ? "Crashed" : "Failed to Launch"
|
||||||
|
game = Store.applications.games.find { |g| g.id == app_id }
|
||||||
|
title = "#{reason}: #{game.name}" if game
|
||||||
|
title = "Application #{reason}" unless game
|
||||||
|
|
||||||
|
message = if status.pid.negative?
|
||||||
|
"Command Not Found."
|
||||||
|
else
|
||||||
|
"Application crashed."
|
||||||
|
end
|
||||||
|
|
||||||
|
push_state(
|
||||||
|
States::MessageDialog,
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
accept_callback: proc {
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def join_server(app_id, channel, server, username = Store.settings[:server_list_username], password = nil, multi = false)
|
def join_server(app_id, channel, server, username = Store.settings[:server_list_username], password = nil, multi = false)
|
||||||
if installed?(app_id, channel) && username.to_s.length.positive?
|
return unless installed?(app_id, channel) && username.to_s.length.positive?
|
||||||
run(
|
|
||||||
app_id, channel,
|
run(
|
||||||
"+connect #{server.address}:#{server.port} +netplayername #{username}#{password ? " +password \"#{password}\"" : ""}#{multi ? " +multi" : ""}"
|
app_id, channel,
|
||||||
)
|
"+connect #{server.address}:#{server.port} +netplayername #{username}#{password ? " +password \"#{password}\"" : ""}#{multi ? " +multi" : ""}"
|
||||||
end
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def play_now_server(app_id, channel)
|
def play_now_server(app_id, channel)
|
||||||
@@ -469,7 +555,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def installing?(app_id, channel)
|
def installing?(app_id, channel)
|
||||||
@tasks.find { |t| t.is_a?(Installer) && t.app_id == app_id && t.release_channel == channel }
|
@tasks.find { |t| t.is_a?(Installer) && t.context.app_id == app_id && t.context.channel_id == channel }
|
||||||
end
|
end
|
||||||
|
|
||||||
def updateable?(app_id, channel)
|
def updateable?(app_id, channel)
|
||||||
@@ -522,6 +608,54 @@ class W3DHub
|
|||||||
app.channels.detect { |g| g.id.to_s == channel_id.to_s }
|
app.channels.detect { |g| g.id.to_s == channel_id.to_s }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_task_event(event)
|
||||||
|
# ONLY CALL on MAIN Ractor
|
||||||
|
raise "Something has gone horribly wrong!" unless Ractor.main?
|
||||||
|
|
||||||
|
pp event
|
||||||
|
task = @tasks.find { |t| t.context.task_id == event.task_id }
|
||||||
|
return unless task # FIXME: This is probably a fatal error
|
||||||
|
|
||||||
|
case event.type
|
||||||
|
when Task::EVENT_FAILURE
|
||||||
|
Store.main_thread_queue << proc do
|
||||||
|
window.push_state(
|
||||||
|
W3DHub::States::MessageDialog,
|
||||||
|
type: event.data[:type],
|
||||||
|
title: event.data[:title],
|
||||||
|
message: event.data[:message]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
# FIXME: Send event to Games page to trigger refresh
|
||||||
|
|
||||||
|
States::Interface.instance&.hide_application_taskbar
|
||||||
|
@tasks.delete(task)
|
||||||
|
|
||||||
|
when Task::EVENT_START
|
||||||
|
States::Interface.instance&.show_application_taskbar
|
||||||
|
|
||||||
|
when Task::EVENT_SUCCESS
|
||||||
|
States::Interface.instance&.hide_application_taskbar
|
||||||
|
@tasks.delete(task)
|
||||||
|
# FIXME: Send event to Games page to trigger refresh
|
||||||
|
when Task::EVENT_STATUS
|
||||||
|
task.status = event.data
|
||||||
|
States::Interface.instance&.update_interface_task_status(task)
|
||||||
|
|
||||||
|
when Task::EVENT_STATUS_OPERATION
|
||||||
|
hash = event.data
|
||||||
|
operation = task.status.operations[operation[:id]]
|
||||||
|
|
||||||
|
if operation
|
||||||
|
operation.label = hash[:label]
|
||||||
|
operation.value = hash[:value]
|
||||||
|
operation.progress = hash[:progress]
|
||||||
|
|
||||||
|
States::Interface.instance&.update_interface_task_status(task)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# No application tasks are being done
|
# No application tasks are being done
|
||||||
def idle?
|
def idle?
|
||||||
!busy?
|
!busy?
|
||||||
@@ -542,15 +676,37 @@ class W3DHub
|
|||||||
@tasks.delete_if { |t| t.state == :complete || t.state == :halted || t.state == :failed }
|
@tasks.delete_if { |t| t.state == :complete || t.state == :halted || t.state == :failed }
|
||||||
|
|
||||||
task = @tasks.find { |t| t.state == :not_started }
|
task = @tasks.find { |t| t.state == :not_started }
|
||||||
task&.start
|
|
||||||
|
return unless task
|
||||||
|
|
||||||
|
# mark MAIN ractor's task as started before handing off to background ractor
|
||||||
|
# so that we don't start up multiple tasks at once.
|
||||||
|
task.start
|
||||||
|
on_ractor(task)
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_ractor(task)
|
||||||
|
raise "Something has gone horribly wrong!!!" unless Ractor.main?
|
||||||
|
|
||||||
|
ractor = Ractor.new(task) do |t|
|
||||||
|
t.start
|
||||||
|
end
|
||||||
|
|
||||||
|
Thread.new do
|
||||||
|
while (message_event = ractor.take)
|
||||||
|
break unless message_event.is_a?(Task::MessageEvent)
|
||||||
|
|
||||||
|
Store.application_manager.handle_task_event(message_event)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def task?(type, app_id, channel)
|
def task?(type, app_id, channel)
|
||||||
@tasks.find do |t|
|
@tasks.find do |t|
|
||||||
t.type == type &&
|
t.type == type &&
|
||||||
t.app_id == app_id &&
|
t.context.app_id == app_id &&
|
||||||
t.release_channel == channel &&
|
t.context.channel_id == channel &&
|
||||||
[ :not_started, :running, :paused ].include?(t.state)
|
[ :not_started, :running, :paused ].include?(t.state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
class W3DHub
|
class W3DHub
|
||||||
class ApplicationManager
|
class ApplicationManager
|
||||||
class Status
|
class Status
|
||||||
attr_reader :application, :channel, :step, :operations, :data
|
attr_reader :application, :channel, :operations, :data
|
||||||
attr_accessor :label, :value, :progress
|
attr_accessor :label, :value, :progress, :step
|
||||||
|
|
||||||
def initialize(application:, channel:, label: "", value: "", progress: 0.0, step: :pending, operations: {}, &callback)
|
def initialize(application:, channel:, label: "", value: "", progress: 0.0, step: :pending, operations: {})
|
||||||
@application = application
|
@application = application
|
||||||
@channel = channel
|
@channel = channel
|
||||||
|
|
||||||
@@ -15,17 +15,10 @@ class W3DHub
|
|||||||
@step = step
|
@step = step
|
||||||
@operations = operations
|
@operations = operations
|
||||||
|
|
||||||
@callback = callback
|
|
||||||
|
|
||||||
@data = {}
|
@data = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def step=(sym)
|
|
||||||
@step = sym
|
|
||||||
@callback&.call(self)
|
|
||||||
@step
|
|
||||||
end
|
|
||||||
|
|
||||||
class Operation
|
class Operation
|
||||||
attr_accessor :label, :value, :progress
|
attr_accessor :label, :value, :progress
|
||||||
|
|
||||||
|
|||||||
@@ -746,6 +746,8 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
patch_entry = patch_mix.entries.find { |e| e.name.casecmp?(".w3dhub.patch") || e.name.casecmp?(".bhppatch") }
|
patch_entry = patch_mix.entries.find { |e| e.name.casecmp?(".w3dhub.patch") || e.name.casecmp?(".bhppatch") }
|
||||||
patch_entry.read
|
patch_entry.read
|
||||||
|
# "remove" patch meta file from patch before copying patch data
|
||||||
|
patch_mix.entries.delete(patch_entry)
|
||||||
|
|
||||||
patch_info = JSON.parse(patch_entry.blob, symbolize_names: true)
|
patch_info = JSON.parse(patch_entry.blob, symbolize_names: true)
|
||||||
|
|
||||||
@@ -765,20 +767,15 @@ class W3DHub
|
|||||||
patch_info[:updatedFiles].each do |file|
|
patch_info[:updatedFiles].each do |file|
|
||||||
logger.debug(LOG_TAG) { " #{file}" }
|
logger.debug(LOG_TAG) { " #{file}" }
|
||||||
|
|
||||||
patch = patch_mix.entries.find { |e| e.name.casecmp?(file) }
|
patch_mix.entries.each do |entry|
|
||||||
target = target_mix.entries.find { |e| e.name.casecmp?(file) }
|
target_mix.add_entry(entry: entry, replace: true)
|
||||||
|
|
||||||
if target
|
|
||||||
target_mix.entries[target_mix.entries.index(target)] = patch
|
|
||||||
else
|
|
||||||
target_mix.entries << patch
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
logger.info(LOG_TAG) { " Writing updated #{file_path}..." } if patch_info[:updatedFiles].size.positive?
|
logger.info(LOG_TAG) { " Writing updated #{file_path}..." } if patch_info[:updatedFiles].size.positive?
|
||||||
temp_mix_path = "#{temp_path}/#{File.basename(file_path)}"
|
temp_mix_path = "#{temp_path}/#{File.basename(file_path)}"
|
||||||
temp_mix = W3DHub::WWMix.new(path: temp_mix_path)
|
temp_mix = W3DHub::WWMix.new(path: temp_mix_path, encrypted: target_mix.encrypted?)
|
||||||
target_mix.entries.each { |e| temp_mix.add_entry(entry: e) }
|
target_mix.entries.each { |e| temp_mix.add_entry(entry: e, replace: true) }
|
||||||
unless temp_mix.save
|
unless temp_mix.save
|
||||||
raise temp_mix.error_reason
|
raise temp_mix.error_reason
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def execute_task
|
def execute_task
|
||||||
show_application_taskbar
|
fail_fast!
|
||||||
|
|
||||||
fail_fast
|
|
||||||
return false if failed?
|
return false if failed?
|
||||||
|
|
||||||
fetch_manifests
|
fetch_manifests
|
||||||
@@ -46,9 +44,6 @@ class W3DHub
|
|||||||
mark_application_installed
|
mark_application_installed
|
||||||
return false if failed?
|
return false if failed?
|
||||||
|
|
||||||
sleep 1
|
|
||||||
hide_application_taskbar
|
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
class W3DHub
|
|
||||||
class BackgroundWorker
|
|
||||||
LOG_TAG = "W3DHub::BackgroundWorker"
|
|
||||||
@@instance = nil
|
|
||||||
@@alive = false
|
|
||||||
|
|
||||||
def self.create
|
|
||||||
raise "BackgroundWorker instance already exists!" if @@instance
|
|
||||||
logger.info(LOG_TAG) { "Starting background job worker..." }
|
|
||||||
|
|
||||||
|
|
||||||
@@thread = Thread.current
|
|
||||||
@@alive = true
|
|
||||||
@@run = true
|
|
||||||
@@instance = self.new
|
|
||||||
|
|
||||||
@@instance.handle_jobs
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.instance
|
|
||||||
@@instance
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.run?
|
|
||||||
@@run
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.alive?
|
|
||||||
@@alive
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.busy?
|
|
||||||
instance&.busy?
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.shutdown!
|
|
||||||
@@run = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.kill!
|
|
||||||
@@thread.kill
|
|
||||||
|
|
||||||
@@instance.kill!
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.job(job, callback, error_handler = nil, data = nil)
|
|
||||||
@@instance.add_job(Job.new(job: job, callback: callback, error_handler: error_handler, data: data))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.parallel_job(job, callback, error_handler = nil, data = nil)
|
|
||||||
@@instance.add_parallel_job(Job.new(job: job, callback: callback, error_handler: error_handler, data: data))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.foreground_job(job, callback, error_handler = nil, data = nil)
|
|
||||||
@@instance.add_job(Job.new(job: job, callback: callback, error_handler: error_handler, deliver_to_queue: true, data: data))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.foreground_parallel_job(job, callback, error_handler = nil, data = nil)
|
|
||||||
@@instance.add_parallel_job(Job.new(job: job, callback: callback, error_handler: error_handler, deliver_to_queue: true, data: data))
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@busy = false
|
|
||||||
@jobs = []
|
|
||||||
|
|
||||||
# Jobs which are order independent
|
|
||||||
@parallel_busy = false
|
|
||||||
@thread_pool = []
|
|
||||||
@parallel_jobs = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def kill!
|
|
||||||
@thread_pool.each(&:kill)
|
|
||||||
|
|
||||||
logger.info(LOG_TAG) { "Forcefully killed background job worker." }
|
|
||||||
@@alive = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_jobs
|
|
||||||
8.times do |i|
|
|
||||||
Thread.new do
|
|
||||||
@thread_pool << Thread.current
|
|
||||||
|
|
||||||
while BackgroundWorker.run?
|
|
||||||
job = @parallel_jobs.shift
|
|
||||||
|
|
||||||
@parallel_busy = true
|
|
||||||
|
|
||||||
begin
|
|
||||||
job&.do
|
|
||||||
rescue => e
|
|
||||||
job&.raise_error(e)
|
|
||||||
end
|
|
||||||
|
|
||||||
@parallel_busy = !@parallel_jobs.empty?
|
|
||||||
|
|
||||||
sleep 0.1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Thread.new do
|
|
||||||
@thread_pool << Thread.current
|
|
||||||
|
|
||||||
while BackgroundWorker.run?
|
|
||||||
job = @jobs.shift
|
|
||||||
|
|
||||||
@busy = true
|
|
||||||
|
|
||||||
begin
|
|
||||||
job&.do
|
|
||||||
rescue => e
|
|
||||||
job&.raise_error(e)
|
|
||||||
end
|
|
||||||
|
|
||||||
@busy = !@jobs.empty?
|
|
||||||
|
|
||||||
sleep 0.1
|
|
||||||
end
|
|
||||||
|
|
||||||
logger.info(LOG_TAG) { "Stopped background job worker." }
|
|
||||||
@@alive = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_job(job)
|
|
||||||
@jobs << job
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_parallel_job(job)
|
|
||||||
@parallel_jobs << job
|
|
||||||
end
|
|
||||||
|
|
||||||
def busy?
|
|
||||||
@busy || @parallel_busy
|
|
||||||
end
|
|
||||||
|
|
||||||
class Job
|
|
||||||
def initialize(job:, callback:, error_handler: nil, deliver_to_queue: false, data: nil)
|
|
||||||
@job = job
|
|
||||||
@callback = callback
|
|
||||||
@error_handler = error_handler
|
|
||||||
@deliver_to_queue = deliver_to_queue
|
|
||||||
@data = data
|
|
||||||
end
|
|
||||||
|
|
||||||
def do
|
|
||||||
result = @data ? @job.call(@data) : @job.call
|
|
||||||
deliver(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
def deliver(result)
|
|
||||||
if @deliver_to_queue
|
|
||||||
Store.main_thread_queue << -> { @callback.call(result) }
|
|
||||||
else
|
|
||||||
@callback.call(result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def raise_error(error)
|
|
||||||
logger.error error
|
|
||||||
@error_handler&.call(error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
16
lib/cache.rb
16
lib/cache.rb
@@ -9,19 +9,17 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Fetch a generic uri
|
# Fetch a generic uri
|
||||||
def self.fetch(uri:, force_fetch: false, async: true, backend: :w3dhub)
|
def self.fetch(uri:, force_fetch: false, backend: :w3dhub)
|
||||||
path = path(uri)
|
path = path(uri)
|
||||||
|
|
||||||
if !force_fetch && File.exist?(path)
|
if !force_fetch && File.exist?(path)
|
||||||
path
|
path
|
||||||
elsif async
|
|
||||||
BackgroundWorker.job(
|
|
||||||
-> { Api.fetch(uri, W3DHub::Api::DEFAULT_HEADERS, nil, backend) },
|
|
||||||
->(response) { File.open(path, "wb") { |f| f.write response.body } if response.status == 200 }
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
response = Api.fetch(uri, W3DHub::Api::DEFAULT_HEADERS, nil, backend)
|
Api.fetch(path: uri, backend: backend) do |result|
|
||||||
File.open(path, "wb") { |f| f.write response.body } if response.status == 200
|
if result.okay?
|
||||||
|
File.open(path, "wb") { |f| f.write result.data }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -90,8 +88,6 @@ class W3DHub
|
|||||||
result = true
|
result = true
|
||||||
end
|
end
|
||||||
|
|
||||||
binding.irb unless response
|
|
||||||
|
|
||||||
if response&.status == 200 || response&.status == 206
|
if response&.status == 200 || response&.status == 206
|
||||||
result = true
|
result = true
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
class W3DHub
|
class W3DHub
|
||||||
|
PLATFORM_WINDOWS = RbConfig::CONFIG["host_os"] =~ /(mingw|mswin|windows)/i
|
||||||
|
PLATFORM_DARWIN = RbConfig::CONFIG["host_os"] =~ /(darwin|mac os)/i
|
||||||
|
PLATFORM_LINUX = RbConfig::CONFIG["host_os"] =~ /(linux|bsd|aix|solaris)/i
|
||||||
|
|
||||||
def self.format_size(bytes)
|
def self.format_size(bytes)
|
||||||
case bytes
|
case bytes
|
||||||
when 0..1023 # Bytes
|
when 0..1023 # Bytes
|
||||||
@@ -17,15 +21,15 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.windows?
|
def self.windows?
|
||||||
RbConfig::CONFIG["host_os"] =~ /(mingw|mswin|windows)/i
|
PLATFORM_WINDOWS
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.mac?
|
def self.mac?
|
||||||
RbConfig::CONFIG["host_os"] =~ /(darwin|mac os)/i
|
PLATFORM_DARWIN
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.linux?
|
def self.linux?
|
||||||
RbConfig::CONFIG["host_os"] =~ /(linux|bsd|aix|solaris)/i
|
PLATFORM_LINUX
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.unix?
|
def self.unix?
|
||||||
|
|||||||
92
lib/network_manager.rb
Normal file
92
lib/network_manager.rb
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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, :async, :callback)
|
||||||
|
Context = Data.define(
|
||||||
|
:request_id,
|
||||||
|
:method,
|
||||||
|
:url,
|
||||||
|
:headers,
|
||||||
|
:body
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@requests = []
|
||||||
|
@running = true
|
||||||
|
|
||||||
|
@thread = Thread.new do
|
||||||
|
@http_client = HttpClient.new
|
||||||
|
|
||||||
|
Sync do
|
||||||
|
while @running
|
||||||
|
request = @requests.find { |r| !r.active }
|
||||||
|
|
||||||
|
# goto sleep for an second if there is no work to be doing
|
||||||
|
unless request
|
||||||
|
sleep 1
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
request.active = true
|
||||||
|
|
||||||
|
Async do |task|
|
||||||
|
assigned_request = 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)
|
||||||
|
|
||||||
|
# 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, async, &block)
|
||||||
|
request_id = SecureRandom.hex
|
||||||
|
|
||||||
|
request = Request.new(
|
||||||
|
false,
|
||||||
|
Context.new(
|
||||||
|
request_id,
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
body
|
||||||
|
),
|
||||||
|
async,
|
||||||
|
block
|
||||||
|
)
|
||||||
|
|
||||||
|
@requests << request
|
||||||
|
|
||||||
|
|
||||||
|
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?
|
||||||
|
@requests.any?(&:active)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
57
lib/network_manager/http_client.rb
Normal file
57
lib/network_manager/http_client.rb
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
class W3DHub
|
||||||
|
class NetworkManager
|
||||||
|
# non-blocking, http requests.
|
||||||
|
class HttpClient
|
||||||
|
def initialize
|
||||||
|
@http_clients = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(task, request)
|
||||||
|
result = CyberarmEngine::Result.new
|
||||||
|
context = request.context
|
||||||
|
|
||||||
|
task.with_timeout(W3DHub::Api::API_TIMEOUT) do
|
||||||
|
uri = URI(context.url)
|
||||||
|
|
||||||
|
response = provision_http_client(uri.origin).send(
|
||||||
|
context.method,
|
||||||
|
uri.request_uri,
|
||||||
|
context.headers,
|
||||||
|
context.body
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.success?
|
||||||
|
result.data = response.read
|
||||||
|
else
|
||||||
|
result.error = response
|
||||||
|
end
|
||||||
|
rescue Async::TimeoutError => e
|
||||||
|
result.error = e
|
||||||
|
rescue StandardError => e
|
||||||
|
result.error = e
|
||||||
|
ensure
|
||||||
|
response&.close
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def provision_http_client(hostname)
|
||||||
|
return @http_clients[hostname.downcase] if @http_clients[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[hostname.downcase] = Async::HTTP::Client.new(endpoint)
|
||||||
|
end
|
||||||
|
|
||||||
|
def wrapped_error(error)
|
||||||
|
WrappedError.new(error.class, error.message.to_s, error.backtrace)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -65,15 +65,7 @@ class W3DHub
|
|||||||
para I18n.t(:"games.fetching_news"), padding: 8
|
para I18n.t(:"games.fetching_news"), padding: 8
|
||||||
end
|
end
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(
|
fetch_w3dhub_news
|
||||||
-> { fetch_w3dhub_news },
|
|
||||||
lambda do |result|
|
|
||||||
if result
|
|
||||||
populate_w3dhub_news
|
|
||||||
Cache.release_net_lock(result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -89,15 +81,7 @@ class W3DHub
|
|||||||
title I18n.t(:"games.fetching_news"), padding: 8
|
title I18n.t(:"games.fetching_news"), padding: 8
|
||||||
end
|
end
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(
|
fetch_w3dhub_news
|
||||||
-> { fetch_w3dhub_news },
|
|
||||||
lambda do |result|
|
|
||||||
if result
|
|
||||||
populate_w3dhub_news
|
|
||||||
Cache.release_net_lock(result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -105,19 +89,24 @@ class W3DHub
|
|||||||
lock = Cache.acquire_net_lock("w3dhub_news")
|
lock = Cache.acquire_net_lock("w3dhub_news")
|
||||||
return false unless lock
|
return false unless lock
|
||||||
|
|
||||||
news = Api.news("launcher-home")
|
Api.news("launcher-home") do |result|
|
||||||
Cache.release_net_lock("w3dhub_news") unless news
|
news = result.data
|
||||||
|
|
||||||
return unless news
|
Cache.release_net_lock("w3dhub_news") unless news
|
||||||
|
|
||||||
news.items[0..15].each do |item|
|
next false unless news
|
||||||
Cache.fetch(uri: item.image, async: false, backend: :w3dhub)
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
@w3dhub_news = news
|
|
||||||
@w3dhub_news_expires = Gosu.milliseconds + (60 * 60 * 1000) # 1 hour (in ms)
|
|
||||||
|
|
||||||
"w3dhub_news"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def populate_w3dhub_news
|
def populate_w3dhub_news
|
||||||
@@ -125,37 +114,7 @@ class W3DHub
|
|||||||
|
|
||||||
if (feed = @w3dhub_news)
|
if (feed = @w3dhub_news)
|
||||||
@wd3hub_news_container.clear do
|
@wd3hub_news_container.clear do
|
||||||
# feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item|
|
feed.items.sort_by(&: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 "<b>#{item.title}</b>", 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|
|
|
||||||
image_path = Cache.path(item.image)
|
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
|
flow(width: 1.0, max_width: 1230, height: 200, margin: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(0xff_252525))) do
|
||||||
|
|||||||
@@ -31,30 +31,16 @@ class W3DHub
|
|||||||
def update
|
def update
|
||||||
super
|
super
|
||||||
|
|
||||||
|
|
||||||
@game_news.each do |key, value|
|
@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"]
|
next unless Gosu.milliseconds >= value
|
||||||
@game_news.delete(key)
|
|
||||||
@game_news["#{key}_expires"] = Gosu.milliseconds + 30_000 # seconds
|
|
||||||
|
|
||||||
if @focused_game && @focused_game.id == key
|
# try to refresh game news after last data 'expired', every 30 seconds until success
|
||||||
@game_news_container.clear do
|
@game_news[key] = Gosu.milliseconds + 30_000 # seconds
|
||||||
title I18n.t(:"games.fetching_news"), padding: 8
|
|
||||||
end
|
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(
|
game = Store.applications.games.find { |g| g.id == key.split("_").first }
|
||||||
-> { fetch_game_news(@focused_game) },
|
fetch_game_news(game)
|
||||||
lambda do |result|
|
|
||||||
if result
|
|
||||||
populate_game_news(@focused_game)
|
|
||||||
Cache.release_net_lock(result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -292,22 +278,6 @@ class W3DHub
|
|||||||
return if Store.offline_mode
|
return if Store.offline_mode
|
||||||
|
|
||||||
unless Cache.net_lock?("game_news_#{game.id}")
|
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]
|
if @game_news[game.id]
|
||||||
populate_game_news(game)
|
populate_game_news(game)
|
||||||
else
|
else
|
||||||
@@ -315,15 +285,15 @@ class W3DHub
|
|||||||
title I18n.t(:"games.fetching_news"), padding: 8
|
title I18n.t(:"games.fetching_news"), padding: 8
|
||||||
end
|
end
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(
|
fetch_game_news(game)
|
||||||
-> { fetch_game_news(game) },
|
end
|
||||||
lambda do |result|
|
end
|
||||||
if result
|
|
||||||
populate_game_news(game)
|
unless Cache.net_lock?("game_events_#{game.id}")
|
||||||
Cache.release_net_lock(result)
|
if @game_events[game.id]
|
||||||
end
|
populate_game_events(game)
|
||||||
end
|
else
|
||||||
)
|
fetch_game_events(game)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -413,19 +383,25 @@ class W3DHub
|
|||||||
lock = Cache.acquire_net_lock("game_news_#{game.id}")
|
lock = Cache.acquire_net_lock("game_news_#{game.id}")
|
||||||
return false unless lock
|
return false unless lock
|
||||||
|
|
||||||
news = Api.news(game.id)
|
Api.news(game.id) do |result|
|
||||||
Cache.release_net_lock("game_news_#{game.id}") unless news
|
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|
|
news.items[0..15].each do |item|
|
||||||
Cache.fetch(uri: item.image, async: false, backend: :w3dhub)
|
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
|
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
|
end
|
||||||
|
|
||||||
def populate_game_news(game)
|
def populate_game_news(game)
|
||||||
@@ -436,38 +412,11 @@ class W3DHub
|
|||||||
game_color.alpha = 0xaa
|
game_color.alpha = 0xaa
|
||||||
|
|
||||||
@game_news_container.clear do
|
@game_news_container.clear do
|
||||||
# Patch Notes
|
feed.items.sort_by(&:timestamp).reverse[0..9].each do |item|
|
||||||
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 "<b>Patch 2.0 is now out!</b>"
|
|
||||||
|
|
||||||
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|
|
|
||||||
image_path = Cache.path(item.image)
|
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
|
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 if File.file?(image_path)
|
||||||
image image_path, height: 1.0
|
|
||||||
end
|
|
||||||
|
|
||||||
stack(fill: true, height: 1.0, padding: 4, border_thickness_left: 1, border_color_left: lighten(Gosu::Color.new(game.color))) do
|
stack(fill: true, height: 1.0, padding: 4, border_thickness_left: 1, border_color_left: lighten(Gosu::Color.new(game.color))) do
|
||||||
tagline "<b>#{item.title}</b>", width: 1.0
|
tagline "<b>#{item.title}</b>", width: 1.0
|
||||||
@@ -494,14 +443,14 @@ class W3DHub
|
|||||||
lock = Cache.acquire_net_lock("game_events_#{game.id}")
|
lock = Cache.acquire_net_lock("game_events_#{game.id}")
|
||||||
return false unless lock
|
return false unless lock
|
||||||
|
|
||||||
events = Api.events(game.id)
|
Api.events(game.id) do |result|
|
||||||
Cache.release_net_lock("game_events_#{game.id}") unless events
|
next unless result.okay?
|
||||||
|
|
||||||
return false unless events
|
@game_events[game.id] = result.data
|
||||||
|
populate_game_events(game)
|
||||||
@game_events[game.id] = events
|
ensure
|
||||||
|
Cache.release_net_lock("game_events_#{game.id}")
|
||||||
"game_events_#{game.id}"
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def populate_game_events(game)
|
def populate_game_events(game)
|
||||||
|
|||||||
@@ -34,9 +34,8 @@ class W3DHub
|
|||||||
|
|
||||||
# Do network stuff
|
# Do network stuff
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(
|
Api.user_login(@username.value, @password.value) do |result|
|
||||||
lambda do
|
if result.okay?
|
||||||
account = Api.user_login(@username.value, @password.value)
|
|
||||||
applications = nil
|
applications = nil
|
||||||
|
|
||||||
if account
|
if account
|
||||||
@@ -45,14 +44,15 @@ class W3DHub
|
|||||||
Store.settings.save_settings
|
Store.settings.save_settings
|
||||||
|
|
||||||
if account
|
if 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) {}
|
||||||
applications = Api._applications
|
Api._applications do |r|
|
||||||
|
applications = r.result if r.okay?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
[account, applications]
|
[account, applications]
|
||||||
end,
|
else
|
||||||
lambda do |result|
|
|
||||||
account, applications = result
|
account, applications = result
|
||||||
|
|
||||||
if account
|
if account
|
||||||
@@ -70,7 +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
|
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
|
||||||
@@ -80,13 +80,10 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
if Store.account
|
if Store.account
|
||||||
BackgroundWorker.foreground_job(
|
Cache.fetch(uri: Store.account.avatar_uri, backend: :w3dhub) do |result|
|
||||||
-> { Cache.fetch(uri: Store.account.avatar_uri, async: false, backend: :w3dhub) },
|
populate_account_info
|
||||||
->(result) {
|
page(W3DHub::Pages::Games)
|
||||||
populate_account_info
|
end
|
||||||
page(W3DHub::Pages::Games)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -153,29 +150,26 @@ class W3DHub
|
|||||||
Store.settings.save_settings
|
Store.settings.save_settings
|
||||||
Store.account = nil
|
Store.account = nil
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(
|
Api._applications do |result|
|
||||||
-> { Api._applications },
|
if result.okay?
|
||||||
lambda do |applications|
|
Store.applications = result.data
|
||||||
if applications
|
page(W3DHub::Pages::Games) if @host.current_page.is_a?(W3DHub::Pages::Games)
|
||||||
Store.applications = applications
|
page(W3DHub::Pages::ServerBrowser) if @host.current_page.is_a?(W3DHub::Pages::ServerBrowser)
|
||||||
page(W3DHub::Pages::Games) if @host.current_page.is_a?(W3DHub::Pages::Games)
|
end
|
||||||
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: 1.0, height: 1.0) do
|
stack(width: 1.0, height: 1.0) do
|
||||||
tagline "<b>#{I18n.t(:"interface.not_logged_in")}</b>", text_wrap: :none
|
tagline "<b>#{I18n.t(:"interface.not_logged_in")}</b>", text_wrap: :none
|
||||||
|
|
||||||
flow(width: 1.0) do
|
flow(width: 1.0) do
|
||||||
link(I18n.t(:"interface.log_in"), text_size: 22, width: 0.5) { page(W3DHub::Pages::Login) }
|
link(I18n.t(:"interface.log_in"), text_size: 22, width: 0.5) { page(W3DHub::Pages::Login) }
|
||||||
link I18n.t(:"interface.register"), text_size: 22, width: 0.49 do
|
link I18n.t(:"interface.register"), text_size: 22, width: 0.49 do
|
||||||
W3DHub.url("https://secure.w3dhub.com/forum/index.php?app=core&module=global§ion=register")
|
W3DHub.url("https://secure.w3dhub.com/forum/index.php?app=core&module=global§ion=register")
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -145,15 +145,7 @@ class W3DHub
|
|||||||
reorder_server_list
|
reorder_server_list
|
||||||
|
|
||||||
if @selected_server&.id == @refresh_server&.id
|
if @selected_server&.id == @refresh_server&.id
|
||||||
if @refresh_server
|
fetch_server_details(@refresh_server) 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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -353,10 +345,7 @@ class W3DHub
|
|||||||
|
|
||||||
reorder_server_list if @selected_server_container
|
reorder_server_list if @selected_server_container
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(
|
fetch_server_details(server)
|
||||||
-> { fetch_server_details(server) },
|
|
||||||
->(result) { populate_server_info(server) if server == @selected_server }
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
stylize_selected_server(server_container) if server.id == @selected_server&.id
|
stylize_selected_server(server_container) if server.id == @selected_server&.id
|
||||||
@@ -523,10 +512,13 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fetch_server_details(server)
|
def fetch_server_details(server)
|
||||||
BackgroundWorker.foreground_job(
|
Api.server_details(server.id, 2) do |result|
|
||||||
-> { Api.server_details(server.id, 2) },
|
if result.okay?
|
||||||
->(server_data) { server.update(server_data) if server_data }
|
server.update(result.data)
|
||||||
)
|
populate_server_info(server) if server == @selected_server
|
||||||
|
@refresh_server = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def game_icon(server)
|
def game_icon(server)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class W3DHub
|
|||||||
|
|
||||||
subtask.async do
|
subtask.async do
|
||||||
container.pinger.ping
|
container.pinger.ping
|
||||||
pp [container.pinger.address, container.pinger.average_ping]
|
# pp [container.pinger.address, container.pinger.average_ping]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -115,9 +115,8 @@ class W3DHub
|
|||||||
Api.on_thread(:refresh_user_login, account.refresh_token) do |refreshed_account|
|
Api.on_thread(:refresh_user_login, account.refresh_token) do |refreshed_account|
|
||||||
update_account_data(refreshed_account)
|
update_account_data(refreshed_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
BackgroundWorker.foreground_job(-> { update_account_data(account) }, ->(_) {})
|
Store.main_thread_queue << -> { update_account_data(account) }
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
@@ -131,7 +130,7 @@ class W3DHub
|
|||||||
|
|
||||||
Store.settings[:account][:data] = account
|
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
|
else
|
||||||
Store.settings[:account] = {}
|
Store.settings[:account] = {}
|
||||||
end
|
end
|
||||||
@@ -175,8 +174,10 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def service_status
|
def service_status
|
||||||
Api.on_thread(:service_status) do |service_status|
|
@status_label.value = "Checking service status..." #I18n.t(:"server_browser.fetching_server_list")
|
||||||
@service_status = service_status
|
|
||||||
|
Api.on_thread(:service_status) do |result|
|
||||||
|
@service_status = result.okay? ? result.data : nil
|
||||||
|
|
||||||
if @service_status
|
if @service_status
|
||||||
Store.service_status = @service_status
|
Store.service_status = @service_status
|
||||||
@@ -187,9 +188,7 @@ class W3DHub
|
|||||||
|
|
||||||
@tasks[:service_status][:complete] = true
|
@tasks[:service_status][:complete] = true
|
||||||
else
|
else
|
||||||
BackgroundWorker.foreground_job(-> {}, lambda { |_|
|
Store.main_thread_queue << -> { @status_label.value = I18n.t(:"boot.w3dhub_service_is_down") }
|
||||||
@status_label.value = I18n.t(:"boot.w3dhub_service_is_down")
|
|
||||||
})
|
|
||||||
@tasks[:service_status][:complete] = true
|
@tasks[:service_status][:complete] = true
|
||||||
|
|
||||||
@offline_mode = true
|
@offline_mode = true
|
||||||
@@ -201,9 +200,9 @@ class W3DHub
|
|||||||
def launcher_updater
|
def launcher_updater
|
||||||
@status_label.value = "Checking for Launcher updates..." # I18n.t(:"boot.checking_for_updates")
|
@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|
|
Api.on_thread(:fetch, "https://api.github.com/repos/cyberarm/w3d_hub_linux_launcher/releases/latest") do |result|
|
||||||
if response.status == 200
|
if result.okay?
|
||||||
hash = JSON.parse(response.body, symbolize_names: true)
|
hash = JSON.parse(result.data, symbolize_names: true)
|
||||||
available_version = hash[:tag_name].downcase.sub("v", "")
|
available_version = hash[:tag_name].downcase.sub("v", "")
|
||||||
|
|
||||||
pp Gem::Version.new(available_version) > Gem::Version.new(W3DHub::VERSION)
|
pp Gem::Version.new(available_version) > Gem::Version.new(W3DHub::VERSION)
|
||||||
@@ -227,14 +226,13 @@ class W3DHub
|
|||||||
def applications
|
def applications
|
||||||
@status_label.value = I18n.t(:"boot.checking_for_updates")
|
@status_label.value = I18n.t(:"boot.checking_for_updates")
|
||||||
|
|
||||||
Api.on_thread(:_applications) do |applications|
|
Api.on_thread(:_applications) do |result|
|
||||||
if applications
|
if result.okay?
|
||||||
Store.applications = applications
|
Store.applications = result.data
|
||||||
Store.settings.save_application_cache(applications.data.to_json)
|
Store.settings.save_application_cache(Store.applications.data.to_json)
|
||||||
@tasks[:applications][:complete] = true
|
@tasks[:applications][:complete] = true
|
||||||
else
|
else
|
||||||
# FIXME: Failed to retreive!
|
@status_label.value = "FAILED TO RETRIEVE APPS LIST"
|
||||||
BackgroundWorker.foreground_job(-> {}, ->(_) { @status_label.value = "FAILED TO RETREIVE APPS LIST" })
|
|
||||||
|
|
||||||
@offline_mode = true
|
@offline_mode = true
|
||||||
Store.offline_mode = true
|
Store.offline_mode = true
|
||||||
@@ -253,40 +251,39 @@ 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
|
||||||
|
|
||||||
Api.on_thread(:package_details, packages, :alt_w3dhub) do |package_details|
|
Api.on_thread(:package_details, packages, :alt_w3dhub) do |result|
|
||||||
package_details ||= nil
|
if result.okay?
|
||||||
|
result.data.each do |package|
|
||||||
|
next if package.error?
|
||||||
|
|
||||||
package_details&.each do |package|
|
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
||||||
next if package.error?
|
generated_icon_path = "#{CACHE_PATH}/#{package.subcategory}.png"
|
||||||
|
|
||||||
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
|
regenerate = false
|
||||||
generated_icon_path = "#{CACHE_PATH}/#{package.subcategory}.png"
|
|
||||||
|
|
||||||
regenerate = false
|
if File.exist?(path)
|
||||||
|
broken_or_out_dated_icon = Digest::SHA256.new.hexdigest(File.binread(path)).upcase != package.checksum.upcase
|
||||||
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
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
next unless regenerate
|
|
||||||
|
|
||||||
BackgroundWorker.foreground_job(-> { ICO.new(file: path) }, lambda { |result|
|
|
||||||
result.save(result.images.max_by(&:width), generated_icon_path)
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@tasks[:app_icons][:complete] = true unless failure
|
@tasks[:app_icons][:complete] = true unless failure
|
||||||
@@ -304,18 +301,18 @@ class W3DHub
|
|||||||
packages << { category: app.category, subcategory: app.id, name: "background.png", version: "" }
|
packages << { category: app.category, subcategory: app.id, name: "background.png", version: "" }
|
||||||
end
|
end
|
||||||
|
|
||||||
Api.on_thread(:package_details, packages, :alt_w3dhub) do |package_details|
|
Api.on_thread(:package_details, packages, :alt_w3dhub) do |result|
|
||||||
package_details ||= nil
|
if result.okay?
|
||||||
|
result.data.each do |package|
|
||||||
|
next if package.error?
|
||||||
|
|
||||||
package_details&.each do |package|
|
package_cache_path = Cache.package_path(package.category, package.subcategory, package.name,
|
||||||
next if package.error?
|
package.version)
|
||||||
|
|
||||||
package_cache_path = Cache.package_path(package.category, package.subcategory, package.name,
|
missing_or_broken_image = File.exist?(package_cache_path) ? Digest::SHA256.new.hexdigest(File.binread(package_cache_path)).upcase != package.checksum.upcase : true
|
||||||
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
|
Cache.fetch_package(package, proc {}) if missing_or_broken_image
|
||||||
|
end
|
||||||
Cache.fetch_package(package, proc {}) if missing_or_broken_image
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@tasks[:app_logos_and_backgrounds][:complete] = true
|
@tasks[:app_logos_and_backgrounds][:complete] = true
|
||||||
@@ -325,15 +322,15 @@ class W3DHub
|
|||||||
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")
|
||||||
|
|
||||||
Api.on_thread(:server_list, 2) do |list|
|
Api.on_thread(:server_list, 2) do |result|
|
||||||
if list
|
if result.okay?
|
||||||
Store.server_list = list.sort_by! { |s| s&.status&.players&.size }.reverse
|
Store.server_list = result.data.sort_by! { |s| s&.status&.players&.size }.reverse
|
||||||
|
|
||||||
Store.server_list_last_fetch = Gosu.milliseconds
|
Store.server_list_last_fetch = Gosu.milliseconds
|
||||||
|
|
||||||
Api::ServerListUpdater.instance
|
Api::ServerListUpdater.instance
|
||||||
|
|
||||||
list.each do |server|
|
Store.server_list.each do |server|
|
||||||
server.send_ping(true)
|
server.send_ping(true)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -164,15 +164,15 @@ class W3DHub
|
|||||||
if Gosu.milliseconds >= @server_list_expire
|
if Gosu.milliseconds >= @server_list_expire
|
||||||
@server_list_expire = Gosu.milliseconds + 30_000
|
@server_list_expire = Gosu.milliseconds + 30_000
|
||||||
|
|
||||||
Api.on_thread(:server_list, 2) do |list|
|
Api.on_thread(:server_list, 2) do |result|
|
||||||
if list
|
if result.okay?
|
||||||
@server_list_expire = Gosu.milliseconds + SERVER_LIST_UPDATE_INTERVAL # five minutes
|
@server_list_expire = Gosu.milliseconds + SERVER_LIST_UPDATE_INTERVAL # five minutes
|
||||||
|
|
||||||
Store.server_list_last_fetch = Gosu.milliseconds
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
class W3DHub
|
class W3DHub
|
||||||
DIR_NAME = "W3DHubAlt".freeze
|
DIR_NAME = "W3DHubAlt".freeze
|
||||||
VERSION = "0.9.0".freeze
|
VERSION = "0.9.1".freeze
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ class W3DHub
|
|||||||
|
|
||||||
Store[:server_list] = []
|
Store[:server_list] = []
|
||||||
Store[:settings] = Settings.new
|
Store[:settings] = Settings.new
|
||||||
|
Store[:network_manager] = NetworkManager.new
|
||||||
Store[:application_manager] = ApplicationManager.new
|
Store[:application_manager] = ApplicationManager.new
|
||||||
Store[:ping_manager] = PingManager.new
|
Store[:ping_manager] = PingManager.new
|
||||||
|
|
||||||
BackgroundWorker.parallel_job(-> { Async { |task| Store.ping_manager.monitor(task) } }, nil)
|
# FIXME
|
||||||
|
# BackgroundWorker.parallel_job(-> { Async { |task| Store.ping_manager.monitor(task) } }, nil)
|
||||||
|
|
||||||
Store[:main_thread_queue] = []
|
Store[:main_thread_queue] = []
|
||||||
|
|
||||||
@@ -35,8 +37,7 @@ class W3DHub
|
|||||||
block&.call
|
block&.call
|
||||||
end
|
end
|
||||||
|
|
||||||
# Manually sleep main thread so that the BackgroundWorker thread can be scheduled
|
sleep(update_interval / 1000.0) if Store.application_manager.busy? || Store.network_manager.busy?
|
||||||
sleep(update_interval / 1000.0) if W3DHub::BackgroundWorker.busy? || Store.application_manager.busy?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def needs_redraw?
|
def needs_redraw?
|
||||||
|
|||||||
@@ -228,27 +228,34 @@ class W3DHub
|
|||||||
@encrypted
|
@encrypted
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_file(path:)
|
def add_file(path:, replace: false)
|
||||||
return false unless File.exist?(path)
|
return false unless File.exist?(path)
|
||||||
return false if File.directory?(path)
|
return false if File.directory?(path)
|
||||||
|
|
||||||
info = EntryInfoHeader.new(0, 0, File.size(path))
|
entry = Entry.new(name: File.basename(path), path: path, info: EntryInfoHeader.new(0, 0, File.size(path)))
|
||||||
@entries << Entry.new(name: File.basename(path), path: path, info: info)
|
add_entry(entry: entry, replace: replace)
|
||||||
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_blob(path:, blob:)
|
def add_blob(path:, blob:, replace: false)
|
||||||
info = EntryInfoHeader.new(0, 0, blob.size)
|
info = EntryInfoHeader.new(0, 0, blob.size)
|
||||||
@entries << Entry.new(name: File.basename(path), path: path, info: info, blob: blob)
|
entry = Entry.new(name: File.basename(path), path: path, info: info, blob: blob)
|
||||||
into.crc32 = @entries.last.calculate_crc32
|
into.crc32 = @entries.last.calculate_crc32
|
||||||
|
|
||||||
true
|
add_entry(entry: entry, replace: replace)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_entry(entry:)
|
def add_entry(entry:, replace: false)
|
||||||
@entries << entry
|
duplicate = @entries.find { |e| e.name.upcase == entry.name.upcase }
|
||||||
|
|
||||||
|
if duplicate
|
||||||
|
if replace
|
||||||
|
@entries.delete(duplicate)
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@entries << entry
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ require_relative "lib/broadcast_server"
|
|||||||
require_relative "lib/hardware_survey"
|
require_relative "lib/hardware_survey"
|
||||||
require_relative "lib/game_settings"
|
require_relative "lib/game_settings"
|
||||||
require_relative "lib/websocket_client"
|
require_relative "lib/websocket_client"
|
||||||
require_relative "lib/background_worker"
|
require_relative "lib/network_manager"
|
||||||
|
require_relative "lib/network_manager/http_client"
|
||||||
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"
|
||||||
@@ -178,27 +179,18 @@ end
|
|||||||
|
|
||||||
logger.info(W3DHub::LOG_TAG) { "W3D Hub Linux Launcher v#{W3DHub::VERSION}" }
|
logger.info(W3DHub::LOG_TAG) { "W3D Hub Linux Launcher v#{W3DHub::VERSION}" }
|
||||||
|
|
||||||
Thread.new do
|
|
||||||
W3DHub::BackgroundWorker.create
|
|
||||||
end
|
|
||||||
|
|
||||||
until W3DHub::BackgroundWorker.alive?
|
|
||||||
sleep 0.1
|
|
||||||
end
|
|
||||||
|
|
||||||
logger.info(W3DHub::LOG_TAG) { "Launching window..." }
|
logger.info(W3DHub::LOG_TAG) { "Launching window..." }
|
||||||
# W3DHub::Window.new(width: 980, height: 720, borderless: false, resizable: true).show unless defined?(Ocra)
|
# W3DHub::Window.new(width: 980, height: 720, borderless: false, resizable: true).show unless defined?(Ocra)
|
||||||
W3DHub::Window.new(width: 1280, height: 800, borderless: false, resizable: true).show unless defined?(Ocra)
|
W3DHub::Window.new(width: 1280, height: 800, borderless: false, resizable: true).show unless defined?(Ocra)
|
||||||
# W3DHub::Window.new(width: 1920, height: 1080, borderless: false, resizable: true).show unless defined?(Ocra)
|
# W3DHub::Window.new(width: 1920, height: 1080, borderless: false, resizable: true).show unless defined?(Ocra)
|
||||||
W3DHub::BackgroundWorker.shutdown!
|
|
||||||
|
|
||||||
worker_soft_halt = Gosu.milliseconds
|
# worker_soft_halt = Gosu.milliseconds
|
||||||
|
|
||||||
# Wait for BackgroundWorker to return
|
# # Wait for BackgroundWorker to return
|
||||||
while W3DHub::BackgroundWorker.alive?
|
# while W3DHub::BackgroundWorker.alive?
|
||||||
W3DHub::BackgroundWorker.kill! if Gosu.milliseconds - worker_soft_halt >= 1_000
|
# W3DHub::BackgroundWorker.kill! if Gosu.milliseconds - worker_soft_halt >= 1_000
|
||||||
|
|
||||||
sleep 0.1
|
# sleep 0.1
|
||||||
end
|
# end
|
||||||
|
|
||||||
W3DHub::LOGGER&.close
|
W3DHub::LOGGER&.close
|
||||||
|
|||||||
Reference in New Issue
Block a user