class Aspire::Caching::CacheEntry

Represents an entry in the cache

Attributes

cache[RW]

@!attribute [rw] cache

@return [Aspire::Caching::Cache] the cache
json_api_opt[RW]

:!attribute [rw] draft @!attribute [rw] json_api_opt

@return [Hash] #call parameters for the JSON API call
json_api_url[RW]

@!attribute [rw] json_api_url

@return [String] the JSON API #call URL
parsed_url[RW]

@!attribute [rw] uri

@return [MatchData] the parsed URL
url[RW]

@!attribute [rw] url

@return [String] the URL

Public Class Methods

new(url, cache) click to toggle source

Initialises a new CacheEntry instance @param url [String] the URL of the API object @param cache [Aspire::Caching::Cache] the parent cache @return [void] @raise [Aspire::Exceptions::NotCacheable] if the URL is not

cacheable
# File lib/aspire/caching/cache_entry.rb, line 40
def initialize(url, cache)
  self.cache = cache
  self.url = url
end

Public Instance Methods

==(other) click to toggle source

Returns true if cache entries refer to the same object @param other [Aspire::Caching::CacheEntry, String] a cache entry or URL @return [Boolean] true if the entries refer to the same object

# File lib/aspire/caching/cache_entry.rb, line 48
def ==(other)
  url == url_for_comparison(other, cache.ld_api)
end
cached?(json = false) click to toggle source

Returns true if the object is in the cache, false if not @return [Boolean] true if the object is cached, false if not

# File lib/aspire/caching/cache_entry.rb, line 64
def cached?(json = false)
  filename = json ? json_file : file
  filename.nil? ? nil : File.exist?(filename)
end
child_of?(url, strict: false) click to toggle source

Returns true if this cache entry is a child of the URL @param url [Aspire::Caching::CacheEntry, String] the URL to test @param strict [Boolean] if true, the URL must be a parent of this entry,

otherwise the URL must be a parent or the same as this entry

@return [Boolean] true if the URL is a child of the cache entry, false

otherwise
# File lib/aspire/caching/cache_entry.rb, line 58
def child_of?(url, strict: false)
  child_url?(parsed_url, url, cache.ld_api, strict: strict)
end
delete(force: false, remove_children: false) click to toggle source

Deletes the object from the cache @param force [Boolean] delete even if the entry is marked in-progress @param remove_children [Boolean] if true, remove children of the object

as well as the object, otherwise remove just the object

@return [void] @raise [Aspire::Exceptions::MarkedError] if the entry is

marked in-progress and force = false
# File lib/aspire/caching/cache_entry.rb, line 76
def delete(force: false, remove_children: false)
  mark(force: force) { |_f| delete_entry(file, remove_children) }
end
file() click to toggle source

Returns the linked data filename in the cache @return [String] the linked data filename in the cache

# File lib/aspire/caching/cache_entry.rb, line 82
def file
  File.join(cache.path, url_path)
end
json?() click to toggle source

Returns true if the object has associated JSON API data, false if not @return [Boolean] true if the object has associated JSON API data, false

otherwise
# File lib/aspire/caching/cache_entry.rb, line 89
def json?
  !json_api_url.nil? && !json_api_url.empty?
end
json_file(filename = nil) click to toggle source

Returns the JSON API data filename in the cache or nil if there is no JSON API data for the URL @param filename [String] the linked data filename in the cache @return [String, nil] the JSON API data filename or nil if there is no

JSON API data for the URL
# File lib/aspire/caching/cache_entry.rb, line 98
def json_file(filename = nil)
  json? ? add_filename_suffix(filename || file, '-json') : nil
end
list?(strict: true) click to toggle source

Returns true if the cache entry is a list, false otherwise @param strict [Boolean] if true, the cache entry must be a list,

otherwise the cache entry must be a list or a child of a list

@return [Boolean] true if the cache entry is a list, false otherwise

# File lib/aspire/caching/cache_entry.rb, line 106
def list?(strict: true)
  # The cache entry must be a list or the child of a list
  return false unless parsed_url[:type] == 'lists'
  # Strict checking requires that the cache entry is a list, not a child
  return false if strict && !parsed_url[:child_type].nil?
  true
end
mark(force: false, &block) click to toggle source

Marks the cache entry as in-progress @param force [Boolean] if true, do not raise MarkedError when the entry

is already marked; otherwise, MarkedError is raised when the entry is
already marked.

@return [void] @yield [file] passes the opened status file to the block @yieldparam file [File] the opened status file @raise [Aspire::Exceptions::MarkError] if the operation failed @raise [Aspire::Exceptions::MarkedError] if the cache entry is

already marked
# File lib/aspire/caching/cache_entry.rb, line 124
def mark(force: false, &block)
  filename = status_file
  flags = File::CREAT
  flags |= File::EXCL unless force
  File.open(filename, flags, &block)
rescue Errno::EEXIST
  raise MarkedError, "#{url} already marked [#{filename}]"
rescue SystemCallError => e
  raise MarkError, "#{url} mark failed [#{filename}]: #{e}"
end
marked?() click to toggle source

Returns true if the cache entry is locked @return [Boolean] true if the cache entry is marked as in-progress,

false otherwise
# File lib/aspire/caching/cache_entry.rb, line 138
def marked?
  File.exist?(status_file)
end
parent_of?(url, strict: false) click to toggle source

Returns true if this cache entry is the parent of the URL @param url [Aspire::Caching::CacheEntry, String] the URL to test @param strict [Boolean] if true, the URL must be a parent of this entry,

otherwise the URL must be a parent or the same as this entry

@return [Boolean] true if this cache entry is the parent of the URL,

false otherwise
# File lib/aspire/caching/cache_entry.rb, line 148
def parent_of?(url, strict: false)
  parent_url?(parsed_url, url, cache.ld_api, strict: strict)
end
path(json = false) click to toggle source

Returns the filename of the cache entry @param json [Boolean] if true, returns the JSON API filename, otherwise

returns the linked data API filename
# File lib/aspire/caching/cache_entry.rb, line 155
def path(json = false)
  json ? json_file : file
end
read(json = false, parsed: false) click to toggle source

Returns data from the cache @param json [Boolean] if true, read the JSON API file, otherwise read

the linked data API file

@param parsed [Boolean] if true, return JSON-parsed data, otherwise

return a JSON string

@return [Array, Hash, String, nil] the parsed JSON data or JSON string,

or nil if JSON API data is requested but not available for this entry

@raise [Aspire::Exceptions::CacheMiss] when the data is not in the cache @raise [Aspire::Exceptions::ReadError] when the read operation fails

# File lib/aspire/caching/cache_entry.rb, line 168
def read(json = false, parsed: false)
  filename = json ? json_file : file
  return nil if filename.nil? || filename.empty?
  File.open(filename, 'r') do |f|
    data = f.read
    return parsed ? JSON.parse(data) : data
  end
rescue Errno::ENOENT
  raise CacheMiss, "#{url} cache miss [#{filename}"
rescue IOError, SystemCallError => e
  raise ReadError, "#{url} cache read failed [#{filename}]: #{e}"
end
references?() click to toggle source

Returns true if the object’s references are cacheable @return [Boolean] true if the object’s references are cacheable, false

otherwise
# File lib/aspire/caching/cache_entry.rb, line 184
def references?
  # Events are not JSON-LD so we can't cache references
  parsed_url[:type] != 'events' && parsed_url[:child_type] != 'events'
end
status_file(filename = nil) click to toggle source

Returns the status filename in the cache @param filename [String] the linked data filename in the cache

# File lib/aspire/caching/cache_entry.rb, line 191
def status_file(filename = nil)
  # Prepend '.' to the filename
  add_filename_prefix(filename || file, '.')
end
to_s() click to toggle source

Returns a string representation of the cache entry @return [String] the string representation (URL) of the cache entry

# File lib/aspire/caching/cache_entry.rb, line 198
def to_s
  url
end
unmark() click to toggle source

Removes an in-progress mark from the cache entry

# File lib/aspire/caching/cache_entry.rb, line 203
def unmark
  filename = status_file
  File.delete(filename) if File.exist?(filename)
rescue SystemCallError => e
  raise UnmarkError, "#{url} unmark failed [#{filename}]: #{e}"
end
url=(u) click to toggle source

Sets the URL and associated flags @param u [String] the URL of the API object @return [void] @raise [Aspire::Exceptions::NotCacheable] if the URL is not

cacheable
# File lib/aspire/caching/cache_entry.rb, line 215
def url=(u)
  # Convert the URL to canonical form for comparison
  u = cache.canonical_url(u)
  # Parse and check the URL
  # - this will raise NotCacheable if it is not a valid cacheable URL
  self.parsed_url = cacheable_url(u)
  # Set the URL properties
  @url = u
  return unless list_url?(parsed: parsed_url)
  self.json_api_opt = { bookjacket: 1, editions: 1, draft: 1, history: 1 }
  self.json_api_url = "lists/#{strip_ext(parsed_url[:id])}"
end
write(data, json = false, parsed: false) click to toggle source

Writes data to the cache @param data [Object] the data to write to the cache @param json [Boolean] if true, write the data as JSON API data,

otherwise write it as linked data

@param parsed [Boolean] if true, treat data as a parsed JSON data

structure, otherwise treat it as a JSON string

@return [void] @raise [Aspire::Exceptions::WriteError] when the write operation fails

# File lib/aspire/caching/cache_entry.rb, line 236
def write(data, json = false, parsed: false)
  filename = json ? json_file : file
  return if filename.nil? || filename.empty?
  # Create the path to the file
  FileUtils.mkdir_p(File.dirname(filename), mode: cache.mode)
  # Write the data
  File.open(filename, 'w') do |f|
    f.flock(File::LOCK_EX)
    f.write(parsed ? JSON.generate(data) : data)
  end
rescue IOError, JSON::JSONError, SystemCallError => e
  raise WriteError, "#{url} cache write failed [#{filename}]: #{e}"
end

Private Instance Methods

delete_children(filename) click to toggle source

Deletes children of the cache entry @param filename [String] the linked data API filename @return [nil] @raise [Aspire::Exceptions::RemoveError] if the operation fails

# File lib/aspire/caching/cache_entry.rb, line 256
def delete_children(filename)
  # Child objects of the cache entry are stored in a directory with the
  # same name as the linked data cache file without the '.json' extension
  children = "#{strip_ext(filename)}/*"
  return unless children.nil? || children.empty? || children == '/*'
  FileUtils.rm_rf(Dir.glob(children), secure: true)
rescue SystemCallError => e
  raise RemoveError, "#{url} remove failed [#{children}]: #{e}"
end
delete_entry(filename, remove_children = false) click to toggle source

Deletes the files for the cache entry and removes any empty directories on the cache file’s path @param filename [String] the linked data filename in the cache @param remove_children [Boolean] @return [nil]

# File lib/aspire/caching/cache_entry.rb, line 271
def delete_entry(filename, remove_children = false)
  # Delete the files for the cache entry
  delete_file(filename)
  delete_file(json_file(filename))
  delete_file(status_file(filename))
  delete_children(filename) if remove_children
  # Delete any empty directories on the entry's file path
  rmdir_empty(filename, cache.path)
end
delete_file(filename) click to toggle source

Deletes the specified file @param filename [String] the filename to delete @return [void] @raise [Aspire::Exceptions::RemoveError] if the delete fails

for any reason other than the file not existing
# File lib/aspire/caching/cache_entry.rb, line 286
def delete_file(filename)
  File.delete(filename) unless filename.nil? || filename.empty?
rescue Errno::ENOENT
  # Ignore file-does-not-exist errors
  nil
rescue SystemCallError => e
  raise RemoveError, "#{url} remove failed [#{filename}]: #{e}"
end