Class: Deliver::AppMetadata

Inherits:
Object
  • Object
show all
Defined in:
lib/deliver/app_metadata.rb

Direct Known Subclasses

IpaUploader

Constant Summary collapse

ITUNES_NAMESPACE =
"http://apple.com/itunes/importer"
INVALID_LANGUAGE_ERROR =
"The specified language could not be found. Make sure it is available in Deliver::Languages::ALL_LANGUAGES"

Instance Attribute Summary collapse

Updating metadata information collapse

Screenshot related collapse

Manually fetching elements from the metadata.xml collapse

Uploading the updated metadata collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, dir, redownload_package = true) ⇒ AppMetadata

You don’t have to manually create an AppMetadata object. It will be created when you access the app’s metadata (Deliver::App#metadata)

Parameters:

  • app (Deliver::App)

    The app this metadata is from/for

  • dir (String)

    The app this metadata is from/for

  • redownload_package (bool) (defaults to: true)

    When true the current package will be downloaded from iTC before you can modify any values. This should only be false for unit tests

Raises:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/deliver/app_metadata.rb', line 46

def initialize(app, dir, redownload_package = true)
  raise AppMetadataParameterError.new("No valid Deliver::App given") unless app.kind_of?Deliver::App

  @metadata_dir = dir
  @app = app

  if self.class == AppMetadata
    if redownload_package
      # Delete the one that may exists already
      unless Helper.is_test?
        `rm -rf #{dir}/*.itmsp`
      end

      # we want to update the metadata, so first we have to download the existing one
      transporter.download(app, dir)

      # Parse the downloaded package
      parse_package(dir)
    else
      # use_data contains the data to be used. This is the case for unit tests
      parse_package(dir)
    end
  end
end

Instance Attribute Details

#informationObject

Returns Data contains all information for this app, including the unmodified one.

Returns:

  • Data contains all information for this app, including the unmodified one



15
16
17
# File 'lib/deliver/app_metadata.rb', line 15

def information
  @information
end

Instance Method Details

#add_new_locale(language) ⇒ Bool

Adds a new locale (language) to the given app

Parameters:

Returns:

  • (Bool)

    Is true, if the language was created. False, when the language alreade existed

Raises:



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
113
114
115
116
117
118
# File 'lib/deliver/app_metadata.rb', line 87

def add_new_locale(language)
  unless Deliver::Languages::ALL_LANGUAGES.include?language
    raise "Language '#{language}' is invalid. It must be in #{Deliver::Languages::ALL_LANGUAGES}."
  end

  if information[language] != nil
    Helper.log.info("Locale '#{language}' already exists. Can not create it again.")
    return false
  end
  

  locales = fetch_value("//x:locales").first
  
  new_locale = @data.create_element('locale')
  new_locale['name'] = language
  locales << new_locale

  # Title is the only thing which is required by iTC
  default_title = information.values.first[:title][:value]

  title = @data.create_element('title')
  title.content = default_title
  new_locale << title

  Helper.log.info("Successfully created the new locale '#{language}'. The default title '#{default_title}' was set, since it's required by iTunesConnect.")
  Helper.log.info("You can update the title using 'app.metadata.update_title'")
  
  information[language] ||= {}
  information[language][:title] = { value: default_title, modified: true}

  true
end

#add_screenshot(language, app_screenshot) ⇒ Object

Appends another screenshot to the already existing ones

Parameters:

  • language (String)

    The language, which has to be in this list: Languages.

  • app_screenshot (Deliver::AppScreenshot)

    The screenshot you want to add to the app metadata.

Raises:



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/deliver/app_metadata.rb', line 219

def add_screenshot(language, app_screenshot)
  raise AppMetadataParameterError.new(INVALID_LANGUAGE_ERROR) unless Languages::ALL_LANGUAGES.include?language
  
  create_locale_if_not_exists(language)

  # Fetch the 'software_screenshots' node (array) for the specific locale
  locales = self.fetch_value("//x:locale[@name='#{language}']")

  screenshots = self.fetch_value("//x:locale[@name='#{language}']/x:software_screenshots").first
  
  if not screenshots or screenshots.children.count == 0
    screenshots.remove if screenshots

    # First screenshot ever
    screenshots = Nokogiri::XML::Node.new('software_screenshots', @data)
    locales.first << screenshots

    node_set = Nokogiri::XML::NodeSet.new(@data)
    node_set << app_screenshot.create_xml_node(@data, 1)
    screenshots.children = node_set
  else
    # There is already at least one screenshot
    next_index = 1
    screenshots.children.each do |screen|
      if screen['display_target'] == app_screenshot.screen_size
        next_index += 1
      end
    end

    if next_index > MAXIMUM_NUMBER_OF_SCREENSHOTS
      raise AppMetadataParameterError.new("Only #{MAXIMUM_NUMBER_OF_SCREENSHOTS} screenshots are allowed per language per device type (#{app_screenshot.screen_size})")
    end

    # Ready for storing the screenshot into the metadata.xml now
    screenshots.children.after(app_screenshot.create_xml_node(@data, next_index))
  end

  information[language][:screenshots] << app_screenshot

  app_screenshot.store_file_inside_package(@package_path)
end

#clear_all_screenshots(language) ⇒ Object

Removes all currently enabled screenshots for the given language.

Parameters:

  • language (String)

    The language, which has to be in this list: Languages.

Raises:



204
205
206
207
208
209
210
211
212
# File 'lib/deliver/app_metadata.rb', line 204

def clear_all_screenshots(language)
  raise AppMetadataParameterError.new(INVALID_LANGUAGE_ERROR) unless Languages::ALL_LANGUAGES.include?language

  update_localized_value('software_screenshots', {language => {}}) do |field, useless, language|
    field.children.remove # remove all the screenshots
  end
  information[language][:screenshots] = []
  true
end

#fetch_value(xpath) ⇒ Object

Directly fetch XML nodes from the metadata.xml.

Examples:

Fetch all keywords

fetch_value("//x:keyword")

Fetch a specific locale

fetch_value("//x:locale[@name='de-DE']")

Fetch the node that contains all screenshots for a specific language

fetch_value("//x:locale[@name='de-DE']/x:software_screenshots")

Returns:

  • the requests XML nodes or node set



353
354
355
# File 'lib/deliver/app_metadata.rb', line 353

def fetch_value(xpath)
  @data.xpath(xpath, "x" => ITUNES_NAMESPACE)
end

#set_all_screenshots(new_screenshots) ⇒ bool

This method will clear all screenshots and set the new ones you pass This method uses #clear_all_screenshots and #add_screenshot under the hood.

Parameters:

Returns:

  • (bool)

    true if everything was successful

Raises:



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/deliver/app_metadata.rb', line 274

def set_all_screenshots(new_screenshots)
  error_text = "Please pass a hash, containing an array of AppScreenshot objects"
  raise AppMetadataParameterError.new(error_text) unless new_screenshots.kind_of?Hash

  new_screenshots.each do |key, value|
    if key.kind_of?String and value.kind_of?Array and value.count > 0 and value.first.kind_of?AppScreenshot
      
      self.clear_all_screenshots(key)

      value.each do |screen|
        add_screenshot(key, screen)
      end
    else
      raise AppMetadataParameterError.new(error_text)
    end
  end
  true
end

#set_all_screenshots_from_path(path) ⇒ Object

This method will run through all the available locales, check if there is a folder for this language (e.g. ‘en-US’) and use all screenshots in there

Parameters:

  • path (String)

    A path to the folder, which contains a folder for each locale

Raises:



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/deliver/app_metadata.rb', line 324

def set_all_screenshots_from_path(path)
  raise AppMetadataParameterError.new("Parameter needs to be a path (string)") unless path.kind_of?String

  found = false
  Deliver::Languages::ALL_LANGUAGES.each do |language|
    full_path = path + "/#{language}"
    if File.directory?(full_path)
      found = true
      set_screenshots_for_each_language({
        language => full_path
      })
    end
  end
  return found
end

#set_screenshots_for_each_language(hash) ⇒ Object

Automatically add all screenshots contained in the given directory to the app.

This method will automatically detect which device type each screenshot is.

This will also clear all existing screenshots before setting the new ones.

Parameters:

Raises:



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/deliver/app_metadata.rb', line 299

def set_screenshots_for_each_language(hash)
  raise AppMetadataParameterError.new("Parameter needs to be an hash, containg strings with the new description") unless hash.kind_of?Hash

  hash.each do |language, current_path|
    resulting_path = "#{current_path}/*.png"

    raise AppMetadataParameterError.new(INVALID_LANGUAGE_ERROR) unless Languages::ALL_LANGUAGES.include?language

    if Dir[resulting_path].count == 0
      Helper.log.error("No screenshots found at the given path '#{resulting_path}'") 
    else
      self.clear_all_screenshots(language)
      
      Dir[resulting_path].sort.each do |path|
        add_screenshot(language, Deliver::AppScreenshot.new(path))
      end
    end
  end

  true
end

#update_changelog(hash) ⇒ Object

Updates the app changelog of the latest version

Parameters:

  • hash (Hash)

    The hash should contain the correct language codes (Languages) as keys.

Raises:



145
146
147
# File 'lib/deliver/app_metadata.rb', line 145

def update_changelog(hash)
  (:version_whats_new, hash)
end

#update_description(hash) ⇒ Object

Updates the app description which is shown in the AppStore

Parameters:

  • hash (Hash)

    The hash should contain the correct language codes (Languages) as keys.

Raises:



137
138
139
# File 'lib/deliver/app_metadata.rb', line 137

def update_description(hash)
  (:description, hash)
end

#update_keywords(hash) ⇒ Object

Updates the app keywords

Parameters:

  • hash (Hash)

    The hash should contain the correct language codes (Languages) as keys. The value should be an array of keywords (each keyword is a string)

Raises:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/deliver/app_metadata.rb', line 177

def update_keywords(hash)
  update_localized_value('keywords', hash) do |field, keywords, language|
    raise AppMetadataParameterError.new("Parameter needs to be a hash (each language) with an array of keywords in it (given: #{hash})") unless keywords.kind_of?Array

    if not information[language][:keywords] or keywords.sort != information[language][:keywords][:value].sort
      field.children.remove # remove old keywords

      node_set = Nokogiri::XML::NodeSet.new(@data)
      keywords.each do |word|
        keyword = Nokogiri::XML::Node.new('keyword', @data)
        keyword.content = word
        node_set << keyword
      end

      field.children = node_set

      information[language][:keywords] = { value: keywords, modified: true }
    end
  end
end

#update_marketing_url(hash) ⇒ Object

Updates the Marketing URL

Parameters:

  • hash (Hash)

    The hash should contain the correct language codes (Languages) as keys.

Raises:



153
154
155
# File 'lib/deliver/app_metadata.rb', line 153

def update_marketing_url(hash)
  (:software_url, hash)
end

#update_privacy_url(hash) ⇒ Object

Updates the Privacy URL

Parameters:

  • hash (Hash)

    The hash should contain the correct language codes (Languages) as keys.

Raises:



169
170
171
# File 'lib/deliver/app_metadata.rb', line 169

def update_privacy_url(hash)
  (:privacy_url, hash)
end

#update_support_url(hash) ⇒ Object

Updates the Support URL

Parameters:

  • hash (Hash)

    The hash should contain the correct language codes (Languages) as keys.

Raises:



161
162
163
# File 'lib/deliver/app_metadata.rb', line 161

def update_support_url(hash)
  (:support_url, hash)
end

#update_title(hash) ⇒ Object

Updates the app title

Parameters:

  • hash (Hash)

    The hash should contain the correct language codes (Languages) as keys.

Raises:



129
130
131
# File 'lib/deliver/app_metadata.rb', line 129

def update_title(hash)
  (:title, hash)
end

#upload!Object

Actually uploads the updated metadata to Apple. This method might take a while.

Raises:



366
367
368
369
370
371
372
373
# File 'lib/deliver/app_metadata.rb', line 366

def upload!
  unless Helper.is_test?
    # First: Write the current XML state to disk
    File.write("#{@package_path}/#{METADATA_FILE_NAME}", @data.to_xml)
  end

  transporter.upload(@app, @metadata_dir)
end

#verify_version(version_number) ⇒ Object

Verifies the if the version of iTunesConnect matches the one you pass as parameter



76
77
78
79
80
# File 'lib/deliver/app_metadata.rb', line 76

def verify_version(version_number)
  xml_version = self.fetch_value("//x:version").first['string']
  raise "Version mismatch: on iTunesConnect the latest version is '#{xml_version}', you specified '#{version_number}'" if xml_version != version_number
  true
end