mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2026-03-21 19:56:14 +00:00
352 lines
8.3 KiB
Ruby
352 lines
8.3 KiB
Ruby
class W3DHub
|
|
class ApplicationManager
|
|
class Task
|
|
LOG_TAG = "W3DHub::ApplicationManager::Task".freeze
|
|
|
|
class AbortTaskExecutionError < StandardError
|
|
end
|
|
|
|
# Task failed
|
|
EVENT_FAILURE = -1
|
|
# Task started, show application taskbar
|
|
EVENT_START = 0
|
|
# Task completed successfully
|
|
EVENT_SUCCESS = 1
|
|
# Task progress
|
|
EVENT_STATUS = 2
|
|
# Subtask progress
|
|
EVENT_STATUS_OPERATION = 3
|
|
|
|
Context = Data.define(
|
|
:task_id,
|
|
:app_type,
|
|
:application,
|
|
:channel,
|
|
:version,
|
|
:target_path,
|
|
:temp_path
|
|
)
|
|
|
|
MessageEvent = Data.define(
|
|
:task_id,
|
|
:type,
|
|
:subtype,
|
|
:data # { message: "Complete", progress: 0.1 }
|
|
)
|
|
|
|
attr_reader :context, :state
|
|
attr_accessor :status
|
|
|
|
def initialize(context:)
|
|
@context = context
|
|
|
|
@state = :not_started
|
|
|
|
# remember all case insensitive file paths
|
|
@cache_file_paths = {}
|
|
|
|
@status = Status.new(application: context.application, channel: context.channel)
|
|
|
|
@failure = false
|
|
@failure_reason = ""
|
|
end
|
|
|
|
def setup
|
|
end
|
|
|
|
def fail!(reason: "")
|
|
@state = :failed
|
|
@failure = true
|
|
@failure_reason = reason
|
|
|
|
send_task_result
|
|
|
|
raise AbortTaskExecutionError, reason
|
|
end
|
|
|
|
def failed?
|
|
@failure
|
|
end
|
|
|
|
def send_status_update(type)
|
|
end
|
|
|
|
def send_package_list()
|
|
end
|
|
|
|
def send_task_result
|
|
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(
|
|
context.task_id,
|
|
type,
|
|
subtype,
|
|
data
|
|
)
|
|
)
|
|
end
|
|
|
|
def type
|
|
raise NotImplementedError
|
|
end
|
|
|
|
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
|
|
|
|
sleep 1
|
|
|
|
@state = failed? ? :failed : :complete
|
|
|
|
send_task_result
|
|
|
|
sleep 1
|
|
rescue StandardError => e
|
|
fail!(reason: "Fatal Error\n#{e}") unless e.is_a?(AbortTaskExecutionError)
|
|
end
|
|
|
|
# returns true on success and false on failure
|
|
def execute_task
|
|
end
|
|
|
|
###########################
|
|
## High level task steps ##
|
|
###########################
|
|
|
|
# Quick checks before network and computational work starts
|
|
def fail_fast!
|
|
# is wine present?
|
|
if W3DHub.unix?
|
|
wine_present = W3DHub.command("which #{Store.settings[:wine_command]}")
|
|
|
|
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
|
|
|
|
# can read/write to destination
|
|
# 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
|
|
end
|
|
|
|
def build_package_list
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def remove_deleted_files
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def verify_files
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def fetch_packages
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def verify_packages
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def unpack_packages
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def create_wine_prefix
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def install_dependencies
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def write_paths_ini
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def mark_application_installed
|
|
end
|
|
|
|
##########################
|
|
## Supporting functions ##
|
|
##########################
|
|
|
|
# pestimistically estimate required disk space to:
|
|
# download, unpack/patch, and install.
|
|
def disk_space_available?
|
|
true
|
|
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)
|
|
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
|
|
end
|
|
|
|
def verify_package(version, name)
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def unpack_package(version, name)
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
# Apply all patches for a particular MIX file at once
|
|
def apply_patches(package)
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def apply_patch(target_mix, patch_mix)
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
end
|
|
|
|
def unzip(package_path)
|
|
result = CyberarmEngine::Result.new
|
|
|
|
result
|
|
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
|