Class: Blinkbox::CommonMapping

Inherits:
Object
  • Object
show all
Defined in:
lib/blinkbox/common_mapping.rb

Constant Summary collapse

VERSION =
begin
  File.read(File.join(__dir__, "../../VERSION")).strip
rescue Errno::ENOENT
  "0.0.0-unknown"
end
@@logger =

NullLogger by default

Class.new {
  def debug(*); end
  def info(*); end
  def warn(*); end
  def error(*); end
  def fatal(*); end
}.new

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(storage_service_url, service_name: raise(ArgumentError, "A service name is required"), schema_root: "schemas", mapping_timeout: 7 * 24 * 3600) ⇒ CommonMapping

Initializing a mapper will retrieve the mapping file from the specified storage service and set up an exclusive queue to receive updates which might occur while this instance is in use.

Parameters:

  • storage_service_url (String)

    The Base URL for the storage service.

  • :service_name (String)

    The name of your service. Defines the name of the mapping updates queue.

  • :schema_root (String, nil)

    If not nil, the location (relative to the current directory) of the schema root (mapping/update/v1.schema.json will be used to validate messages).

  • :mapping_timeout (Integer)

    The length of time before a new mapping file is requested from the storage service.


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
68
69
70
71
72
73
74
75
# File 'lib/blinkbox/common_mapping.rb', line 40

def initialize(storage_service_url, service_name: raise(ArgumentError, "A service name is required"), schema_root: "schemas", mapping_timeout: 7 * 24 * 3600)
  @ss = URI.parse(storage_service_url)
  @service_name = service_name
  uid = [Socket.gethostname, Process.pid].join("$")
  queue_name = "#{service_name.tr('/', '.')}.mapping_updates.#{uid}"

  @queue = CommonMessaging::Queue.new(
    queue_name,
    exchange: "Mapping",
    bindings: [{ "content-type" => "application/vnd.blinkbox.books.mapping.update.v1+json" }],
    prefetch: 1,
    exclusive: true,
    temporary: true,
    dlx: nil
  )

  @timeout = mapping_timeout

  opts = { block: false }
  if !schema_root.nil?
    CommonMessaging.init_from_schema_at(File.join(schema_root, "mapping"), schema_root)
    opts[:accept] = [CommonMessaging::MappingUpdateV1]
  end

  # We're about to request the latest mapping file, so we don't need any of the ones on the queue.
  @queue.purge!
  @queue.subscribe(opts) do |, update|
    next :reject unless [:timestamp].is_a?(Time)
    update_mapping!([:timestamp], update)
    :ack
  end

  @@logger.debug "Queue #{queue_name} created, bound and subscribed to"
  retrieve_mapping!
  @@logger.info "Mapping initialized"
end

Class Method Details

.logger=(logger) ⇒ Object

Set a logger to send all log messages to

Parameters:

  • logger (:debug, :info, :warn, :error, :fatal)

    A logger instance.


20
21
22
# File 'lib/blinkbox/common_mapping.rb', line 20

def self.logger=(logger)
  @@logger = logger
end

Instance Method Details

#inspectObject


140
141
142
# File 'lib/blinkbox/common_mapping.rb', line 140

def inspect
  "<Token Mapper: #{@ss.host}>"
end

#open(token) ⇒ Object

Opens a given token and returns an IO object for the associated asset or - if a block is passed - yields with the IO object as the only argument.

Parameters:

  • token (String)

    The token referring to the asset to be opened

Returns:

  • An IO object referencing the object or (if a block was given) the value returned from the block

Raises:

  • InvalidTokenError if the given token string isn't a valid token


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
113
114
# File 'lib/blinkbox/common_mapping.rb', line 83

def open(token)
  raise InvalidTokenError unless valid_token?(token)
  @@logger.debug "Opening #{token}"
  locations = map(token)
  @@logger.debug "Locations for #{token} are: #{locations.inspect}"
  # TODO: We currently assume the first is the best. Later iterations of this library may be more intelligent
  while locations.size > 0
    provider, uri = locations.shift
    @@logger.debug "Trying #{uri} from #{provider}"
    begin
      io = open_uri(URI.parse(uri))
      return io if !block_given?
      returns = yield(io)
      io.close
      return returns
    rescue
      # There was a problem with this provider file, register it and move on to another
      status = retrieve_status(token, provider_failure: provider)
      if status.nil?
        @@logger.warn "Storage service has no status for #{token}."
        break
      end
      available_providers = status['providers'].map { |this_provider, this_status|
        this_status['available'] ? this_provider : nil
      }.compact
      locations.delete_if { |p, u|
        !(status['providers'][p] && status['providers'][p]['available'])
      }
    end
  end
  raise MissingAssetError, "The asset for #{token} could not be downloaded from anywhere."
end

#retrieve_mapping!Boolean

Collects the mappings from the storage service specified when initialised.

Returns:

  • (Boolean)

    Returns true if the mapping file was updated, false if the mapping already stored was the same or more recent.

Raises:

  • StorageServiceUnavailableError if the response from the server isn't 200, isn't JSON or (if the schema are available) isn't a valid mapping document.


128
129
130
131
132
133
134
135
136
137
138
# File 'lib/blinkbox/common_mapping.rb', line 128

def retrieve_mapping!
  response = ss_get("/mappings")
  raise StorageServiceUnavailableError, "Storage service gave #{response.code} response code, cannot update the mapping details." unless response.code == "200"
  mapping = JSON.parse(response.body)
  # This will raise a JSON::Schema::ValidationError if the mapping file isn't valid
  CommonMessaging::MappingUpdateV1.new(mapping) if CommonMessaging.const_defined?('MappingUpdateV1')
  timestamp = response['Date'].nil? ? Time.now : Time.parse(response['Date'])
  update_mapping!(timestamp, mapping)
rescue JSON::ParserError, JSON::Schema::ValidationError
  raise StorageServiceUnavailableError, "The response from the storage service wasn't a valid mapping update."
end

#status(token) ⇒ Object

Gets information about a specific token.

Parameters:

  • token (String)

    The token for the asset to get the status of


119
120
121
122
# File 'lib/blinkbox/common_mapping.rb', line 119

def status(token)
  # Duplicate method so the external API can't register a provider failure
  retrieve_status(token)
end