lib/miga/metadata.rb
# @package MiGA # @license Artistic-2.0 ## # Metadata associated to objects like MiGA::Project, MiGA::Dataset, and # MiGA::Result. class MiGA::Metadata < MiGA::MiGA # Class-level ## # Does the metadata described in +path+ already exist? def self.exist?(path) File.exist? path end ## # Load the metadata described in +path+ and return MiGA::Metadata if it # exists, or nil otherwise. def self.load(path) return nil unless Metadata.exist? path MiGA::Metadata.new(path) end # Instance-level ## # Path to the JSON file describing the metadata attr_reader :path ## # Initiate a MiGA::Metadata object with description in +path+. # It will create it if it doesn't exist. def initialize(path, defaults = {}) @data = nil @path = File.absolute_path(path) unless File.exist? path @data = {} defaults.each { |k, v| self[k] = v } create end end ## # Parsed data as a Hash def data self.load if @data.nil? @data end ## # Reset :created field and save the current data def create self[:created] = Time.now.to_s save end ## # Save the metadata into #path def save return if self[:never_save] MiGA::MiGA.DEBUG "Metadata.save #{path}" self[:updated] = Time.now.to_s json = to_json wait_for_lock FileUtils.touch(lock_file) ofh = File.open("#{path}.tmp", 'w') ofh.puts json ofh.close unless File.exist?("#{path}.tmp") && File.exist?(lock_file) raise "Lock-racing detected for #{path}" end File.rename("#{path}.tmp", path) File.unlink(lock_file) end ## # (Re-)load metadata stored in #path def load sleeper = 0.0 while File.exist? lock_file sleeper += 0.1 if sleeper <= 10.0 sleep(sleeper.to_i) end tmp = MiGA::Json.parse(path, additions: true) @data = {} tmp.each { |k, v| self[k] = v } end ## # Delete file at #path def remove! MiGA.DEBUG "Metadata.remove! #{path}" File.unlink(path) nil end ## # Lock file for the metadata def lock_file "#{path}.lock" end ## # Return the value of +k+ in #data def [](k) if k.to_s =~ /(.+):(.+)/ data[$1.to_sym]&.fetch($2) else data[k.to_sym] end end ## # Set the value of +k+ to +v+ def []=(k, v) self.load if @data.nil? k = k.to_sym return @data.delete(k) if v.nil? case k when :name # Protect the special field :name v = v.miga_name when :type # Symbolize the special field :type v = v.to_sym if k == :type end @data[k] = v end ## # Iterate +blk+ for each data with 2 arguments: key and value def each(&blk) data.each { |k, v| blk.call(k, v) } end ## # Time of last update def updated Time.parse(self[:updated]) unless self[:updated].nil? end ## # Time of creation def created Time.parse(self[:created]) unless self[:created].nil? end ## # Show contents in JSON format as a String def to_json MiGA::Json.generate(data) end private ## # Wait for the lock to go away def wait_for_lock sleeper = 0.0 slept = 0.0 while File.exist?(lock_file) MiGA::MiGA.DEBUG "Waiting for lock: #{lock_file}" sleeper += 0.1 if sleeper <= 10.0 sleep(sleeper) slept += sleeper raise "Lock detected for over 10 minutes: #{lock_file}" if slept > 600 end end end