mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2026-03-21 19:56:14 +00:00
WIP Ractor Task stuff
This commit is contained in:
@@ -555,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)
|
||||||
@@ -610,34 +610,49 @@ class W3DHub
|
|||||||
|
|
||||||
def handle_task_event(event)
|
def handle_task_event(event)
|
||||||
# ONLY CALL on MAIN Ractor
|
# ONLY CALL on MAIN Ractor
|
||||||
raise "Something has gone horribly wrong!" unless Ractor.current == Ractor.main
|
raise "Something has gone horribly wrong!" unless Ractor.main?
|
||||||
|
|
||||||
|
pp event
|
||||||
task = @tasks.find { |t| t.context.task_id == event.task_id }
|
task = @tasks.find { |t| t.context.task_id == event.task_id }
|
||||||
return unless task # FIXME: This is probably a fatal error
|
return unless task # FIXME: This is probably a fatal error
|
||||||
|
|
||||||
case event.type
|
case event.type
|
||||||
when Task::EVENT_FAILURE
|
when Task::EVENT_FAILURE
|
||||||
|
Store.main_thread_queue << proc do
|
||||||
window.push_state(
|
window.push_state(
|
||||||
W3DHub::States::MessageDialog,
|
W3DHub::States::MessageDialog,
|
||||||
type: event.data[:type],
|
type: event.data[:type],
|
||||||
title: event.data[:title],
|
title: event.data[:title],
|
||||||
message: event.data[:message]
|
message: event.data[:message]
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
# FIXME: Send event to Games page to trigger refresh
|
||||||
|
|
||||||
States::Interface.instance&.hide_application_taskbar
|
States::Interface.instance&.hide_application_taskbar
|
||||||
@tasks.delete(task)
|
@tasks.delete(task)
|
||||||
|
|
||||||
when Task::EVENT_START
|
when Task::EVENT_START
|
||||||
task.started! # mark ApplicationManager's version of Task as :running
|
|
||||||
States::Interface.instance&.show_application_taskbar
|
States::Interface.instance&.show_application_taskbar
|
||||||
|
|
||||||
when Task::EVENT_SUCCESS
|
when Task::EVENT_SUCCESS
|
||||||
States::Interface.instance&.hide_application_taskbar
|
States::Interface.instance&.hide_application_taskbar
|
||||||
@tasks.delete(task)
|
@tasks.delete(task)
|
||||||
when Task::EVENT_PROGRESS
|
# FIXME: Send event to Games page to trigger refresh
|
||||||
:FIXME
|
when Task::EVENT_STATUS
|
||||||
when Task::EVENT_PACKAGE_LIST
|
task.status = event.data
|
||||||
:FIXME
|
States::Interface.instance&.update_interface_task_status(task)
|
||||||
when Task::EVENT_PACKAGE_STATUS
|
|
||||||
:FIXME
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -661,7 +676,13 @@ 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
|
||||||
|
BackgroundWorker.ractor_task(task)
|
||||||
end
|
end
|
||||||
|
|
||||||
def task?(type, app_id, channel)
|
def task?(type, app_id, channel)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ class W3DHub
|
|||||||
class Task
|
class Task
|
||||||
LOG_TAG = "W3DHub::ApplicationManager::Task".freeze
|
LOG_TAG = "W3DHub::ApplicationManager::Task".freeze
|
||||||
|
|
||||||
|
class AbortTaskExecutionError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
# Task failed
|
# Task failed
|
||||||
EVENT_FAILURE = -1
|
EVENT_FAILURE = -1
|
||||||
# Task started, show application taskbar
|
# Task started, show application taskbar
|
||||||
@@ -10,17 +13,15 @@ class W3DHub
|
|||||||
# Task completed successfully
|
# Task completed successfully
|
||||||
EVENT_SUCCESS = 1
|
EVENT_SUCCESS = 1
|
||||||
# Task progress
|
# Task progress
|
||||||
EVENT_PROGRESS = 2
|
EVENT_STATUS = 2
|
||||||
# List of packages this task will be working over
|
# Subtask progress
|
||||||
EVENT_PACKAGE_LIST = 3
|
EVENT_STATUS_OPERATION = 3
|
||||||
# Update a package's status: verifying, downloading, unpacking, patching
|
|
||||||
EVENT_PACKAGE_STATUS = 4
|
|
||||||
|
|
||||||
Context = Data.define(
|
Context = Data.define(
|
||||||
:task_id,
|
:task_id,
|
||||||
:app_type,
|
:app_type,
|
||||||
:app_id,
|
:application,
|
||||||
:channel_id,
|
:channel,
|
||||||
:version,
|
:version,
|
||||||
:target_path,
|
:target_path,
|
||||||
:temp_path
|
:temp_path
|
||||||
@@ -34,6 +35,7 @@ class W3DHub
|
|||||||
)
|
)
|
||||||
|
|
||||||
attr_reader :context, :state
|
attr_reader :context, :state
|
||||||
|
attr_accessor :status
|
||||||
|
|
||||||
def initialize(context:)
|
def initialize(context:)
|
||||||
@context = context
|
@context = context
|
||||||
@@ -43,6 +45,8 @@ class W3DHub
|
|||||||
# remember all case insensitive file paths
|
# remember all case insensitive file paths
|
||||||
@cache_file_paths = {}
|
@cache_file_paths = {}
|
||||||
|
|
||||||
|
@status = Status.new(application: context.application, channel: context.channel)
|
||||||
|
|
||||||
@failure = false
|
@failure = false
|
||||||
@failure_reason = ""
|
@failure_reason = ""
|
||||||
end
|
end
|
||||||
@@ -51,14 +55,13 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fail!(reason: "")
|
def fail!(reason: "")
|
||||||
|
@state = :failed
|
||||||
@failure = true
|
@failure = true
|
||||||
|
@failure_reason = reason
|
||||||
|
|
||||||
Ractor.current.send(
|
send_task_result
|
||||||
MessageEvent.new(
|
|
||||||
context.task_id,
|
|
||||||
|
|
||||||
)
|
raise AbortTaskExecutionError, reason
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def failed?
|
def failed?
|
||||||
@@ -72,12 +75,20 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def send_task_result
|
def send_task_result
|
||||||
Ractor.current.send(
|
internal_send_message_event(
|
||||||
|
failed? ? EVENT_FAILURE : EVENT_SUCCESS,
|
||||||
|
nil,
|
||||||
|
failed? ? { type: :failure, title: "Task Failed", message: @failure_reason } : nil
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def internal_send_message_event(type, subtype = nil, data = nil)
|
||||||
|
Ractor.yield(
|
||||||
MessageEvent.new(
|
MessageEvent.new(
|
||||||
context.task_id,
|
context.task_id,
|
||||||
@failure ? EVENT_FAILURE : EVENT_SUCCESS,
|
type,
|
||||||
nil,
|
subtype,
|
||||||
@failure_reason
|
data
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -87,9 +98,27 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
|
@state = :running
|
||||||
|
|
||||||
|
# only mark task as running then return unless we're NOT running on the
|
||||||
|
# main ractor. Task is deep copied when past to the ractor.
|
||||||
|
return if Ractor.main?
|
||||||
|
|
||||||
|
internal_send_message_event(EVENT_START)
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
execute_task
|
execute_task
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
@state = failed? ? :failed : :complete
|
||||||
|
|
||||||
send_task_result
|
send_task_result
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
rescue StandardError => e
|
||||||
|
fail!(reason: "Fatal Error\n#{e}") unless e.is_a?(AbortTaskExecutionError)
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns true on success and false on failure
|
# returns true on success and false on failure
|
||||||
@@ -102,14 +131,52 @@ class W3DHub
|
|||||||
|
|
||||||
# Quick checks before network and computational work starts
|
# Quick checks before network and computational work starts
|
||||||
def fail_fast!
|
def fail_fast!
|
||||||
# can read/write to destination
|
# is wine present?
|
||||||
|
if W3DHub.unix?
|
||||||
|
wine_present = W3DHub.command("which #{Store.settings[:wine_command]}")
|
||||||
|
|
||||||
fail!("FAIL FAST: Insufficient disk space available.") unless disk_space_available?
|
unless wine_present
|
||||||
|
fail!(reason: "FAIL FAST: `which #{Store.settings[:wine_command]}` command failed, wine is not installed.\n\n"\
|
||||||
|
"Will be unable to launch game.\n\n"\
|
||||||
|
"Check wine options in launcher's settings.")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_manifests
|
# can read/write to destination
|
||||||
result = CyberarmEngine::Result.new
|
# TODO
|
||||||
|
|
||||||
|
# have enough disk space
|
||||||
|
|
||||||
|
fail!(reason: "FAIL FAST: Insufficient disk space available.") unless disk_space_available?
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_manifests(version)
|
||||||
|
manifests = []
|
||||||
|
result = Result.new
|
||||||
|
|
||||||
|
while (package_result = fetch_package(category, subcategory, version, "manifest.xml"))
|
||||||
|
break unless package_result.okay?
|
||||||
|
|
||||||
|
path = package_file_path(category, subcategory, version, "manifest.xml")
|
||||||
|
unless File.exist?(path) && !File.directory?(path)
|
||||||
|
result.error = RuntimeError.new("File missing: #{path}")
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
manifest = LegacyManifest.new(path)
|
||||||
|
|
||||||
|
manifests << manifest
|
||||||
|
|
||||||
|
break unless manifest.patch?
|
||||||
|
|
||||||
|
version = manifest.base_version
|
||||||
|
end
|
||||||
|
|
||||||
|
# return in oldest to newest order
|
||||||
|
result.data = manifests.reverse
|
||||||
|
result
|
||||||
|
rescue StandardError => e # Async derives its errors from StandardError
|
||||||
|
result.error = e
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -180,8 +247,63 @@ class W3DHub
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# returns JSON hash on success, false or nil on failure
|
||||||
|
def fetch_package_details(packages)
|
||||||
|
endpoint = "/apis/launcher/1/get-package-details"
|
||||||
|
result = Result.new
|
||||||
|
|
||||||
|
hash = {
|
||||||
|
packages: packages.map do |h|
|
||||||
|
{ category: h[:category], subcategory: h[:subcategory], name: h[:name], version: h[:version] }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
body = URI.encode_www_form("data": JSON.dump(hash))
|
||||||
|
|
||||||
|
Sync do
|
||||||
|
Async::HTTP::Internet.post("#{UPSTREAM_ENDPOINT}#{endpoint}", CLIENT_FORM_ENCODED_HEADERS, body) do |response|
|
||||||
|
if response.success?
|
||||||
|
result.data = JSON.parse(response.read)
|
||||||
|
else
|
||||||
|
result.error = RuntimeError.new(response) # FIXME: have better error
|
||||||
|
end
|
||||||
|
rescue StandardError => e
|
||||||
|
result.error = e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_package(version, name)
|
def fetch_package(version, name)
|
||||||
result = CyberarmEngine::Result.new
|
endpoint = "/apis/launcher/1/get-package"
|
||||||
|
result = Result.new
|
||||||
|
|
||||||
|
path = package_file_path(category, subcategory, version, name)
|
||||||
|
headers = [
|
||||||
|
["user-agent", USER_AGENT],
|
||||||
|
["content-type", "application/x-www-form-urlencoded"],
|
||||||
|
["authorization", "Bearer #{FAKE_BEARER_TOKEN}"]
|
||||||
|
].freeze
|
||||||
|
body = URI.encode_www_form("data": JSON.dump({ category: category, subcategory: subcategory, name: name, version: version }))
|
||||||
|
|
||||||
|
Sync do
|
||||||
|
Async::HTTP::Internet.post("#{UPSTREAM_ENDPOINT}#{endpoint}", headers, body) do |response|
|
||||||
|
if response.success?
|
||||||
|
create_directories(path)
|
||||||
|
|
||||||
|
File.open(path, "wb") do |file|
|
||||||
|
response.each do |chunk|
|
||||||
|
file.write(chunk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result.data = true
|
||||||
|
end
|
||||||
|
rescue StandardError => e
|
||||||
|
result.error = e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
@@ -216,6 +338,14 @@ class W3DHub
|
|||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def package_file_path(category, subcategory, version, name)
|
||||||
|
"#{PACKAGE_CACHE}/"\
|
||||||
|
"#{category}/"\
|
||||||
|
"#{subcategory.to_s.empty? ? "" : "#{subcategory}/"}"\
|
||||||
|
"#{version.to_s.empty? ? "" : "#{version}/"}"\
|
||||||
|
"#{name}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -59,6 +59,26 @@ class W3DHub
|
|||||||
@@instance.add_parallel_job(Job.new(job: job, callback: callback, error_handler: error_handler, deliver_to_queue: true, data: data))
|
@@instance.add_parallel_job(Job.new(job: job, callback: callback, error_handler: error_handler, deliver_to_queue: true, data: data))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.ractor_task(task)
|
||||||
|
raise "Something has gone horribly wrong!!!" unless Ractor.main?
|
||||||
|
|
||||||
|
ractor = Ractor.new do
|
||||||
|
t = Ractor.receive
|
||||||
|
|
||||||
|
t.start
|
||||||
|
end
|
||||||
|
|
||||||
|
ractor.send(task)
|
||||||
|
|
||||||
|
Thread.new do
|
||||||
|
while (message_event = ractor.take)
|
||||||
|
break unless message_event.is_a?(W3DHub::ApplicationManager::Task::MessageEvent)
|
||||||
|
|
||||||
|
Store.application_manager.handle_task_event(message_event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@busy = false
|
@busy = false
|
||||||
@jobs = []
|
@jobs = []
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
Reference in New Issue
Block a user