diff --git a/apps/api/app/services/concerns/software_updater/archive_extraction.rb b/apps/api/app/services/concerns/software_updater/archive_extraction.rb new file mode 100644 index 0000000..1d2d874 --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/archive_extraction.rb @@ -0,0 +1,45 @@ +require "zip" +require "fileutils" + +module SoftwareUpdater + module ArchiveExtraction + private + + def base_path + @base_path ||= ENV.fetch("FILE_CONTAINER_PATH", "/softwares") + end + + def full_path(filename) + File.join(base_path, filename) + end + + def dir_exists?(dirname) + File.directory?(full_path(dirname)) + end + + def delete_dir(dirname) + FileUtils.rm_rf(full_path(dirname)) + end + + def create_dir(dirname) + FileUtils.mkdir_p(full_path(dirname)) + end + + def unzip_file(zip_filename, dest_dirname) + dest = full_path(dest_dirname) + Zip::File.open(full_path(zip_filename)) do |zip| + zip.each do |entry| + entry_dest = File.join(dest, entry.name) + FileUtils.mkdir_p(File.dirname(entry_dest)) + entry.extract(entry_dest) { true } + end + end + end + + def extract_zip_to_dir(zip_file, dir_name) + delete_dir(dir_name) if dir_exists?(dir_name) + create_dir(dir_name) + unzip_file(zip_file, dir_name) + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/metadata_parsing.rb b/apps/api/app/services/concerns/software_updater/metadata_parsing.rb new file mode 100644 index 0000000..a8cd0da --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/metadata_parsing.rb @@ -0,0 +1,14 @@ +require "json" + +module SoftwareUpdater + module MetadataParsing + METADATA_KEYS = %i[name title author desc site repo license].freeze + + private + + def parse_json_metadata(path) + raw = JSON.parse(File.read(path), symbolize_names: true) + raw.slice(*METADATA_KEYS) + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/software_persistence.rb b/apps/api/app/services/concerns/software_updater/software_persistence.rb new file mode 100644 index 0000000..2f3e06d --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/software_persistence.rb @@ -0,0 +1,27 @@ +module SoftwareUpdater + module SoftwarePersistence + private + + def update_or_create_software(attrs) + software = Software.unscoped.find_or_initialize_by(name: attrs[:name]) + software.assign_attributes(attrs.except(:name)) + software.deleted_at = nil + software.save! + software + end + + def upsert_external_link(software_id, label, url) + link = ExternalLink.unscoped.find_or_initialize_by(software_id: software_id, label: label) + link.url = url + link.deleted_at = nil + link.save! + end + + def create_release_if_not_exists(attrs) + existing = Release.unscoped.find_by(software_id: attrs[:software_id], version: attrs[:version]) + return existing if existing + + Release.create!(attrs) + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/updatable.rb b/apps/api/app/services/concerns/software_updater/updatable.rb new file mode 100644 index 0000000..a1fcdd2 --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/updatable.rb @@ -0,0 +1,48 @@ +module SoftwareUpdater + module Updatable + extend ActiveSupport::Concern + include ArchiveExtraction + include MetadataParsing + include SoftwarePersistence + + class_methods do + def platform(value = nil) + @platform = value if value + @platform ||= name.demodulize.delete_suffix("Service").downcase + end + end + + def update(name, version) + versioned = "#{name}-#{version}" + prepare_files(versioned) + + metadata = parse_metadata(versioned) + site_url = metadata.delete(:site) + repo_url = metadata.delete(:repo) + + ActiveRecord::Base.transaction do + software = update_or_create_software(metadata.merge(platform: self.class.platform)) + upsert_external_link(software.id, "Source Code", site_url) if site_url.present? + upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? + + create_release_if_not_exists( + { software_id: software.id, version: version }.merge(release_paths(versioned)) + ) + end + end + + private + + def prepare_files(versioned) + extract_zip_to_dir("#{versioned}.html.zip", versioned) + end + + def parse_metadata(versioned) + parse_json_metadata(full_path("#{versioned}.metadata.json")) + end + + def release_paths(versioned) + { html_folder_path: full_path(versioned) } + end + end +end diff --git a/apps/api/app/services/software_updater/base_service.rb b/apps/api/app/services/software_updater/base_service.rb deleted file mode 100644 index 05da284..0000000 --- a/apps/api/app/services/software_updater/base_service.rb +++ /dev/null @@ -1,73 +0,0 @@ -require "zip" -require "json" -require "fileutils" - -module SoftwareUpdater - class BaseService - def initialize - @base_path = ENV.fetch("FILE_CONTAINER_PATH", "/softwares") - end - - private - - def full_path(filename) - File.join(@base_path, filename) - end - - def dir_exists?(dirname) - File.directory?(full_path(dirname)) - end - - def delete_dir(dirname) - FileUtils.rm_rf(full_path(dirname)) - end - - def create_dir(dirname) - FileUtils.mkdir_p(full_path(dirname)) - end - - def unzip_file(zip_filename, dest_dirname) - dest = full_path(dest_dirname) - Zip::File.open(full_path(zip_filename)) do |zip| - zip.each do |entry| - entry_dest = File.join(dest, entry.name) - FileUtils.mkdir_p(File.dirname(entry_dest)) - entry.extract(entry_dest) { true } - end - end - end - - def extract_zip_to_dir(zip_file, dir_name) - delete_dir(dir_name) if dir_exists?(dir_name) - create_dir(dir_name) - unzip_file(zip_file, dir_name) - end - - def parse_json_metadata(path) - raw = JSON.parse(File.read(path), symbolize_names: true) - raw.slice(:name, :title, :author, :desc, :site, :repo, :license) - end - - def update_or_create_software(attrs) - software = Software.unscoped.find_or_initialize_by(name: attrs[:name]) - software.assign_attributes(attrs.except(:name)) - software.deleted_at = nil - software.save! - software - end - - def upsert_external_link(software_id, label, url) - link = ExternalLink.unscoped.find_or_initialize_by(software_id: software_id, label: label) - link.url = url - link.deleted_at = nil - link.save! - end - - def create_release_if_not_exists(attrs) - existing = Release.unscoped.find_by(software_id: attrs[:software_id], version: attrs[:version]) - return existing if existing - - Release.create!(attrs) - end - end -end diff --git a/apps/api/app/services/software_updater/bevy_service.rb b/apps/api/app/services/software_updater/bevy_service.rb index dab92d0..f4b754d 100644 --- a/apps/api/app/services/software_updater/bevy_service.rb +++ b/apps/api/app/services/software_updater/bevy_service.rb @@ -1,24 +1,5 @@ module SoftwareUpdater - class BevyService < BaseService - def update(name, version) - versioned = "#{name}-#{version}" - extract_zip_to_dir("#{versioned}.html.zip", versioned) - - metadata = parse_json_metadata(full_path("#{versioned}.metadata.json")) - site_url = metadata.delete(:site) - repo_url = metadata.delete(:repo) - - ActiveRecord::Base.transaction do - software = update_or_create_software(metadata.merge(platform: "bevy")) - upsert_external_link(software.id, "Source Code", site_url) if site_url.present? - upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? - - create_release_if_not_exists( - software_id: software.id, - version: version, - html_folder_path: full_path(versioned) - ) - end - end + class BevyService + include Updatable end end diff --git a/apps/api/app/services/software_updater/c64_service.rb b/apps/api/app/services/software_updater/c64_service.rb index 43894c0..0ed192b 100644 --- a/apps/api/app/services/software_updater/c64_service.rb +++ b/apps/api/app/services/software_updater/c64_service.rb @@ -1,23 +1,15 @@ module SoftwareUpdater - class C64Service < BaseService - def update(name, version) - versioned = "#{name}-#{version}" + class C64Service + include Updatable - metadata = parse_json_metadata(full_path("#{versioned}.metadata.json")) - site_url = metadata.delete(:site) - repo_url = metadata.delete(:repo) + private - ActiveRecord::Base.transaction do - software = update_or_create_software(metadata.merge(platform: "c64")) - upsert_external_link(software.id, "Source Code", site_url) if site_url.present? - upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? + def prepare_files(_versioned) + # a c64 buildhez nem tartozik html zip, csak a .prg fájl + end - create_release_if_not_exists( - software_id: software.id, - version: version, - cartridge_path: full_path("#{versioned}.prg") - ) - end + def release_paths(versioned) + { cartridge_path: full_path("#{versioned}.prg") } end end end diff --git a/apps/api/app/services/software_updater/ebitengine_service.rb b/apps/api/app/services/software_updater/ebitengine_service.rb index 168bb94..77bde66 100644 --- a/apps/api/app/services/software_updater/ebitengine_service.rb +++ b/apps/api/app/services/software_updater/ebitengine_service.rb @@ -1,24 +1,5 @@ module SoftwareUpdater - class EbitengineService < BaseService - def update(name, version) - versioned = "#{name}-#{version}" - extract_zip_to_dir("#{versioned}.html.zip", versioned) - - metadata = parse_json_metadata(full_path("#{versioned}.metadata.json")) - site_url = metadata.delete(:site) - repo_url = metadata.delete(:repo) - - ActiveRecord::Base.transaction do - software = update_or_create_software(metadata.merge(platform: "ebitengine")) - upsert_external_link(software.id, "Source Code", site_url) if site_url.present? - upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? - - create_release_if_not_exists( - software_id: software.id, - version: version, - html_folder_path: full_path(versioned) - ) - end - end + class EbitengineService + include Updatable end end diff --git a/apps/api/app/services/software_updater/godot_service.rb b/apps/api/app/services/software_updater/godot_service.rb index a4c83f7..b245044 100644 --- a/apps/api/app/services/software_updater/godot_service.rb +++ b/apps/api/app/services/software_updater/godot_service.rb @@ -1,24 +1,5 @@ module SoftwareUpdater - class GodotService < BaseService - def update(name, version) - versioned = "#{name}-#{version}" - extract_zip_to_dir("#{versioned}.html.zip", versioned) - - metadata = parse_json_metadata(full_path("#{versioned}.metadata.json")) - site_url = metadata.delete(:site) - repo_url = metadata.delete(:repo) - - ActiveRecord::Base.transaction do - software = update_or_create_software(metadata.merge(platform: "godot")) - upsert_external_link(software.id, "Source Code", site_url) if site_url.present? - upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? - - create_release_if_not_exists( - software_id: software.id, - version: version, - html_folder_path: full_path(versioned) - ) - end - end + class GodotService + include Updatable end end diff --git a/apps/api/app/services/software_updater/love_service.rb b/apps/api/app/services/software_updater/love_service.rb index 26e00ab..f58ce77 100644 --- a/apps/api/app/services/software_updater/love_service.rb +++ b/apps/api/app/services/software_updater/love_service.rb @@ -1,24 +1,5 @@ module SoftwareUpdater - class LoveService < BaseService - def update(name, version) - versioned = "#{name}-#{version}" - extract_zip_to_dir("#{versioned}.html.zip", versioned) - - metadata = parse_json_metadata(full_path("#{versioned}.metadata.json")) - site_url = metadata.delete(:site) - repo_url = metadata.delete(:repo) - - ActiveRecord::Base.transaction do - software = update_or_create_software(metadata.merge(platform: "love")) - upsert_external_link(software.id, "Source Code", site_url) if site_url.present? - upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? - - create_release_if_not_exists( - software_id: software.id, - version: version, - html_folder_path: full_path(versioned) - ) - end - end + class LoveService + include Updatable end end diff --git a/apps/api/app/services/software_updater/phaser_service.rb b/apps/api/app/services/software_updater/phaser_service.rb index 7d66192..1626724 100644 --- a/apps/api/app/services/software_updater/phaser_service.rb +++ b/apps/api/app/services/software_updater/phaser_service.rb @@ -1,24 +1,5 @@ module SoftwareUpdater - class PhaserService < BaseService - def update(name, version) - versioned = "#{name}-#{version}" - extract_zip_to_dir("#{versioned}.html.zip", versioned) - - metadata = parse_json_metadata(full_path("#{versioned}.metadata.json")) - site_url = metadata.delete(:site) - repo_url = metadata.delete(:repo) - - ActiveRecord::Base.transaction do - software = update_or_create_software(metadata.merge(platform: "phaser")) - upsert_external_link(software.id, "Source Code", site_url) if site_url.present? - upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? - - create_release_if_not_exists( - software_id: software.id, - version: version, - html_folder_path: full_path(versioned) - ) - end - end + class PhaserService + include Updatable end end diff --git a/apps/api/app/services/software_updater/tic80_service.rb b/apps/api/app/services/software_updater/tic80_service.rb index 7d90b61..935a24e 100644 --- a/apps/api/app/services/software_updater/tic80_service.rb +++ b/apps/api/app/services/software_updater/tic80_service.rb @@ -1,34 +1,27 @@ module SoftwareUpdater - class Tic80Service < BaseService - def update(name, version) - versioned = "#{name}-#{version}" - docs_dir = "#{versioned}-docs" - - extract_zip_to_dir("#{versioned}.html.zip", versioned) - extract_zip_to_dir("#{versioned}-docs.zip", docs_dir) - - metadata = parse_lua_metadata(full_path("#{versioned}.lua")) - site_url = metadata.delete(:site) - repo_url = metadata.delete(:repo) - - ActiveRecord::Base.transaction do - software = update_or_create_software(metadata.merge(platform: "tic80")) - upsert_external_link(software.id, "Source Code", site_url) if site_url.present? - upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? - - create_release_if_not_exists( - software_id: software.id, - version: version, - cartridge_path: full_path("#{versioned}.tic"), - source_path: full_path("#{versioned}.lua"), - html_folder_path: full_path(versioned), - docs_folder_path: full_path(docs_dir) - ) - end - end + class Tic80Service + include Updatable private + def prepare_files(versioned) + extract_zip_to_dir("#{versioned}.html.zip", versioned) + extract_zip_to_dir("#{versioned}-docs.zip", "#{versioned}-docs") + end + + def parse_metadata(versioned) + parse_lua_metadata(full_path("#{versioned}.lua")) + end + + def release_paths(versioned) + { + cartridge_path: full_path("#{versioned}.tic"), + source_path: full_path("#{versioned}.lua"), + html_folder_path: full_path(versioned), + docs_folder_path: full_path("#{versioned}-docs") + } + end + def parse_lua_metadata(source_path) metadata = {} File.foreach(source_path) do |line| @@ -39,7 +32,7 @@ module SoftwareUpdater value = parts[1].strip metadata[key] = value end - metadata.slice(:name, :title, :author, :desc, :site, :repo, :license) + metadata.slice(*MetadataParsing::METADATA_KEYS) end end end diff --git a/apps/api/app/services/update_service.rb b/apps/api/app/services/update_service.rb index a43b5f6..7814e3e 100644 --- a/apps/api/app/services/update_service.rb +++ b/apps/api/app/services/update_service.rb @@ -1,17 +1,11 @@ class UpdateService - PLATFORM_SERVICES = { - "tic80" => "SoftwareUpdater::Tic80Service", - "ebitengine" => "SoftwareUpdater::EbitengineService", - "love" => "SoftwareUpdater::LoveService", - "c64" => "SoftwareUpdater::C64Service", - "godot" => "SoftwareUpdater::GodotService", - "bevy" => "SoftwareUpdater::BevyService", - "phaser" => "SoftwareUpdater::PhaserService" - }.freeze + SUPPORTED_PLATFORMS = %w[tic80 ebitengine love c64 godot bevy phaser].freeze def update(input) - service_class = PLATFORM_SERVICES[input.platform] - raise ArgumentError, "Unsupported platform: #{input.platform}" unless service_class - service_class.constantize.new.update(input.name, input.version) + unless SUPPORTED_PLATFORMS.include?(input.platform) + raise ArgumentError, "Unsupported platform: #{input.platform}" + end + + "SoftwareUpdater::#{input.platform.camelize}Service".constantize.new.update(input.name, input.version) end end