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.



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

def path
  @path
end

#paths_loaderObject

Returns the value of attribute paths_loader.



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

def paths_loader
  @paths_loader
end

Class Method Details

.from_file(path:, paths_loader:) ⇒ Object



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

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



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

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



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

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).



261
262
263
264
# File 'lib/fontist/system_index.rb', line 261

def disable_read_only_mode
  @read_only_mode = false
  self
end

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



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

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



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

def index
  if @read_only_mode && !fonts.nil?
    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)


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 'lib/fontist/system_index.rb', line 266

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



307
308
309
310
# File 'lib/fontist/system_index.rb', line 307

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



253
254
255
256
# File 'lib/fontist/system_index.rb', line 253

def read_only_mode
  @read_only_mode = true
  self
end

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



343
344
345
# File 'lib/fontist/system_index.rb', line 343

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)



313
314
315
316
317
# File 'lib/fontist/system_index.rb', line 313

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

#set_content(path, paths_loader) ⇒ Object



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

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



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

def set_path(path)
  @path = path
end

#set_path_loader(paths_loader) ⇒ Object



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

def set_path_loader(paths_loader)
  @paths_loader = paths_loader
end

#to_file(path) ⇒ Object



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

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

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



319
320
321
322
323
324
# File 'lib/fontist/system_index.rb', line 319

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