## --- BEGIN LICENSE BLOCK --- # Copyright (c) 2016-present WeWantToKnow AS # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ## --- END LICENSE BLOCK --- require 'u3d/unity_version_definition' require 'u3d_core/helper' require 'net/http' module U3d # Takes care of fectching versions and version list module UnityVersions ##################################################### # @!group URLS: Locations to fetch information from ##################################################### # URL for the forum thread listing all the Linux releases UNITY_LINUX_DOWNLOADS = 'https://forum.unity.com/threads/unity-on-linux-release-notes-and-known-issues.350256/'.freeze # URL for the main releases for Windows and Macintosh UNITY_DOWNLOADS = 'https://unity3d.com/get-unity/download/archive'.freeze # URL for the patch releases for Windows and Macintosh UNITY_PATCHES = 'https://unity3d.com/unity/qa/patch-releases'.freeze # URL for the beta releases list, they need to be accessed after UNITY_BETAS = 'https://unity3d.com/unity/beta/archive'.freeze # URL for a specific beta, takes into parameter a version string (%s) UNITY_BETA_URL = 'https://unity3d.com/unity/beta/unity%s'.freeze ##################################################### # @!group REGEX: expressions to interpret data ##################################################### # Captures a version and its base url MAC_DOWNLOAD = %r{"(https?://[\w/\.-]+/[0-9a-f]{12}/)MacEditorInstaller/[a-zA-Z0-9/\.]+-(\d+\.\d+\.\d+\w\d+)\.?\w+"} WIN_DOWNLOAD = %r{"(https?://[\w/\.-]+/[0-9a-f]{12}/)Windows..EditorInstaller/[a-zA-Z0-9/\.]+-(\d+\.\d+\.\d+\w\d+)\.?\w+"} LINUX_DOWNLOAD_DATED = %r{"(https?://[\w/\._-]+/unity\-editor\-installer\-(\d+\.\d+\.\d+\w\d+).*\.sh)"} LINUX_DOWNLOAD_RECENT_PAGE = %r{"(http://beta\.unity3d\.com/download/[a-zA-Z0-9/\.]+/public_download\.html)"} LINUX_DOWNLOAD_RECENT_FILE = %r{'(https?://beta\.unity3d\.com/download/[a-zA-Z0-9/\.]+/unity\-editor\-installer\-(\d+\.\d+\.\d+(?:x)?\w\d+).*\.sh)'} # Captures a beta version in html page UNITY_BETAVERSION_REGEX = %r{\/unity\/beta\/unity(\d+\.\d+\.\d+\w\d+)"} UNITY_EXTRA_DOWNLOAD_REGEX = %r{"(https?:\/\/[\w\/.-]+\.unity3d\.com\/(\w+))\/[a-zA-Z\/.-]+\/download.html"} class << self def list_available(os: nil) os ||= U3dCore::Helper.operating_system case os when :linux return U3d::UnityVersions::LinuxVersions.list_available when :mac return U3d::UnityVersions::MacVersions.list_available when :win return U3d::UnityVersions::WindowsVersions.list_available else raise ArgumentError, "Operating system #{os} not supported" end end def fetch_version(url, pattern) hash = {} data = Utils.get_ssl(url) results = data.scan(pattern) results.each { |capt| hash[capt[1]] = capt[0] } return hash end def fetch_version_paged(url, pattern) U3d::Utils.get_ssl(url).scan(/\?page=\d+/).map do |page| fetch_version("#{url}#{page}", pattern) end.reduce({}, :merge) end def fetch_betas(url, pattern) hash = {} data = Utils.get_ssl(url) results = data.scan(UNITY_BETAVERSION_REGEX).uniq results.each { |beta| hash.merge!(fetch_version(format(UNITY_BETA_URL, version: beta[0]), pattern)) } hash end end class LinuxVersions class << self def list_available UI.message 'Loading Unity releases' data = linux_forum_page_content versions = {} results = data.scan(LINUX_DOWNLOAD_DATED) results.each do |capt| save_package_size(capt[1], capt[0]) versions[capt[1]] = capt[0] end response = nil results = data.scan(LINUX_DOWNLOAD_RECENT_PAGE) results.each do |page| response = linux_forum_version_page_content(page[0]) if response.is_a? Net::HTTPSuccess capt = response.body.match(LINUX_DOWNLOAD_RECENT_FILE) if capt && capt[1] && capt[2] ver = capt[2].delete('x') UI.important "Version #{ver} does not match standard Unity versions" unless ver =~ Utils::UNITY_VERSION_REGEX save_package_size(ver, capt[1]) versions[ver] = capt[1] else UI.error("Could not retrieve a fitting file from #{url}") end else UI.error("Could not access #{url}") end end if versions.count.zero? UI.important 'Found no releases' else UI.success "Found #{versions.count} releases." end versions end def save_package_size(version, url) uvd = UnityVersionDefinition.new(version, :linux, nil, offline: true) if (size = uvd.size_in_bytes('Unity')) UI.verbose "Package size for version #{version} already cached: #{size}" return end UI.verbose "Finding out package size for version #{version}" size = Utils.get_url_content_length(url) if size UnityVersionDefinition.create_fake(version, size, url) else UI.important "u3d tried to get the size of the installer for version #{version}, but wasn't able to" end end def linux_forum_page_content response = nil data = '' uri = URI(UNITY_LINUX_DOWNLOADS) Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| request = Net::HTTP::Get.new uri request['Connection'] = 'keep-alive' response = http.request request case response when Net::HTTPSuccess then # Successfully retrieved forum content data = response.body when Net::HTTPRedirection then # A session must be opened with the server before accessing forum res = nil cookie_str = '' # Store the name and value of the cookies returned by the server response['set-cookie'].gsub(/\s+/, '').split(',').each do |c| cookie_str << c.split(';', 2)[0] + '; ' end cookie_str.chomp!('; ') # It should be the Unity register API uri = URI(response['location']) Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http_api| request = Net::HTTP::Get.new uri request['Connection'] = 'keep-alive' res = http_api.request request end raise 'Unexpected result' unless res.is_a? Net::HTTPRedirection # It should be a redirection to the forum to perform authentication uri = URI(res['location']) request = Net::HTTP::Get.new uri request['Connection'] = 'keep-alive' request['Cookie'] = cookie_str res = http.request request raise 'Unable to establish a session with Unity forum' unless res.is_a? Net::HTTPRedirection cookie_str << '; ' + res['set-cookie'].gsub(/\s+/, '').split(';', 2)[0] uri = URI(res['location']) request = Net::HTTP::Get.new uri request['Connection'] = 'keep-alive' request['Cookie'] = cookie_str res = http.request request data = res.body if res.is_a? Net::HTTPSuccess else raise "Request failed with status #{response.code}" end end data end def linux_forum_version_page_content(url) uri = URI(url) Net::HTTP.start(uri.host, uri.port) do |http| request = Net::HTTP::Get.new uri return http.request request end end end end class MacVersions class << self def list_available versions = {} UI.message 'Loading Unity releases' current = UnityVersions.fetch_version(UNITY_DOWNLOADS, MAC_DOWNLOAD) UI.success "Found #{current.count} releases." if current.count.nonzero? versions = versions.merge(current) UI.message 'Loading Unity patch releases' current = UnityVersions.fetch_version_paged(UNITY_PATCHES, MAC_DOWNLOAD) UI.success "Found #{current.count} patch releases." if current.count.nonzero? versions = versions.merge(current) UI.message 'Loading Unity beta releases' current = UnityVersions.fetch_betas(UNITY_BETAS, MAC_DOWNLOAD) UI.success "Found #{current.count} beta releases." if current.count.nonzero? versions = versions.merge(current) versions end end end class WindowsVersions class << self def list_available versions = {} UI.message 'Loading Unity releases' current = UnityVersions.fetch_version(UNITY_DOWNLOADS, WIN_DOWNLOAD) UI.success "Found #{current.count} releases." if current.count.nonzero? versions = versions.merge(current) UI.message 'Loading Unity patch releases' current = UnityVersions.fetch_version_paged(UNITY_PATCHES, WIN_DOWNLOAD) UI.success "Found #{current.count} patch releases." if current.count.nonzero? versions = versions.merge(current) UI.message 'Loading Unity beta releases' current = UnityVersions.fetch_betas(UNITY_BETAS, WIN_DOWNLOAD) UI.success "Found #{current.count} beta releases." if current.count.nonzero? versions = versions.merge(current) versions end end end end end