Application updating and repair is now possible, save news and applications listing to cache directory instead of current working directory, added support for application unlinking, misc. other changes

This commit is contained in:
2026-10-07 22:54:42 -05:00
parent bdf678342e
commit a7e1378f55
17 changed files with 285 additions and 64 deletions

View File

@@ -71,5 +71,9 @@ module W3DHubLauncher
found_server
end
def self.announce_application_changed(application, state)
CyberarmEngine::EventBus.publish("application_changed", { application: application, state: state })
end
end
end

View File

@@ -67,7 +67,7 @@ module W3DHubLauncher
Worker::Api.update_settings(MemCache[:settings])
# FIXME: broadcast app installed on CyberarmEngine::EventBus
ApplicationHelper.announce_application_changed(app, :installed)
end
end
end

View File

@@ -187,7 +187,7 @@ module W3DHubLauncher
Worker::Api.applications do |result|
if result.okay?
File.write("applications.json", result.data)
File.write("#{CACHE_PATH}/applications.json", result.data)
hash = JSON.parse(result.data)
applications = hash["applications"]&.map { |app| W3DHubLauncher::Worker::Api::Application.new(app) } || []

View File

@@ -35,11 +35,19 @@ module W3DHubLauncher
end
def focus
@parent.show_battleview_panel
# @parent.show_battleview_panel
CyberarmEngine::EventBus.subscribe("application_changed") do |hash|
if @current_app&.id == hash[:application].id && @current_channel&.id == hash[:application].channel
populate_game_info
end
end
end
def blur
@parent.hide_battleview_panel
CyberarmEngine::EventBus.unsubscribe("application_changed")
end
def populate_game_content_container
@@ -115,7 +123,7 @@ module W3DHubLauncher
unless MemCache[:"events_#{app_id}"]
Worker::Api.events(app_id) do |result|
if result.okay?
File.write("events_#{app_id}.json", result.data)
File.write("#{CACHE_PATH}/events_#{app_id}.json", result.data)
MemCache[:"events_#{app_id}"] = JSON.parse(result.data)&.map { |item| Worker::Api::ServerEvent.new(item) } || []
populate_game_event if app_id == @current_app.id
@@ -126,7 +134,7 @@ module W3DHubLauncher
unless MemCache[:"news_#{app_id}"]
Worker::Api.news(app_id) do |result|
if result.okay?
File.write("news_#{app_id}.json", result.data)
File.write("#{CACHE_PATH}/news_#{app_id}.json", result.data)
MemCache[:"news_#{app_id}"] = JSON.parse(result.data)["news"]&.map { |item| Worker::Api::NewsItem.new(item) } || []
populate_game_news if app_id == @current_app.id
@@ -186,15 +194,25 @@ module W3DHubLauncher
flow(width: 1.0, height: 60) do
if application
button "JOIN", tip: "Join most populated or lowest ping server", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_LEFT, **CTA_BUTTON_THEME do |btn|
server = ApplicationHelper.play_now_server(application)
if @current_channel.update_available?(application.version)
button "Update", tip: "Update application from v#{application.version} to v#{@current_channel.version}", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_LEFT, **UPDATE_BUTTON_THEME do |btn|
btn.enabled = false
if server
ApplicationHelper.join_server(application, server)
Worker::Api.update_application(@current_app.id, @current_channel.id) do |result, status|
handle_task_update(result, status)
end
end
else
puts "No server available."
button "JOIN", tip: "Join most populated or lowest ping server", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_LEFT, **CTA_BUTTON_THEME do |btn|
server = ApplicationHelper.play_now_server(application)
if server
ApplicationHelper.join_server(application, server)
else
puts "No server available."
end
end
end
end
button safe_get_image("#{ROOT_PATH}/media/icons/singleplayer.png"), tip: "Single player", image_height: 1.0, background_nine_slice: NINE_SLICE_SQUARE, **CTA_BUTTON_THEME do
ApplicationHelper.run(application)
end
@@ -206,26 +224,48 @@ module W3DHubLauncher
menu_item("Screenshots Folder", tip: "Open application installation folder:\n#{Dir.home}/Documents/W3D Hub/#{application.id}-#{application.channel}/Screenshots")
stack(width: 1.0, height: 2, background: 0xff_bbbbbb)
menu_item("Application Settings", tip: "Edit application launch options")
menu_item("Repair Installation", tip: "Attempt to fix and repair installation")
menu_item("Check for Updates", tip: "Manually check for application updates")
if @current_app.servicable?
menu_item("Repair Installation", tip: "Attempt to fix and repair installation") do
Worker::Api.repair_application(@current_app.id, @current_channel.id) do |result, status|
handle_task_update(result, status)
end
end
menu_item("Check for Updates", tip: "Manually check for application updates")
end
stack(width: 1.0, height: 2, background: 0xff_bbbbbb)
menu_item("Move Installation", tip: "Move application installation to a different directory or disk")
menu_item("Unlink Installation", tip: "The application will be removed from the launcher\nbut the application's files will not be touched")
menu_item("Uninstall", tip: "Uninstall application and delete files")
if @current_app.servicable?
menu_item("Move Installation", tip: "Move application installation to a different directory or disk") do
# FIXME: Show a prompt dialog for choosing new location
# Worker::Api.move_application(@current_app.id, @current_channel.id) do |result, status|
# handle_task_update(result, status)
# end
end
end
menu_item("Unlink Installation", tip: "The application will be removed from the launcher\nbut the application's files will not be touched") do
Worker::Api.unlink_application(@current_app.id, @current_channel.id) do |result, status|
if result.okay?
MemCache[:settings] = Worker::Api::Settings.new(JSON.parse(result.data))
populate_game_info
end
end
end
if @current_app.servicable?
menu_item("Uninstall", tip: "Uninstall application and delete files")
end
end.show
end
elsif @current_app.servicable?
# pp @current_app
button "Import", tip: "Import existing application installation", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_LEFT, **CTA_BUTTON_THEME do |btn|
dialog(Dialog::ImportApplication, application: @current_app, channel: @current_channel)
end
button "Download", tip: "Download and install application", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_RIGHT, **CTA_BUTTON_THEME do |btn|
button "Download", tip: "Download and install application", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_LEFT, **CTA_BUTTON_THEME do |btn|
btn.enabled = false
Worker::Api.install_application(@current_app.id, @current_channel.id) do |status, result|
handle_task_update(status, result)
Worker::Api.install_application(@current_app.id, @current_channel.id) do |result, status|
handle_task_update(result, status)
end
end
button "Import", tip: "Import existing application installation", fill: true, height: 1.0, background_nine_slice: NINE_SLICE_ROUNDED_RIGHT, **CTA_BUTTON_THEME do |btn|
dialog(Dialog::ImportApplication, application: @current_app, channel: @current_channel)
end
else
button "Import", tip: "Import existing application installation", fill: true, height: 1.0, **CTA_BUTTON_THEME do |btn|
dialog(Dialog::ImportApplication, application: @current_app, channel: @current_channel)
@@ -297,10 +337,12 @@ module W3DHubLauncher
when Worker::Request::STATUS_COMPLETE
status_bar.hide
MemCache[:settings].applications << Worker::Api::Settings::Application.new(data["application"])
# FIXME: Application might NOT be installed
application = MemCache[:settings].add_or_update_application(Worker::Api::Settings::Application.new(data["application"]))
Worker::Api.update_settings(MemCache[:settings])
populate_game_info
# FIXME: Application might NOT be installed
ApplicationHelper.announce_application_changed(application, :installed)
when Worker::Request::STATUS_IN_PROGRESS
status_bar.show

View File

@@ -93,7 +93,7 @@ module W3DHubLauncher
end
# battleview/friends container
@battleview_container = stack(width: 400, height: 1.0, margin_left: LARGE_PADDING) do
@battleview_container = stack(width: 400, height: 1.0, margin_left: LARGE_PADDING, visible: false) do
# friend management container
flow(width: 1.0, height: 60) do
flow(width: 1.0, v_align: :center) do
@@ -358,7 +358,7 @@ module W3DHubLauncher
def button_up(id)
super
@battleview_container.toggle if id == Gosu::KB_F8
# @battleview_container.toggle if id == Gosu::KB_F8
end
end
end

View File

@@ -39,6 +39,19 @@ module W3DHubLauncher
}
}
UPDATE_BUTTON_THEME = {
color: 0xff_ffffff,
background_nine_slice_color: 0xff_e66100,
hover: {
color: 0xff_ffffff,
background_nine_slice_color: 0xff_ff7800
},
active: {
color: 0xff_ffffff,
background_nine_slice_color: 0xff_c64600
}
}
THEME = {
TextBlock: {
text_static: true,

View File

@@ -49,24 +49,38 @@ module W3DHubLauncher
@w3dhub_api = W3DHubLauncher::W3DHubApi.new
Async do |task|
UNIXServer.open(IPC_PATH) do |server|
while(socket = server.accept)
@client = socket
task.async do
while true
@tasks.delete_if { |t| [Request::STATUS_ERROR, Request::STATUS_COMPLETE].include?(t.status) }
task.async do
while(data = socket.gets)
json = JSON.parse(data)
query = Request::Query.new(type: json["type"].to_sym, request_id: json["request_id"], data: json["data"])
if @tasks.first && @tasks.all? { |t| t.status == Request::STATUS_PENDING }
@tasks.first.start(self)
end
# pp [:server_incoming, data, query]
sleep 0.25
end
end
if respond_to?(query.type)
response = send(query.type, query)
# pp [:server_to_client, response]
payload = { status: response.status, request_id: response.request_id, data: response.result.data, error: response.result.error }.to_json
socket.write(payload)
socket.write(RESPONSE_SEPARATOR)
socket.flush
task.async do |subtask|
UNIXServer.open(IPC_PATH) do |server|
while(socket = server.accept)
@client = socket
task.async do
while(data = socket.gets)
json = JSON.parse(data)
query = Request::Query.new(type: json["type"].to_sym, request_id: json["request_id"], data: json["data"])
# pp [:server_incoming, data, query]
if respond_to?(query.type)
response = send(query.type, query)
# pp [:server_to_client, response]
payload = { status: response.status, request_id: response.request_id, data: response.result.data, error: response.result.error }.to_json
socket.write(payload)
socket.write(RESPONSE_SEPARATOR)
socket.flush
end
end
end
end
@@ -269,8 +283,8 @@ module W3DHubLauncher
total_downloaded_bytes: total_downloaded_bytes,
content_length: content_length
})
# FIXME: Send intermediate response to requester
# send(query, Response.new(Request::STATUS_IN_PROGRESS, query.request_id progress_result))
message_requester(query.request_id, Request::STATUS_IN_PROGRESS, progress_result.data, progress_result.error)
end
end
@@ -453,15 +467,44 @@ module W3DHubLauncher
installed_version: nil,
target_version: channel.version
)
@tasks.last.start(self)
Response.new(Request::STATUS_PENDING, query.request_id, CyberarmEngine::Result.new(data: true))
end
def task_update_application(query)
application = @w3dhub_api.applications.find { |app| app.id == query.data["app_id"] }
channel = application.channels.find { |chan| chan.id == query.data["channel_id"] }
settings_application = @settings.application_installed?(application.id, channel.id)
@tasks << Task::UpdateApplication.new(
request_id: query.request_id,
application: application,
channel: channel,
installed_version: settings_application.version,
target_version: channel.version
)
Response.new(Request::STATUS_PENDING, query.request_id, CyberarmEngine::Result.new(data: true))
end
def task_repair_application(query)
application = @w3dhub_api.applications.find { |app| app.id == query.data["app_id"] }
channel = application.channels.find { |chan| chan.id == query.data["channel_id"] }
settings_application = @settings.application_installed?(application.id, channel.id)
@tasks << Task::RepairApplication.new(
request_id: query.request_id,
application: application,
channel: channel,
installed_version: settings_application.version,
target_version: settings_application.version
)
pp @tasks.last
Response.new(Request::STATUS_PENDING, query.request_id, CyberarmEngine::Result.new(data: true))
end
def task_move_application(query)
@@ -469,5 +512,20 @@ module W3DHubLauncher
def task_uninstall_application(query)
end
def unlink_application(query)
result = CyberarmEngine::Result.new
application = @settings.application_installed?(query.data["app_id"], query.data["channel_id"])
if application
@settings.remove_application(application)
query = Request::Query.new(query.type, query.request_id, @settings.to_json)
return update_settings(query)
end
deliver_response(result, query)
end
end
end

View File

@@ -100,6 +100,11 @@ module W3DHubLauncher
def self.uninstall_application(app_id, channel_id, &block)
Worker::Request.new(:task_uninstall_application, { app_id: app_id, channel_id: channel_id }, &block)
end
# request unlinking of application
def self.unlink_application(app_id, channel_id, &block)
Worker::Request.new(:unlink_application, { app_id: app_id, channel_id: channel_id }, &block)
end
end
end
end

View File

@@ -80,6 +80,10 @@ module W3DHubLauncher
extended_data("serverchannel", @id)
end
def update_available?(current_version)
current_version < @version
end
def to_json(context = nil)
{
"id": @id,

View File

@@ -41,6 +41,22 @@ module W3DHubLauncher
applications.find { |app| app.id == app_id && app.channel == channel_id }
end
def add_or_update_application(application)
index = applications.index { |app| app.id == application.id && app.channel == application.channel }
if index
applications[index] = application
else
applications << application
end
application
end
def remove_application(application)
applications.delete(application)
end
# User explictly set options
class Preferences
SCHEMA = 0

View File

@@ -6,6 +6,7 @@ module W3DHubLauncher
IndexedFile = Data.define(:type, :path)
attr_reader :request_id, :application, :channel, :installed_version, :target_version
attr_reader :status
def initialize(request_id:, application:, channel:, installed_version:, target_version:)
@request_id = request_id
@@ -16,6 +17,8 @@ module W3DHubLauncher
pp self
@status = Worker::Request::STATUS_PENDING
# caching mechinism for file path normalization
@file_index = {}
# manifests!
@@ -36,19 +39,28 @@ module W3DHubLauncher
end
def start(worker)
return unless status == Worker::Request::STATUS_PENDING
@worker = worker
@installation_directory ||= application_target_installation_directory
update_status(label: "Starting...", fraction: 0.0)
execute
@status = Worker::Request::STATUS_BUSY
v = execute
@status = Worker::Request::STATUS_COMPLETE
v
end
def abort_task!(reason = "")
puts reason
message_requester(Worker::Request::STATUS_ERROR, data: nil, error: reason)
@status = Worker::Request::STATUS_ERROR
raise reason
end
@@ -120,6 +132,8 @@ module W3DHubLauncher
@required_manifest_files = @manifest_files.clone
cached_checksums = {}
# Process manifest game files in NEWEST to OLDEST order so that we don't erroneously flag
# valid files as invalid due to an OLDER version of the file being checked FIRST.
@manifest_files.reverse.each do |file|
@@ -133,13 +147,17 @@ module W3DHubLauncher
next unless File.exist?(file_path) # && !File.directory?(file_path)
puts "verifying #{file_path}..."
checksum = Digest::SHA256.file(file_path).hexdigest.upcase
checksum = cached_checksums[file_path] || Digest::SHA256.file(file_path).hexdigest.upcase
cached_checksums[file_path] ||= checksum
if checksum == file.checksum
@required_manifest_files.delete(file)
# remove irrelevant older versions of the file from consideration
@required_manifest_files.delete_if { |f| f.name.casecmp?(file.name) && f.version < file.version }
puts " verified:#{file.version}"
else
puts " failed verification:#{file.version}"
end
end
end
@@ -279,12 +297,14 @@ module W3DHubLauncher
timestamp: Time.now.to_i
)
@status = Worker::Request::STATUS_COMPLETE
message_requester(Worker::Request::STATUS_COMPLETE, data: { application: app })
puts "APPLICATION: #{@application.name} #{@application.id}:#{@channel.id}:#{@target_version} installed."
end
def mark_application_uninstalled
@status = Worker::Request::STATUS_COMPLETE
message_requester(Worker::Request::STATUS_COMPLETE, data: {})
end
@@ -295,7 +315,7 @@ module W3DHubLauncher
end
def update_status(label:, fraction:)
message_requester(data: { status: { title: "TASK #{@application.name} (#{@channel.name})", label: label, fraction: fraction } } )
message_requester(data: { status: { title: "#{@task_verb} #{@application.name} (#{@channel.name})", label: label, fraction: fraction } } )
end
def application_target_installation_directory

View File

@@ -2,6 +2,7 @@ module W3DHubLauncher
class Task
class InstallApplication < Task
def setup
@task_verb = "Installing".freeze
end
def execute

View File

@@ -0,0 +1,11 @@
module W3DHubLauncher
class Task
class RepairApplication < InstallApplication
def setup
super
@task_verb = "Repairing".freeze
end
end
end
end

View File

@@ -0,0 +1,11 @@
module W3DHubLauncher
class Task
class UpdateApplication < InstallApplication
def setup
super
@task_verb = "Updating".freeze
end
end
end
end