Class: Relaton::Index::FileIO

Inherits:
Object
  • Object
show all
Defined in:
lib/relaton/index/file_io.rb

Overview

File IO class is used to read and write index files. In searh mode url is used to fetch index from external repository and save it to storage. In index mode url should be nil.

Constant Summary collapse

@@file_locks =
{}
@@file_locks_mutex =
Mutex.new

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dir, url, filename, id_keys, pubid_class = nil) ⇒ FileIO

Initialize FileIO

Parameters:

  • dir (String)

    falvor specific local directory in ~/.relaton to store index

  • url (String, Boolean, nil)

    if String then the URL is used to fetch an index from a Git repository

    and save it to the storage (if not exists, or older than 24 hours)
    

    if true then the index is read from the storage (used to remove index file) if nil then the fiename is used to read and write file (used to create indes in GH actions)

  • pubid (Pubid::Core::Identifier::Base)

    class for deserialization



26
27
28
29
30
31
32
33
# File 'lib/relaton/index/file_io.rb', line 26

def initialize(dir, url, filename, id_keys, pubid_class = nil)
  @dir = dir
  @url = url
  @filename = filename
  @id_keys = id_keys || []
  @pubid_class = pubid_class
  @sorted = false
end

Instance Attribute Details

#pubid_classObject (readonly)

Returns the value of attribute pubid_class.



9
10
11
# File 'lib/relaton/index/file_io.rb', line 9

def pubid_class
  @pubid_class
end

#sortedObject

Returns the value of attribute sorted.



10
11
12
# File 'lib/relaton/index/file_io.rb', line 10

def sorted
  @sorted
end

#urlObject (readonly)

Returns the value of attribute url.



9
10
11
# File 'lib/relaton/index/file_io.rb', line 9

def url
  @url
end

Instance Method Details

#check_basic_format(index) ⇒ Object



91
92
93
94
95
96
# File 'lib/relaton/index/file_io.rb', line 91

def check_basic_format(index)
  return false unless index.is_a? Array

  keys = %i[file id]
  index.all? { |item| item.respond_to?(:keys) && item.keys.sort == keys }
end

#check_fileArray<Hash>?

Check if index file exists and is not older than 24 hours

Returns:

  • (Array<Hash>, nil)

    index or nil



73
74
75
76
77
78
# File 'lib/relaton/index/file_io.rb', line 73

def check_file
  ctime = Index.config.storage.ctime(file)
  return unless ctime && ctime > Time.now - 86400

  read_file
end

#check_format(index) ⇒ Boolean

Check if index has correct format

Parameters:

  • index (Array<Hash>)

    index to check

Returns:

  • (Boolean)

    <description>



87
88
89
# File 'lib/relaton/index/file_io.rb', line 87

def check_format(index)
  check_basic_format(index) && check_id_format(index)
end

#check_id_format(index) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/relaton/index/file_io.rb', line 98

def check_id_format(index)
  return true if @id_keys.empty?

  keys = index.each_with_object(Set.new) do |item, acc|
    acc.merge item[:id].keys if item[:id].is_a?(Hash)
  end
  keys.none? { |k| !@id_keys.include? k }
end

#deserialize_pubid(index) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/relaton/index/file_io.rb', line 119

def deserialize_pubid(index)
  return index unless @pubid_class

  @sorted = true
  prev_number = nil
  index.map do |r|
    id = @pubid_class.create(**(r[:id] || {}))
    num = get_id_number id
    @sorted = false if prev_number && prev_number > num
    prev_number = num
    { id: id, file: r[:file] }
  end
end

#fetch_and_saveArray<Hash>

Fetch index from external repository and save it to storage

Returns:

  • (Array<Hash>)

    index



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/relaton/index/file_io.rb', line 170

def fetch_and_save
  uri = URI.parse(url)
  body = Net::HTTP.get(uri)
  yaml = nil
  Zip::File.open_buffer(body) do |zip|
    entry = zip.entries.first
    yaml = entry.get_input_stream.read
  end
  Util.info "Downloaded index from `#{url}`", progname
  load_index(yaml, true)
end

#fileObject



55
56
57
# File 'lib/relaton/index/file_io.rb', line 55

def file
  @file ||= url ? path_to_local_file : @filename
end

#get_id_number(id) ⇒ Object



210
211
212
# File 'lib/relaton/index/file_io.rb', line 210

def get_id_number(id)
  id.respond_to?(:base) && id.base ? id.base.number.to_s : id.number.to_s
end

#load_index(yaml, save = false) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/relaton/index/file_io.rb', line 147

def load_index(yaml, save = false)
  index = YAML.safe_load(yaml, permitted_classes: [Symbol])
  save index if save
  return deserialize_pubid(index) if check_format index

  if save
    warn_remote_index_error "Wrong structure of"
  else
    warn_local_index_error "Wrong structure of"
  end
rescue Psych::SyntaxError
  if save
    warn_remote_index_error "YAML parsing error when reading"
  else
    warn_local_index_error "YAML parsing error when reading"
  end
end

#path_to_local_file<Type>

Create path to local file

Returns:

  • (<Type>)

    <description>



64
65
66
# File 'lib/relaton/index/file_io.rb', line 64

def path_to_local_file
  File.join(Index.config.storage_dir, ".relaton", @dir, @filename)
end

#prognameObject



143
144
145
# File 'lib/relaton/index/file_io.rb', line 143

def progname
  @progname ||= "relaton-#{@dir}"
end

#readArray<Hash>

If url is String, check if index file exists and is not older than 24

hours. If not, fetch index from external repository and save it to
storage.

If url is true, read index from path to local file. If url is nil, read index from filename.

Returns:

  • (Array<Hash>)

    index



44
45
46
47
48
49
50
51
52
53
# File 'lib/relaton/index/file_io.rb', line 44

def read
  case url
  when String
    with_file_lock do
      check_file || fetch_and_save
    end
  else
    read_file || []
  end
end

#read_fileArray<Hash>

Read index from storage

Returns:

  • (Array<Hash>)

    index



112
113
114
115
116
117
# File 'lib/relaton/index/file_io.rb', line 112

def read_file
  yaml = Index.config.storage.read(file)
  return unless yaml

  load_index(yaml) || []
end

#removeArray

Remove index file from storage

Returns:

  • (Array)


219
220
221
222
# File 'lib/relaton/index/file_io.rb', line 219

def remove
  Index.config.storage.remove file
  []
end

#save(index) ⇒ void

This method returns an undefined value.

Save index to storage

Parameters:

  • index (Array<Hash>)

    index to save



195
196
197
198
199
200
# File 'lib/relaton/index/file_io.rb', line 195

def save(index)
  yaml = sort_structured_index(index).map do |item|
    item.transform_values { |value| value.is_a?(Pubid::Core::Identifier::Base) ? value.to_h : value }
  end.to_yaml
  Index.config.storage.write file, yaml
end

#sort_structured_index(index) ⇒ Object



202
203
204
205
206
207
208
# File 'lib/relaton/index/file_io.rb', line 202

def sort_structured_index(index)
  if @pubid_class && index.first&.dig(:id).is_a?(Pubid::Core::Identifier::Base)
    index.sort_by { |item| get_id_number item[:id] }
  else
    index
  end
end

#warn_local_index_error(reason) ⇒ Object



133
134
135
136
137
138
139
140
141
# File 'lib/relaton/index/file_io.rb', line 133

def warn_local_index_error(reason)
  Util.info "#{reason} file `#{file}`", progname
  if url.is_a? String
    Util.info "Considering `#{file}` file corrupt, re-downloading from `#{url}`", progname
  else
    Util.info "Considering `#{file}` file corrupt, removing it.", progname
    remove
  end
end

#warn_remote_index_error(reason) ⇒ Object



182
183
184
185
186
# File 'lib/relaton/index/file_io.rb', line 182

def warn_remote_index_error(reason)
  Util.info "#{reason} newly downloaded file `#{file}` at `#{url}`, " \
       "the remote index seems to be invalid. Please report this " \
       "issue at https://github.com/relaton/relaton-cli.", progname
end