| 
									
										
										
										
											2025-04-23 03:30:15 +01:00
										 |  |  | # typed: strict | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require "url" | 
					
						
							|  |  |  | require "checksum" | 
					
						
							| 
									
										
										
										
											2024-07-16 21:19:37 +02:00
										 |  |  | require "download_strategy" | 
					
						
							| 
									
										
										
										
											2025-08-20 19:20:19 +01:00
										 |  |  | require "utils/output" | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 21:03:08 -04:00
										 |  |  | module Downloadable | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   include Context | 
					
						
							| 
									
										
										
										
											2025-08-20 19:20:19 +01:00
										 |  |  |   include Utils::Output::Mixin | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   extend T::Helpers | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   abstract! | 
					
						
							| 
									
										
										
										
											2024-10-06 09:25:57 -07:00
										 |  |  |   requires_ancestor { Kernel } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-26 11:35:26 -07:00
										 |  |  |   sig { overridable.returns(T.any(NilClass, String, URL)) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   attr_reader :url | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 22:51:54 -04:00
										 |  |  |   sig { overridable.returns(T.nilable(Checksum)) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   attr_reader :checksum | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 22:51:54 -04:00
										 |  |  |   sig { overridable.returns(T::Array[String]) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   attr_reader :mirrors | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { void } | 
					
						
							|  |  |  |   def initialize | 
					
						
							| 
									
										
										
										
											2025-04-23 03:30:15 +01:00
										 |  |  |     @url = T.let(nil, T.nilable(URL)) | 
					
						
							|  |  |  |     @checksum = T.let(nil, T.nilable(Checksum)) | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |     @mirrors = T.let([], T::Array[String]) | 
					
						
							| 
									
										
										
										
											2025-04-23 03:30:15 +01:00
										 |  |  |     @version = T.let(nil, T.nilable(Version)) | 
					
						
							|  |  |  |     @download_strategy = T.let(nil, T.nilable(T::Class[AbstractDownloadStrategy])) | 
					
						
							|  |  |  |     @downloader = T.let(nil, T.nilable(AbstractDownloadStrategy)) | 
					
						
							|  |  |  |     @download_name = T.let(nil, T.nilable(String)) | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-15 19:21:28 +01:00
										 |  |  |   sig { overridable.params(other: Downloadable).void } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def initialize_dup(other) | 
					
						
							|  |  |  |     super | 
					
						
							|  |  |  |     @checksum = @checksum.dup | 
					
						
							|  |  |  |     @mirrors = @mirrors.dup | 
					
						
							|  |  |  |     @version = @version.dup | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 22:51:54 -04:00
										 |  |  |   sig { overridable.returns(T.self_type) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def freeze | 
					
						
							|  |  |  |     @checksum.freeze | 
					
						
							|  |  |  |     @mirrors.freeze | 
					
						
							|  |  |  |     @version.freeze | 
					
						
							|  |  |  |     super | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 11:42:22 -04:00
										 |  |  |   sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2025-08-04 15:51:02 +01:00
										 |  |  |   def download_queue_name = download_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { abstract.returns(String) } | 
					
						
							|  |  |  |   def download_queue_type; end | 
					
						
							| 
									
										
										
										
											2024-07-14 11:42:22 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 22:51:54 -04:00
										 |  |  |   sig(:final) { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def downloaded? | 
					
						
							|  |  |  |     cached_download.exist? | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 22:51:54 -04:00
										 |  |  |   sig { overridable.returns(Pathname) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def cached_download | 
					
						
							|  |  |  |     downloader.cached_location | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 22:51:54 -04:00
										 |  |  |   sig { overridable.void } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def clear_cache | 
					
						
							|  |  |  |     downloader.clear_cache | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 22:51:54 -04:00
										 |  |  |   sig { overridable.returns(T.nilable(Version)) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def version | 
					
						
							|  |  |  |     return @version if @version && !@version.null? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     version = determine_url&.version | 
					
						
							|  |  |  |     version unless version&.null? | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-23 03:30:15 +01:00
										 |  |  |   sig { overridable.returns(T::Class[AbstractDownloadStrategy]) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def download_strategy | 
					
						
							| 
									
										
										
										
											2025-04-23 03:30:15 +01:00
										 |  |  |     @download_strategy ||= T.must(determine_url).download_strategy | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 21:03:08 -04:00
										 |  |  |   sig { overridable.returns(AbstractDownloadStrategy) } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def downloader | 
					
						
							|  |  |  |     @downloader ||= begin | 
					
						
							|  |  |  |       primary_url, *mirrors = determine_url_mirrors | 
					
						
							| 
									
										
										
										
											2024-07-14 21:03:08 -04:00
										 |  |  |       raise ArgumentError, "attempted to use a `Downloadable` without a URL!" if primary_url.blank? | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       download_strategy.new(primary_url, download_name, version, | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |                             mirrors:, cache:, **T.must(@url).specs) | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 11:42:22 -04:00
										 |  |  |   sig { | 
					
						
							| 
									
										
										
										
											2024-07-14 21:03:08 -04:00
										 |  |  |     overridable.params( | 
					
						
							| 
									
										
										
										
											2024-07-14 11:42:22 -04:00
										 |  |  |       verify_download_integrity: T::Boolean, | 
					
						
							|  |  |  |       timeout:                   T.nilable(T.any(Integer, Float)), | 
					
						
							|  |  |  |       quiet:                     T::Boolean, | 
					
						
							|  |  |  |     ).returns(Pathname) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   def fetch(verify_download_integrity: true, timeout: nil, quiet: false) | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |     cache.mkpath | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     begin | 
					
						
							| 
									
										
										
										
											2024-07-14 11:42:22 -04:00
										 |  |  |       downloader.quiet! if quiet | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |       downloader.fetch(timeout:) | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |     rescue ErrorDuringExecution, CurlDownloadStrategyError => e | 
					
						
							|  |  |  |       raise DownloadError.new(self, e) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     download = cached_download | 
					
						
							|  |  |  |     verify_download_integrity(download) if verify_download_integrity | 
					
						
							|  |  |  |     download | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 21:03:08 -04:00
										 |  |  |   sig { overridable.params(filename: Pathname).void } | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   def verify_download_integrity(filename) | 
					
						
							|  |  |  |     if filename.file? | 
					
						
							|  |  |  |       ohai "Verifying checksum for '#{filename.basename}'" if verbose? | 
					
						
							|  |  |  |       filename.verify_checksum(checksum) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   rescue ChecksumMissingError | 
					
						
							| 
									
										
										
										
											2025-03-19 12:45:50 +00:00
										 |  |  |     return if silence_checksum_missing_error? | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |     opoo <<~EOS | 
					
						
							|  |  |  |       Cannot verify integrity of '#{filename.basename}'. | 
					
						
							|  |  |  |       No checksum was provided. | 
					
						
							|  |  |  |       For your reference, the checksum is: | 
					
						
							|  |  |  |         sha256 "#{filename.sha256}" | 
					
						
							|  |  |  |     EOS | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-15 17:13:30 +01:00
										 |  |  |   sig { returns(Integer) } | 
					
						
							|  |  |  |   def hash | 
					
						
							|  |  |  |     [self.class, cached_download].hash | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { params(other: Object).returns(T::Boolean) } | 
					
						
							|  |  |  |   def eql?(other) | 
					
						
							|  |  |  |     return false if self.class != other.class | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     other = T.cast(other, Downloadable) | 
					
						
							|  |  |  |     cached_download == other.cached_download | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(String) } | 
					
						
							|  |  |  |   def to_s | 
					
						
							|  |  |  |     short_cached_download = cached_download.to_s | 
					
						
							|  |  |  |                                            .delete_prefix("#{HOMEBREW_CACHE}/downloads/") | 
					
						
							|  |  |  |     "#<#{self.class}: #{short_cached_download}>" | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 15:51:02 +01:00
										 |  |  |   private | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   sig { overridable.returns(String) } | 
					
						
							|  |  |  |   def download_name | 
					
						
							| 
									
										
										
										
											2025-04-23 03:30:15 +01:00
										 |  |  |     @download_name ||= File.basename(determine_url.to_s).freeze | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-19 12:45:50 +00:00
										 |  |  |   sig { overridable.returns(T::Boolean) } | 
					
						
							|  |  |  |   def silence_checksum_missing_error? | 
					
						
							|  |  |  |     false | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-18 00:22:13 +01:00
										 |  |  |   sig { overridable.returns(T.nilable(URL)) } | 
					
						
							|  |  |  |   def determine_url | 
					
						
							|  |  |  |     @url | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { overridable.returns(T::Array[String]) } | 
					
						
							|  |  |  |   def determine_url_mirrors | 
					
						
							|  |  |  |     [determine_url.to_s, *mirrors].uniq | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { overridable.returns(Pathname) } | 
					
						
							|  |  |  |   def cache | 
					
						
							|  |  |  |     HOMEBREW_CACHE | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |