Class: FontProcessor::ProcessFontJob

Inherits:
Object
  • Object
show all
Defined in:
lib/fontprocessor/process_font_job.rb

Class Method Summary collapse

Class Method Details

.clientObject

Returns a configured FontStore client.



102
103
104
105
106
107
108
# File 'lib/fontprocessor/process_font_job.rb', line 102

def self.client
  @client ||= FontBase::Client.new do |c|
    c.connection = Mongo::Connection.new(Config.mongo_host)
    c.db_name = Config.mongo_db
    c.s3_bucket = Config.s3_source_bucket
  end
end

.fetch_files(font_base_id, temporary_directory) ⇒ Object

Retrieves files from the FontBase and saves them to a temporary local directory.

font_base_id - The FontBase id of the font to fetch. temporary_direction - The full path to store the retrieved files. The

client is responsible for cleanup of the directory
after use.

Returns nothing. Raises FontBase::TransientError if there was an error retrieving the

files from the FontBase.


72
73
74
75
76
77
78
# File 'lib/fontprocessor/process_font_job.rb', line 72

def self.fetch_files(font_base_id, temporary_directory)
  files = client.get_files(font_base_id)
  files.each do |outline_type, data|
    filename = File.join(temporary_directory, outline_type_to_original_filename(outline_type))
    File.open(filename, "wb") { |f| f.write(data) }
  end
end

.log(font_base_id, charset_id, msg) ⇒ Object



223
224
225
# File 'lib/fontprocessor/process_font_job.rb', line 223

def self.log(font_base_id, charset_id, msg)
  Config.logger.puts "[#{font_base_id}:#{charset_id}:#{DateTime.now.strftime("%H:%M:%S.%L")}] #{msg}"
end

.outline_type_to_original_filename(outline_type) ⇒ Object

Creates a suitable temporary filename for the original font files retrieved from the fontbase.

Note: This can’t use cff as an extension because fontforge doesn’t know how to open it properly.

outline_type - either “cff” or “ttf”

Returns a filename name for a given outline type. Raises ArgumentError if the outline_type contains an invalid value.



90
91
92
93
94
95
96
97
98
99
# File 'lib/fontprocessor/process_font_job.rb', line 90

def self.outline_type_to_original_filename(outline_type)
  case outline_type
  when "cff"
    "original.otf"
  when "ttf"
    "original.ttf"
  else
    raise ArgumentError, "#{outline_type} must be either 'cff' or 'ttf'"
  end
end

.perform(status_key, json_request) ⇒ Object

status_key - The redis key to update with the status of this task json_request - A String or Hash of the fully formed JSON request. See FontProcessor::WebRequestJSON.



9
10
11
12
13
14
15
16
17
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
# File 'lib/fontprocessor/process_font_job.rb', line 9

def self.perform(status_key, json_request)
  json_request = JSON.parse(json_request) unless json_request.is_a? Hash

  # For now, we only handle web requests
  begin
    json_request['request_type'] == "web_processing_request" or raise "Invalid request type! json_request['request_type'] = #{json_request['request_type']}"

    charset_data = json_request['charset'] or raise "No charset in JSON Request"
    charset_id = charset_data['charset_id'] or raise "No charset_id set in JSON Request's charset"
    # check unicode and features, but don't assign to a var here
    charset_data['unicode'] or raise "No unicode set in JSON Request's charset"
    charset_data['features'] or raise "No features set in JSON Request's charset"

    font_base_id = json_request['font_base_id'] or raise "No font_base_id set on JSON Request"
    fpv = json_request['fpv'] or raise "No fpv set on JSON Request"

    formats = json_request['formats']
    formats.has_key?('convert') or raise "No conversion set on JSON Request formats block"
    formats['derivatives'] or raise "No derivatives set on JSON Request formats block"
    formats['process_original'] or raise "No process_original value set on JSON Request formats block"
  rescue
    report_failure(status_key)
    raise
  end

  log(font_base_id, charset_id, "Starting processing job...")

  temporary_directory = "/tmp/#{font_base_id}-#{charset_id}"
  FileUtils.mkdir_p(temporary_directory)


  begin
    raise InvalidArgument, "charset_id is #{charset_id}, but should be one of 1, 2 or 3" unless charset_id == "1" || charset_id == "2" || charset_id == "3"

    fetch_files(font_base_id, temporary_directory)
    log(font_base_id, charset_id, "Files fetched")
     = process_files(font_base_id, charset_data, formats, temporary_directory)
    log(font_base_id, charset_id, "Files processed")
    (fpv, font_base_id, )
    log(font_base_id, charset_id, "Metadata uploaded")
    upload_files(fpv, font_base_id, charset_id, formats, temporary_directory)
    log(font_base_id, charset_id, "Files uploaded")
    report_success(status_key)
  rescue => e
    report_failure(status_key)
    log(font_base_id, charset_id, e.message)
    raise e
  ensure
    FileUtils.rm_rf(temporary_directory)
  end
end

.process_files(font_base_id, charset_data, formats, directory, lock = true) ⇒ Object

Processes local files using a Processor and saves metadata to FontBase, if it doesn’t already exist.

font_base_id - The FontBase id of the font to fetch. charset_data - A JSON blob containing the complete definition for the charset

including charset_id, features and unicode list.

formats - A JSON blob containing the formats to process

including process_orignal, convert and a derivatives array.

directory - Local directory of files to process. lock - (Testing only) boolean to determine if the raw files should

be locked or not. Useful to disable if you want to inspect
the results.

Returns a FontProcessor::FontFile object that can be used to retrieve the

processed font's metadata.

Raises Exception if an underlying external tool encounters an error while

processing a file.


127
128
129
130
131
132
133
134
135
136
# File 'lib/fontprocessor/process_font_job.rb', line 127

def self.process_files(font_base_id, charset_data, formats, directory, lock=true)
  filename = select_preferred_original(directory)
  naming_strategy = FontFileNamingStrategy.new(filename, directory)

  processor = FontProcessor::Processor.new(naming_strategy, "http://typekit.com/eulas/#{font_base_id}", font_base_id)

  processor.generate_char_set(charset_data, formats, lock)

  processor.(formats)
end

.redisObject

Returns the current redis connection



199
200
201
# File 'lib/fontprocessor/process_font_job.rb', line 199

def self.redis
  Resque.redis.instance_variable_get("@redis")
end

.report_failure(key) ⇒ Object

Updates the given redis key with the failure state

key - The redis key to set

Returns nothing.



218
219
220
221
# File 'lib/fontprocessor/process_font_job.rb', line 218

def self.report_failure(key)
  redis.set(key, "error")
  redis.expire(key, 30)
end

.report_success(key) ⇒ Object

Updates the given redis key with the success state

key - The redis key to set

Returns nothing.



208
209
210
211
# File 'lib/fontprocessor/process_font_job.rb', line 208

def self.report_success(key)
  redis.set(key, "success")
  redis.expire(key, 24*60*60)
end

.select_preferred_original(directory) ⇒ Object

This method fundamentally shouldn’t exist, however until we convert our processing pipeline to deal with both PostScript and TrueType files we can’t do anything about it.

If a file with PostScript outlines exists, use that one. If it does not exist, use the TrueType one. If neither exist raise an exception.

Returns the filename to use as the source for our processing pipeline. Raises MissingFilesException if neither a TrueType or PostScript font are

found.


167
168
169
170
171
172
# File 'lib/fontprocessor/process_font_job.rb', line 167

def self.select_preferred_original(directory)
  files = Dir.glob(File.join(directory, "*")).map { |f| File.basename(f) }
  return "original.otf" if files.include?("original.otf")
  return "original.ttf" if files.include?("original.ttf")
  raise MissingFilesException, "Neither a TrueType or PostScript original font was found in #{directory.inspect}:#{files.inspect}"
end

.upload_files(fpv, font_base_id, charset_id, formats, directory) ⇒ Object

Iterates over the processed files and uploads them to S3

fpv - A String identifier for the FontProcessingVersion font_base_id - The FontBase unique id for the font to be processed. charset_id - The integer representing the character set contained within

this font. 1 is all, 2 is default and 3 is upper-and-lower.

formats - A JSON blob containing the formats to process

including process_orignal, convert and a derivatives array.

directory - Local directory of files to process.

Returns nothing. Raises Aws::S3::Errors::ServiceError if there is an error transferring a file to

Amazon.

Raises FontProcessor::MissingFilesException if an expected file failed to

be created.


189
190
191
192
193
194
195
196
# File 'lib/fontprocessor/process_font_job.rb', line 189

def self.upload_files(fpv, font_base_id, charset_id, formats, directory)
  iterator = FontProcessor::ProcessedFontIterator.new(font_base_id, charset_id, fpv, directory, formats['convert'])
  iterator.each do |file, s3_key|
    Config.s3_client.put_object(key: s3_key,
                                body: File.binread(file),
                                bucket: Config.s3_processed_bucket)
  end
end

.upload_metadata(fpv, font_base_id, metadata) ⇒ Object

Upload metadata from a processed font file to fontbase using the client.

fpv - A String identifier for the FontProcessingVersion font_base_id - The FontBase id of the font to upload metadata for. metadata - A FontProcessor::FontFile object containing the metatdata

to upload.

Returns nothing.



146
147
148
149
150
151
152
153
154
155
# File 'lib/fontprocessor/process_font_job.rb', line 146

def self.(fpv, font_base_id, )
  client.set_font_processing_version_attributes(font_base_id, fpv, {
    :glyphs => .unicode,
    :metrics => .metrics,
    :names => .names,
    :glyph_count => .glyph_count,
    :optical_size => .optical_size,
    :features => .features
  })
end