Class: Fontist::SystemIndexFontCollection

Inherits:
Lutaml::Model::Collection
  • Object
show all
Includes:
Utils::Locking
Defined in:
lib/fontist/system_index.rb

Constant Summary collapse

INDEX_REBUILD_THRESHOLD =

Don’t rebuild index more frequently than this (in seconds)

30 * 60
ALLOWED_KEYS =
%i[path full_name family_name type].freeze
OPTIONAL_KEYS =

Optional metadata keys for optimization (not required for validity)

%i[file_size file_mtime].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::Locking

#lock

Instance Attribute Details

#pathObject

Returns the value of attribute path.



146
147
148
# File 'lib/fontist/system_index.rb', line 146

def path
  @path
end

#paths_loaderObject

Returns the value of attribute paths_loader.



146
147
148
# File 'lib/fontist/system_index.rb', line 146

def paths_loader
  @paths_loader
end

Class Method Details

.from_file(path:, paths_loader:) ⇒ Object



169
170
171
172
173
174
# File 'lib/fontist/system_index.rb', line 169

def self.from_file(path:, paths_loader:)
  # If the file does not exist, return a new collection
  return new.set_content(path, paths_loader) unless File.exist?(path)

  from_yaml(File.read(path)).set_content(path, paths_loader)
end

Instance Method Details

#build(forced: false, verbose: false, stats: nil) ⇒ Object



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/fontist/system_index.rb', line 329

def build(forced: false, verbose: false, stats: nil)
  if forced
    return rebuild_with_lock(verbose: verbose, stats: stats)
  end

  previous_index = load_index
  updated_fonts = update

  if changed?(updated_fonts, previous_index.fonts || [])
    # Store the updated fonts so we don't need to call update again
    @pending_fonts = updated_fonts.fonts
    rebuild_with_lock(verbose: verbose, stats: stats)
  end

  self
end

#check_indexObject

Check if the content has all required keys



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/fontist/system_index.rb', line 189

def check_index
  Fontist.formulas_repo_path_exists!

  Array(fonts).each do |font|
    missing_keys = ALLOWED_KEYS.reject do |key|
      font.send(key)
    end

    raise_font_index_corrupted(font, missing_keys) if missing_keys.any?
  end
end

#disable_read_only_modeObject

Disable read-only mode so future ‘index` calls go through `index_changed?` again. Paired with `read_only_mode` to scope the optimization to a single block (see Manifest.with_performance_optimizations).



264
265
266
267
# File 'lib/fontist/system_index.rb', line 264

def disable_read_only_mode
  @read_only_mode = false
  self
end

#find(font, style, format_spec: nil) ⇒ Object



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/fontist/system_index.rb', line 206

def find(font, style, format_spec: nil)
  current_fonts = index

  return nil if current_fonts.nil? || current_fonts.empty?

  if style.nil?
    found_fonts = current_fonts.select do |file|
      file.family_name&.casecmp?(font)
    end
  else
    found_fonts = current_fonts.select do |file|
      file.family_name&.casecmp?(font) && file.type&.casecmp?(style)
    end
  end

  # Apply format filtering if specified
  if format_spec&.has_constraints? && found_fonts
    require_relative "format_matcher"
    matcher = FormatMatcher.new(format_spec)
    found_fonts = matcher.filter_indexed_fonts(found_fonts)
  end

  found_fonts.empty? ? nil : found_fonts
end

#indexObject



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/fontist/system_index.rb', line 231

def index
  # Fast path: if read_only mode is set, skip index_changed? check entirely.
  # But we still need to build on first access — treat an empty fonts list
  # the same as nil, since Lutaml initializes `instances :fonts` to `[]`
  # rather than nil when the on-disk index file is absent.
  if @read_only_mode && !fonts.nil? && !fonts.empty?
    return fonts
  end

  return fonts unless index_changed?

  # Notify user about index rebuild for large collections
  paths = @paths_loader&.call || []
  if (paths.size > 100) && !@verbose
    Fontist.ui.say("Building font index (#{paths.size} fonts found, this may take a while...)")
  end

  build
  check_index

  fonts
end

#index_changed?Boolean

Returns:

  • (Boolean)


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
304
305
306
# File 'lib/fontist/system_index.rb', line 269

def index_changed?
  return true if fonts.nil? || fonts.empty?
  return false if @index_check_done # Skip if already verified in this session

  # Quick check: if index was scanned recently, trust it
  if recently_scanned?
    Fontist.ui.debug("System index scanned #{time_since_scan} seconds ago, skipping check")
    @index_check_done = true
    return false
  end

  # Quick check: if directories haven't changed, trust the index
  if directories_unchanged?
    Fontist.ui.debug("Font directories unchanged, skipping full scan")
    @index_check_done = true
    # Update last scan time to extend validity
    self.last_scan_time = Time.now.to_i
    
    return false
  end

  # At this point we need to do a full scan
  Fontist.ui.debug("Font directories changed or stale, performing full scan")

  # Cache the paths loader results to avoid repeated Dir.glob calls
  @cached_current_paths ||= @paths_loader&.call&.sort&.uniq || []

  excluded_paths_in_current = @cached_current_paths.select do |path|
    excluded?(path)
  end

  changed = @cached_current_paths != (font_paths + excluded_paths_in_current).uniq.sort

  # Mark as verified if unchanged, so we don't check again in this session
  @index_check_done = true unless changed

  changed
end

#mark_verified!Object

Mark this index as verified, skipping future index_changed? checks Used after successfully loading the index from file



310
311
312
313
# File 'lib/fontist/system_index.rb', line 310

def mark_verified!
  @index_check_done = true
  self
end

#read_only_modeObject

Enable read-only mode for operations that don’t need index rebuilding This is used during manifest compilation to avoid expensive index checks



256
257
258
259
# File 'lib/fontist/system_index.rb', line 256

def read_only_mode
  @read_only_mode = true
  self
end

#rebuild(verbose: false, stats: nil) ⇒ Object



346
347
348
# File 'lib/fontist/system_index.rb', line 346

def rebuild(verbose: false, stats: nil)
  build(forced: true, verbose: verbose, stats: stats)
end

#reset_verification!Object

Reset verification flag (for testing or forcing re-check)



316
317
318
319
320
# File 'lib/fontist/system_index.rb', line 316

def reset_verification!
  @index_check_done = false
  @cached_current_paths = nil
  self
end

#set_content(path, paths_loader) ⇒ Object



176
177
178
179
180
181
# File 'lib/fontist/system_index.rb', line 176

def set_content(path, paths_loader)
  tap do |content|
    content.set_path(path)
    content.set_path_loader(paths_loader)
  end
end

#set_path(path) ⇒ Object

30 minutes



161
162
163
# File 'lib/fontist/system_index.rb', line 161

def set_path(path)
  @path = path
end

#set_path_loader(paths_loader) ⇒ Object



165
166
167
# File 'lib/fontist/system_index.rb', line 165

def set_path_loader(paths_loader)
  @paths_loader = paths_loader
end

#to_file(path) ⇒ Object



201
202
203
204
# File 'lib/fontist/system_index.rb', line 201

def to_file(path)
  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, to_yaml)
end

#update(verbose: false, stats: nil) ⇒ Object



322
323
324
325
326
327
# File 'lib/fontist/system_index.rb', line 322

def update(verbose: false, stats: nil)
  tap do |col|
    col.fonts = detect_paths(@paths_loader&.call || [], verbose: verbose,
                                                        stats: stats)
  end
end