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



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/fontist/system_index.rb', line 320

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

#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
# 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 if fonts is nil (first time access)
  if @read_only_mode && !fonts.nil?
    return fonts
    # Fall through to build the index on first access
  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)


260
261
262
263
264
265
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
# File 'lib/fontist/system_index.rb', line 260

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



301
302
303
304
# File 'lib/fontist/system_index.rb', line 301

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



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

def read_only_mode
  @read_only_mode = true
  self
end

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



337
338
339
# File 'lib/fontist/system_index.rb', line 337

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)



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

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



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

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