Class: Deliver::UploadScreenshots
- Inherits:
-
Object
- Object
- Deliver::UploadScreenshots
- Defined in:
- deliver/lib/deliver/upload_screenshots.rb
Overview
upload screenshots to App Store Connect
Defined Under Namespace
Classes: DeleteScreenshotJob, UploadScreenshotJob
Constant Summary collapse
- NUMBER_OF_THREADS =
Helper.test? ? 1 : [ENV.fetch("DELIVER_NUMBER_OF_THREADS", 10).to_i, 10].min
Class Method Summary collapse
-
.available_languages ⇒ Object
helper method so Spaceship::Tunes.client.available_languages is easier to test.
-
.calculate_checksum(path) ⇒ Object
helper method to mock this step in tests.
Instance Method Summary collapse
- #collect_screenshots(options) ⇒ Object
- #collect_screenshots_for_languages(path, ignore_validation) ⇒ Object
- #delete_screenshots(localizations, screenshots_per_language, tries: 5) ⇒ Object
-
#retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language) ⇒ Object
Verify all screenshots states on App Store Connect are okay.
- #sort_screenshots(localizations) ⇒ Object
- #upload(options, screenshots) ⇒ Object
- #upload_screenshots(localizations, screenshots_per_language, tries: 5) ⇒ Object
-
#verify_local_screenshots_are_uploaded(iterator, screenshots_per_language) ⇒ Object
Return ‘true` if all the local screenshots are uploaded to App Store Connect.
-
#wait_for_complete(iterator) ⇒ Object
Verify all screenshots have been processed.
Class Method Details
.available_languages ⇒ Object
helper method so Spaceship::Tunes.client.available_languages is easier to test
306 307 308 309 310 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 306 def self.available_languages # 2020-08-24 - Available locales are not available as an endpoint in App Store Connect # Update with Spaceship::Tunes.client.available_languages.sort (as long as endpoint is avilable) Deliver::Languages::ALL_LANGUAGES end |
.calculate_checksum(path) ⇒ Object
helper method to mock this step in tests
313 314 315 316 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 313 def self.calculate_checksum(path) bytes = File.binread(path) Digest::MD5.hexdigest(bytes) end |
Instance Method Details
#collect_screenshots(options) ⇒ Object
237 238 239 240 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 237 def collect_screenshots() return [] if [:skip_screenshots] return collect_screenshots_for_languages([:screenshots_path], [:ignore_language_directory_validation]) end |
#collect_screenshots_for_languages(path, ignore_validation) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 242 def collect_screenshots_for_languages(path, ignore_validation) screenshots = [] extensions = '{png,jpg,jpeg}' available_languages = UploadScreenshots.available_languages.each_with_object({}) do |lang, lang_hash| lang_hash[lang.downcase] = lang end Loader.language_folders(path, ignore_validation).each do |lng_folder| language = File.basename(lng_folder) # Check to see if we need to traverse multiple platforms or just a single platform if language == Loader::APPLE_TV_DIR_NAME || language == Loader::IMESSAGE_DIR_NAME screenshots.concat(collect_screenshots_for_languages(File.join(path, language), ignore_validation)) next end files = Dir.glob(File.join(lng_folder, "*.#{extensions}"), File::FNM_CASEFOLD).sort next if files.count == 0 framed_screenshots_found = Dir.glob(File.join(lng_folder, "*_framed.#{extensions}"), File::FNM_CASEFOLD).count > 0 UI.important("Framed screenshots are detected! 🖼 Non-framed screenshot files may be skipped. 🏃") if framed_screenshots_found language_dir_name = File.basename(lng_folder) if available_languages[language_dir_name.downcase].nil? UI.user_error!("#{language_dir_name} is not an available language. Please verify that your language codes are available in iTunesConnect. See https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/AppStoreTerritories.html for more information.") end language = available_languages[language_dir_name.downcase] files.each do |file_path| is_framed = file_path.downcase.include?("_framed.") is_watch = file_path.downcase.include?("watch") if framed_screenshots_found && !is_framed && !is_watch UI.important("🏃 Skipping screenshot file: #{file_path}") next end screenshots << AppScreenshot.new(file_path, language) end end # Checking if the device type exists in spaceship # Ex: iPhone 6.1 inch isn't supported in App Store Connect but need # to have it in there for frameit support unaccepted_device_shown = false screenshots.select! do |screenshot| exists = !screenshot.device_type.nil? unless exists UI.important("Unaccepted device screenshots are detected! 🚫 Screenshot file will be skipped. 🏃") unless unaccepted_device_shown unaccepted_device_shown = true UI.important("🏃 Skipping screenshot file: #{screenshot.path} - Not an accepted App Store Connect device...") end exists end return screenshots end |
#delete_screenshots(localizations, screenshots_per_language, tries: 5) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 69 def delete_screenshots(localizations, screenshots_per_language, tries: 5) tries -= 1 worker = QueueWorker.new(NUMBER_OF_THREADS) do |job| start_time = Time.now target = "#{job.localization.locale} #{job.app_screenshot_set.screenshot_display_type} #{job.app_screenshot.id}" begin UI.verbose("Deleting '#{target}'") job.app_screenshot.delete! UI.("Deleted '#{target}' - (#{Time.now - start_time} secs)") rescue => error UI.error("Failed to delete screenshot #{target} - (#{Time.now - start_time} secs)") UI.error(error.) end end iterator = AppScreenshotIterator.new(localizations) iterator.each_app_screenshot do |localization, app_screenshot_set, app_screenshot| # Only delete screenshots if trying to upload next unless screenshots_per_language.keys.include?(localization.locale) UI.verbose("Queued delete sceeenshot job for #{localization.locale} #{app_screenshot_set.screenshot_display_type} #{app_screenshot.id}") worker.enqueue(DeleteScreenshotJob.new(app_screenshot, localization, app_screenshot_set)) end worker.start # Verify all screenshots have been deleted # Sometimes API requests will fail but screenshots will still be deleted count = iterator.each_app_screenshot_set.map { |_, app_screenshot_set| app_screenshot_set } .reduce(0) { |sum, app_screenshot_set| sum + app_screenshot_set.app_screenshots.size } UI.important("Number of screenshots not deleted: #{count}") if count > 0 if tries.zero? UI.user_error!("Failed verification of all screenshots deleted... #{count} screenshot(s) still exist") else UI.error("Failed to delete all screenshots... Tries remaining: #{tries}") delete_screenshots(localizations, screenshots_per_language, tries: tries) end else UI.("Successfully deleted all screenshots") end end |
#retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language) ⇒ Object
Verify all screenshots states on App Store Connect are okay
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 180 def retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language) is_failure = states.fetch("FAILED", 0) > 0 is_missing_screenshot = !screenshots_per_language.empty? && !verify_local_screenshots_are_uploaded(iterator, screenshots_per_language) return unless is_failure || is_missing_screenshot if tries.zero? iterator.each_app_screenshot.select { |_, _, app_screenshot| app_screenshot.error? }.each do |localization, _, app_screenshot| UI.error("#{app_screenshot.file_name} for #{localization.locale} has error(s) - #{app_screenshot..join(', ')}") end incomplete_screenshot_count = states.reject { |k, v| k == 'COMPLETE' }.reduce(0) { |sum, (k, v)| sum + v } UI.user_error!("Failed verification of all screenshots uploaded... #{incomplete_screenshot_count} incomplete screenshot(s) still exist") else UI.error("Failed to upload all screenshots... Tries remaining: #{tries}") # Delete bad entries before retry iterator.each_app_screenshot do |_, _, app_screenshot| app_screenshot.delete! unless app_screenshot.complete? end upload_screenshots(localizations, screenshots_per_language, tries: tries) end end |
#sort_screenshots(localizations) ⇒ Object
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 218 def sort_screenshots(localizations) iterator = AppScreenshotIterator.new(localizations) # Re-order screenshots within app_screenshot_set worker = QueueWorker.new(NUMBER_OF_THREADS) do |app_screenshot_set| original_ids = app_screenshot_set.app_screenshots.map(&:id) sorted_ids = app_screenshot_set.app_screenshots.sort_by(&:file_name).map(&:id) if original_ids != sorted_ids app_screenshot_set.reorder_screenshots(app_screenshot_ids: sorted_ids) end end iterator.each_app_screenshot_set do |_, app_screenshot_set| worker.enqueue(app_screenshot_set) end worker.start end |
#upload(options, screenshots) ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 18 def upload(, screenshots) return if [:skip_screenshots] return if [:edit_live] app = [:app] platform = Spaceship::ConnectAPI::Platform.map([:platform]) version = app.get_edit_app_store_version(platform: platform) UI.user_error!("Could not find a version to edit for app '#{app.name}' for '#{platform}'") unless version UI.important("Will begin uploading snapshots for '#{version.version_string}' on App Store Connect") UI.("Starting with the upload of screenshots...") screenshots_per_language = screenshots.group_by(&:language) localizations = version.get_app_store_version_localizations if [:overwrite_screenshots] delete_screenshots(localizations, screenshots_per_language) end # Finding languages to enable languages = screenshots_per_language.keys locales_to_enable = languages - localizations.map(&:locale) if locales_to_enable.count > 0 lng_text = "language" lng_text += "s" if locales_to_enable.count != 1 Helper.show_loading_indicator("Activating #{lng_text} #{locales_to_enable.join(', ')}...") locales_to_enable.each do |locale| version.create_app_store_version_localization(attributes: { locale: locale }) end Helper.hide_loading_indicator # Refresh version localizations localizations = version.get_app_store_version_localizations end upload_screenshots(localizations, screenshots_per_language) Helper.show_loading_indicator("Sorting screenshots uploaded...") sort_screenshots(localizations) Helper.hide_loading_indicator UI.success("Successfully uploaded screenshots to App Store Connect") end |
#upload_screenshots(localizations, screenshots_per_language, tries: 5) ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 114 def upload_screenshots(localizations, screenshots_per_language, tries: 5) tries -= 1 # Upload screenshots worker = QueueWorker.new(NUMBER_OF_THREADS) do |job| begin UI.verbose("Uploading '#{job.path}'...") start_time = Time.now job.app_screenshot_set.upload_screenshot(path: job.path, wait_for_processing: false) UI.("Uploaded '#{job.path}'... (#{Time.now - start_time} secs)") rescue => error UI.error(error) end end number_of_screenshots = 0 iterator = AppScreenshotIterator.new(localizations) iterator.each_local_screenshot(screenshots_per_language) do |localization, app_screenshot_set, screenshot, index| if index >= 10 UI.error("Too many screenshots found for device '#{screenshot.device_type}' in '#{screenshot.language}', skipping this one (#{screenshot.path})") next end checksum = UploadScreenshots.calculate_checksum(screenshot.path) duplicate = (app_screenshot_set.app_screenshots || []).any? { |s| s.source_file_checksum == checksum } # Enqueue uploading job if it's not duplicated otherwise screenshot will be skipped if duplicate UI.("Previous uploaded. Skipping '#{screenshot.path}'...") else worker.enqueue(UploadScreenshotJob.new(app_screenshot_set, screenshot.path)) end number_of_screenshots += 1 end worker.start UI.verbose('Uploading jobs are completed') Helper.show_loading_indicator("Waiting for all the screenshots processed...") states = wait_for_complete(iterator) Helper.hide_loading_indicator retry_upload_screenshots_if_needed(iterator, states, number_of_screenshots, tries, localizations, screenshots_per_language) UI.("Successfully uploaded all screenshots") end |
#verify_local_screenshots_are_uploaded(iterator, screenshots_per_language) ⇒ Object
Return ‘true` if all the local screenshots are uploaded to App Store Connect
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 202 def verify_local_screenshots_are_uploaded(iterator, screenshots_per_language) # Check if local screenshots' checksum exist on App Store Connect checksum_to_app_screenshot = iterator.each_app_screenshot.map { |_, _, app_screenshot| [app_screenshot.source_file_checksum, app_screenshot] }.to_h missing_local_screenshots = iterator.each_local_screenshot(screenshots_per_language).select do |_, _, local_screenshot, index| checksum = UploadScreenshots.calculate_checksum(local_screenshot.path) checksum_to_app_screenshot[checksum].nil? && index < 10 # if index is more than 10, it's skipped end missing_local_screenshots.each do |_, _, screenshot, _| UI.error("#{screenshot.path} is missing on App Store Connect.") end missing_local_screenshots.empty? end |
#wait_for_complete(iterator) ⇒ Object
Verify all screenshots have been processed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'deliver/lib/deliver/upload_screenshots.rb', line 163 def wait_for_complete(iterator) loop do states = iterator.each_app_screenshot.map { |_, _, app_screenshot| app_screenshot }.each_with_object({}) do |app_screenshot, hash| state = app_screenshot.asset_delivery_state['state'] hash[state] ||= 0 hash[state] += 1 end is_processing = states.fetch('UPLOAD_COMPLETE', 0) > 0 return states unless is_processing UI.verbose("There are still incomplete screenshots - #{states}") sleep(5) end end |