class Geminabox::Store::S3

Constants

SPLICEABLE_GZIPPED_FILES
SPLICEABLE_TEXT_FILES

Attributes

logger[R]

Public Class Methods

new(bucket: nil, lock_manager: lock_manager, file_store: Geminabox::GemStore, logger: Logger.new(STDOUT)) click to toggle source
# File lib/geminabox/store/s3.rb, line 19
def initialize(bucket: nil, lock_manager: lock_manager, file_store: Geminabox::GemStore, logger: Logger.new(STDOUT))
  @bucket = bucket
  @file_store = file_store
  @lock_manager = lock_manager
  @logger = logger
end

Public Instance Methods

create(gem, overwrite = false) click to toggle source
# File lib/geminabox/store/s3.rb, line 26
def create(gem, overwrite = false)
  @file_store.create gem, overwrite

  object_name = gem_object_name("/gems/" + gem.name)

  logger.info "Gem: local -> S3 #{object_name}"

  @bucket
    .objects[object_name]
    .write gem.gem_data
  update_metadata
end
delete(path_info) click to toggle source

Note: deleting doesn't make much sense in this case anyway, as other instances will continue serving cached copies of this gem (there's no way to notify them that gem has been deleted)

Do consider using Geminabox.allow_delete = false

# File lib/geminabox/store/s3.rb, line 45
def delete(path_info)
  @file_store.delete path_info

  @bucket.objects[gem_object_name(path_info)].delete
end
reindex(&block) click to toggle source
# File lib/geminabox/store/s3.rb, line 83
def reindex &block
  FileUtils.mkpath @file_store.local_path "gems"
  @bucket.objects.with_prefix("gems/").each do |object|
    path_info = "/" + object.key
    local_file_path = @file_store.local_path path_info

    file_does_not_exist = !File.exists?(local_file_path)

    # File.size raises an exception if file does not exist
    file_size = file_does_not_exist ? 0 : File.size(local_file_path)
    file_has_different_size = object.content_length != file_size

    if file_does_not_exist || file_has_different_size
      logger.info "Gem: S3 -> local #{local_file_path}"
      File.write local_file_path, object.read
    end
  end

  @file_store.reindex(&block)
end
update_local_file(path_info) click to toggle source
# File lib/geminabox/store/s3.rb, line 51
def update_local_file(path_info)
  gem_file = @file_store.local_path path_info

  unless File.exists? gem_file
    gem_object = @bucket.objects[gem_object_name(path_info)]
    if gem_object.exists?
      # Note: this will load the entire contents of the gem into
      # memory  We  might  switch   to  using  streaming  IO  or
      # temporary files  if this  proves to  be critical  in our
      # environment
      io = StringIO.new gem_object.read
      incoming_gem = Geminabox::IncomingGem.new io
      @file_store.create incoming_gem

      update_metadata
    end
  end

  @file_store.update_local_file path_info
end
update_local_metadata_file(path_info) click to toggle source
# File lib/geminabox/store/s3.rb, line 72
def update_local_metadata_file(path_info)
  file_name = File.basename path_info
  pull_file file_name do |local, remote|
    if file_name =~ /\.gz$/
      merge_gzipped local, remote
    else
      merge_text local, remote
    end
  end
end

Private Instance Methods

gem_object_name(path_info) click to toggle source
# File lib/geminabox/store/s3.rb, line 160
def gem_object_name(path_info)
  # Remove loading slash from the path
  path_info[1..-1]
end
merge_file_with_remote(file_name) { |old_contents, read| ... } click to toggle source
# File lib/geminabox/store/s3.rb, line 124
def merge_file_with_remote file_name
  local_index_file = @file_store.local_path file_name
  if File.exists? local_index_file
    old_contents = File.read(local_index_file, open_args: ["rb"])
  else
    old_contents = ''
  end

  object = s3_object(file_name)
  unless object.exists?
    old_contents
  else
    yield old_contents, object.read
  end
end
merge_gzipped(a, b) click to toggle source
# File lib/geminabox/store/s3.rb, line 169
def merge_gzipped(a, b)
  package(unpackage(a) | unpackage(b))
end
merge_text(a, b) click to toggle source
# File lib/geminabox/store/s3.rb, line 173
def merge_text(a, b)
  a.to_s + b.to_s
end
metadata_object_name(path_info) click to toggle source
# File lib/geminabox/store/s3.rb, line 165
def metadata_object_name(path_info)
  path_info.gsub %r{/}, 'metadata/'
end
package(content) click to toggle source
# File lib/geminabox/store/s3.rb, line 181
def package(content)
  Gem.gzip(Marshal.dump(content))
end
pull_file(file_name, &block) click to toggle source
# File lib/geminabox/store/s3.rb, line 152
def pull_file file_name, &block
  logger.info "Pull: S3 -> local #{file_name}"

  new_contents = merge_file_with_remote file_name, &block
  file_path = @file_store.local_path file_name
  File.write file_path, new_contents
end
push_file(file_name, &block) click to toggle source
# File lib/geminabox/store/s3.rb, line 145
def push_file file_name, &block
  logger.info "Push: local -> S3 #{file_name}"

  new_contents = merge_file_with_remote file_name, &block
  s3_object(file_name).write new_contents
end
push_files(file_list, &block) click to toggle source
# File lib/geminabox/store/s3.rb, line 118
def push_files file_list, &block
  file_list.each do |file_name|
    push_file file_name, &block
  end
end
s3_object(file_name) click to toggle source
# File lib/geminabox/store/s3.rb, line 140
def s3_object file_name
  object_name = metadata_object_name('/' + file_name)
  @bucket.objects[object_name]
end
unpackage(content) click to toggle source
# File lib/geminabox/store/s3.rb, line 177
def unpackage(content)
  Marshal.load(Gem.gunzip(content))
end
update_metadata() click to toggle source
# File lib/geminabox/store/s3.rb, line 106
def update_metadata
  @lock_manager.lock ".metadata" do
    push_files SPLICEABLE_GZIPPED_FILES do |local_contents, remote_contents|
      merge_gzipped local_contents, remote_contents
    end

    push_files SPLICEABLE_TEXT_FILES do |local_contents, remote_contents|
      merge_text local_contents, remote_contents
    end
  end
end