diff --git a/Gemfile b/Gemfile index 3c21f18..792ff00 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,20 @@ source "https://gem.coop" +# "standard lib" gems +gem "base64" +gem "rexml" +gem "logger" + +# "game" library gem gem "cyberarm_engine" +gem "sdl2-bindings" + +# networking libs +gem "async" +gem "async-http" +gem "async-websocket" + +# misc. libs +gem "digest-crc" +gem "ircparser" +gem "rubyzip" diff --git a/Gemfile.lock b/Gemfile.lock index 4bd2415..aef10ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,20 +1,124 @@ GEM remote: https://gem.coop/ specs: + async (2.39.0) + console (~> 1.29) + fiber-annotation + io-event (~> 1.11) + metrics (~> 0.12) + traces (~> 0.18) + async-http (0.95.0) + async (>= 2.10.2) + async-pool (~> 0.11) + io-endpoint (~> 0.14) + io-stream (~> 0.6) + metrics (~> 0.12) + protocol-http (~> 0.62) + protocol-http1 (~> 0.39) + protocol-http2 (~> 0.26) + protocol-url (~> 0.2) + traces (~> 0.10) + async-pool (0.11.2) + async (>= 2.0) + async-websocket (0.30.0) + async-http (~> 0.76) + protocol-http (~> 0.34) + protocol-rack (~> 0.7) + protocol-websocket (~> 0.17) + base64 (0.3.0) + console (1.34.3) + fiber-annotation + fiber-local (~> 1.1) + json cyberarm_engine (0.25.1) gosu (~> 1.1) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + ffi (1.17.4-x86_64-linux-gnu) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.1) gosu (1.4.6) + io-endpoint (0.17.2) + io-event (1.15.1) + io-stream (0.11.1) + ircparser (1.0.0) + json (2.19.3) + logger (1.7.0) + metrics (0.15.0) + protocol-hpack (1.5.1) + protocol-http (0.62.0) + protocol-http1 (0.39.0) + protocol-http (~> 0.62) + protocol-http2 (0.26.0) + protocol-hpack (~> 1.4) + protocol-http (~> 0.62) + protocol-rack (0.22.1) + io-stream (>= 0.10) + protocol-http (~> 0.58) + rack (>= 1.0) + protocol-url (0.4.0) + protocol-websocket (0.20.2) + protocol-http (~> 0.2) + rack (3.2.6) + rake (13.4.1) + rexml (3.4.4) + rubyzip (3.2.2) + sdl2-bindings (0.2.3) + ffi (~> 1.15) + traces (0.18.2) PLATFORMS - ruby x86_64-linux DEPENDENCIES + async + async-http + async-websocket + base64 cyberarm_engine + digest-crc + ircparser + logger + rexml + rubyzip + sdl2-bindings CHECKSUMS + async (2.39.0) + async-http (0.95.0) + async-pool (0.11.2) + async-websocket (0.30.0) + base64 (0.3.0) + console (1.34.3) cyberarm_engine (0.25.1) + digest-crc (0.7.0) + ffi (1.17.4-x86_64-linux-gnu) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage (1.0.1) gosu (1.4.6) + io-endpoint (0.17.2) + io-event (1.15.1) + io-stream (0.11.1) + ircparser (1.0.0) + json (2.19.3) + logger (1.7.0) + metrics (0.15.0) + protocol-hpack (1.5.1) + protocol-http (0.62.0) + protocol-http1 (0.39.0) + protocol-http2 (0.26.0) + protocol-rack (0.22.1) + protocol-url (0.4.0) + protocol-websocket (0.20.2) + rack (3.2.6) + rake (13.4.1) + rexml (3.4.4) + rubyzip (3.2.2) + sdl2-bindings (0.2.3) + traces (0.18.2) BUNDLED WITH 4.0.8 diff --git a/lib/constants.rb b/lib/constants.rb index 1073d32..7438729 100644 --- a/lib/constants.rb +++ b/lib/constants.rb @@ -1,3 +1,5 @@ module W3DHubLauncher ROOT_PATH = Dir.pwd + + USER_AGENT = "#{NAME} v#{VERSION}".freeze end diff --git a/lib/worker.rb b/lib/worker.rb index 2f8c152..df585ca 100644 --- a/lib/worker.rb +++ b/lib/worker.rb @@ -1,7 +1,13 @@ module W3DHubLauncher class Worker + Response = Data.define(:status, :request_id, :data) + def initialize @threads = [] + @requests = [] + + # next available request_id to assign incoming requests + @request_id = 0 # listen for requests from frontend listener = Thread.new { listen } @@ -10,15 +16,28 @@ module W3DHubLauncher # connect to and monitor Backend web service @threads << Thread.new { backend_websocket } - Ractor.main.send({ message: "3 o'clock 'nd all's well!" }) + @w3dhub_api = W3DHubLauncher::W3DHubApi.new listener.join end def listen loop do - request = Ractor.receive - pp request + query = Ractor.receive + pp query + + case query.type + when Request::FETCH_URL + when Request::DOWNLOAD_URL + when Request::W3DHUB_API_CALL + Async do + result = @w3dhub_api.send(query.data[:call], *(query.data[:arguments] || [])) + response = Response.new(result.okay? ? Request::STATUS_COMPLETE : Request::STATUS_ERROR, query.request_id, result) + Ractor.main.send(response) + end + else + raise "UNKNOWN REQUEST" + end end end diff --git a/lib/worker/request.rb b/lib/worker/request.rb new file mode 100644 index 0000000..7d52ac5 --- /dev/null +++ b/lib/worker/request.rb @@ -0,0 +1,64 @@ +module W3DHubLauncher + class Worker + class Request + Query = Data.define(:type, :request_id, :data) + + FETCH_URL = 0 + DOWNLOAD_URL = 1 + W3DHUB_API_CALL = 10 + + STATUS_ERROR = -1 # request has failed + STATUS_PENDING = 0 # request has not yet started + STATUS_OK = 1 # request completed successfully + STATUS_COMPLETE = STATUS_OK + STATUS_IN_PROGRESS = 2 # request is in progress + STATUS_BUSY = STATUS_IN_PROGRESS + + # NOT "Thread"/Ractor safe + @request_id = 0 + @requests = [] + + # NOT "Thread"/Ractor safe. Only call from main ractor + # returns next available request id, and auto increments by 1 + def self.request_id + @request_id += 1 + end + + # NOT "Thread"/Ractor safe. + # returns an array of pending requests + def self.requests + @requests + end + + attr_reader :type, :data, :request_id + + def initialize(type, data, request_id: Request.request_id, &block) + @type = type.freeze + @data = data.freeze + @status = STATUS_PENDING + + @request_id = request_id + @callback = block # only called on error or success + + enqueue(@type, @request_id, @data) + end + + def enqueue(type, id, data) + Request.requests << self + W3DHubLauncher::WORKER.send(Query.new(type, id, data)) + end + + # event from Worker received + def handle_event(event, data) + pp [event, data] + + case event + when STATUS_COMPLETE + Request.requests.delete(self) + when STATUS_ERROR + Request.requests.delete(self) + end + end + end + end +end diff --git a/lib/worker/w3dhub_api.rb b/lib/worker/w3dhub_api.rb index e69de29..2d05908 100644 --- a/lib/worker/w3dhub_api.rb +++ b/lib/worker/w3dhub_api.rb @@ -0,0 +1,109 @@ +module W3DHubLauncher + class W3DHubApi + API_TIMEOUT = 30 # seconds + API_CONNECT_TIMEOUT = 10 # seconds + + PRIMARY_W3DHUB_API_ENDPOINT = "https://secure.w3dhub.com".freeze + ALTERNATIVE_W3DHUB_API_ENDPOINT = "https://backend.w3d.cyberarm.dev".freeze + + def initialize + @access_token = nil + end + + def headers(form_encoded: false) + end + + # return raw response to requester + def fetch(url, method: :get, body: nil, headers: headers()) + result = CyberarmEngine::Result.new + + Sync do |task| + task.with_timeout(API_TIMEOUT) do + Async::HTTP::Internet.send(method, url, headers, body) do |response| + result.data = response.read + rescue StandardError => e + result.error = e + end + rescue Async::TimeoutError + result.error = e + end + end + + result + end + + # write response to file, periodically reporting progress to requester + def download(url, path:, method: :get, body: nil, headers: headers(), &block) + result = CyberarmEngine::Result.new + + Sync do |task| + task.with_timeout(API_TIMEOUT) do + Async::HTTP::Internet.send(method, url, headers, body) do |response| + if response.success? + content_length = response.headers["content-length"] || 0 + + total_downloaded_bytes = 0 + File.open(path, "wb") do |file| + response.each do |chunk| + file.write(chunk) + downloaded_bytes = chunk.length + total_downloaded_bytes += downloaded_bytes + + block&.call(downloaded_bytes, total_downloaded_bytes, content_length) + end + end + + result.data = true + end + rescue StandardError => e + result.error = e + end + rescue Async::TimeoutError + result.error = e + end + end + + result + end + + def user_login() + result = CyberarmEngine::Result.new + end + + def refresh_user_login() + result = CyberarmEngine::Result.new + end + + def fetch_user_details() + result = CyberarmEngine::Result.new + end + + def fetch_applications + result = CyberarmEngine::Result.new + end + + def fetch_news() + result = CyberarmEngine::Result.new + end + + def fetch_events() + result = CyberarmEngine::Result.new + end + + def fetch_manifest() + result = CyberarmEngine::Result.new + end + + def fetch_manifests() + result = CyberarmEngine::Result.new + end + + def fetch_package_details() + result = CyberarmEngine::Result.new + end + + def fetch_package() + download() + end + end +end diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index 32861f5..c1f53b4 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -4,6 +4,17 @@ rescue LoadError require "cyberarm_engine" end +require "rexml" +require "base64" +require "logger" + +require "async" +require "async/http/internet/instance" +require "async/websocket" +require "digest/crc" +require "ircparser" +require "zip" + require_relative "lib/version" require_relative "lib/constants" require_relative "lib/attribution" @@ -23,6 +34,13 @@ require_relative "lib/window" require_relative "lib/worker" require_relative "lib/worker/api" +require_relative "lib/worker/request" +require_relative "lib/worker/w3dhub_api" +require_relative "lib/worker/task" +require_relative "lib/worker/tasks/install_application" +require_relative "lib/worker/tasks/uninstall_application" +require_relative "lib/worker/tasks/repair_application" +require_relative "lib/worker/tasks/update_application" module W3DHubLauncher WORKER = Ractor.new(name: "Parallel Worker") { W3DHubLauncher::Worker.new } @@ -37,8 +55,17 @@ end # NOTE: May need to mangle Window#update to do ruby-land sleep so thread gets time to process :( Thread.new do loop do - message = Ractor.receive - pp message + response = Ractor.receive + pp response + + request = W3DHubLauncher::Worker::Request.requests.find { |r| r.request_id == response.request_id } + request&.handle_event(response.status, response.data) + end +end + +10.times do + W3DHubLauncher::Worker::Request.new(W3DHubLauncher::Worker::Request::W3DHUB_API_CALL, { call: :fetch_applications }) do |result| + pp result end end