class W3DHub class Cache def self.path(uri) ext = File.basename(uri).split(".").last "#{CACHE_PATH}/#{Digest::SHA2.hexdigest(uri)}.#{ext}" end # Fetch a generic uri def self.fetch(internet, uri, force_fetch = false) path = path(uri) if !force_fetch && File.exist?(path) path else response = internet.get(uri, W3DHub::Api::DEFAULT_HEADERS) if response.success? response.save(path, "wb") return path end false end end def self.create_directories(path, is_directory = false) target_directory = is_directory ? path : File.dirname(path) FileUtils.mkdir_p(target_directory) unless Dir.exist?(target_directory) end def self.package_path(category, subcategory, name, version) package_cache_dir = Store.settings[:package_cache_dir] "#{package_cache_dir}/#{category}/#{subcategory}/#{version}/#{name}.package" end def self.install_path(application, channel) app_install_dir = Store.settings[:app_install_dir] "#{app_install_dir}/#{application.category}/#{application.id}/#{channel.id}" end # Download a W3D Hub package def self.fetch_package(internet, package, block) path = package_path(package.category, package.subcategory, package.name, package.version) start_from_bytes = package.custom_partially_valid_at_bytes puts " Start from bytes: #{start_from_bytes}" create_directories(path) offset = start_from_bytes parts = [] chunk_size = 4_000_000 workers = 4 file = File.open(path, offset.positive? ? "r+b" : "wb") amount_written = 0 while (offset < package.size) byte_range_start = offset byte_range_end = [offset + chunk_size, package.size].min parts << (byte_range_start...byte_range_end) offset += chunk_size end semaphore = Async::Semaphore.new(workers) barrier = Async::Barrier.new(parent: semaphore) while !parts.empty? barrier.async do part = parts.shift range_header = [["range", "bytes=#{part.min}-#{part.max}"]] body = "data=#{JSON.dump({ category: package.category, subcategory: package.subcategory, name: package.name, version: package.version })}" response = internet.post("#{Api::ENDPOINT}/apis/launcher/1/get-package", W3DHub::Api::FORM_ENCODED_HEADERS + range_header, body) if response.success? chunk = response.read written = file.pwrite(chunk, part.min) amount_written += written remaining_bytes = package.size - amount_written total_bytes = package.size block.call(chunk, remaining_bytes, total_bytes) # puts " Remaining: #{((remaining_bytes.to_f / total_bytes) * 100.0).round}% (#{W3DHub::format_size(total_bytes - remaining_bytes)} / #{W3DHub::format_size(total_bytes)})" end end barrier.wait end ensure file&.close end end end