experteer/blobsterix

View on GitHub
lib/blobsterix/storage/file_system.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'fileutils'
require 'benchmark'

module Blobsterix
  module Storage
    class FileSystem < Storage
      include Blobsterix::Logable

      def initialize(path)
        @contents_path = path
        @contents = nil
      end

      def bucket_exist(bucket="root")
        begin
          Dir.entries(contents).include?(bucket) and File.directory?(File.join(contents,bucket))
        rescue => e
          if e.is_a? ::Blobsterix::StorageError
            raise e
          else
            raise ::Blobsterix::StorageError.new("Could not check for bucket")
          end
        end
      end

      def list(bucket="root", opts={})
        begin
          if bucket =~ /root/
            list_buckets
          else
            if bucket_exist(bucket)
              b = Bucket.new(bucket, time_string_of(bucket))
              b.marker = opts[:start_path] if opts[:start_path]
              Blobsterix.wait_for(Proc.new {
                collect_bucket_entries(bucket, b, opts)
              })
              b
            else
              Nokogiri::XML::Builder.new do |xml|
                xml.Error "no such bucket"
              end
            end
          end
        rescue => e
          if e.is_a? ::Blobsterix::StorageError
            raise e
          else
            raise ::Blobsterix::StorageError.new("Could not list bucket(s)")
          end
        end
      end

      def get(bucket, key)
        begin
          if (not File.directory?(contents(bucket, key))) # and bucket_files(bucket).include?(key)
            Blobsterix.storage_read(BlobAccess.new(:bucket => bucket, :id => key))
            metaData(bucket, key)
          else
            Blobsterix.storage_read_fail(BlobAccess.new(:bucket => bucket, :id => key))
            Blobsterix::Storage::BlobMetaData.new
          end
        rescue => e
          if e.is_a? ::Blobsterix::StorageError
            raise e
          else
            raise ::Blobsterix::StorageError.new("Could not get bucket entry: #{@contents_path}")
          end
        end
      end

      def put(bucket, key, value, opts={})
        begin
          Blobsterix.storage_write(BlobAccess.new(:bucket => bucket, :id => key))

          meta = Blobsterix.wait_for(Proc.new do
            result = metaData(bucket, key).write() do |f|
              FileUtils.copy_stream(value, f)
            end
            FileUtils.touch File.join(contents(bucket), ".keep")
            result
          end)

          value.close if opts[:close_after_write]

          Blobsterix.wait_for(Proc.new {Blobsterix.cache.invalidate(Blobsterix::BlobAccess.new(:bucket => bucket, :id => key))})

          meta
        rescue => e
          if e.is_a? ::Blobsterix::StorageError
            raise e
          else
            raise ::Blobsterix::StorageError.new("Could not create bucket entry")
          end
        end
      end

      def create(bucket)
        begin
          logger.info "Storage: create bucket #{contents(bucket)}"
          FileUtils.mkdir_p(contents(bucket)) unless File.exist?(contents(bucket))
          FileUtils.touch File.join(contents(bucket), ".keep")

          Nokogiri::XML::Builder.new do |xml|
          end.to_s
        rescue => e
          if e.is_a? ::Blobsterix::StorageError
            raise e
          else
            raise ::Blobsterix::StorageError.new("Could not create bucket")
          end
        end
      end

      def delete(bucket)
        begin
          logger.info "Storage: delete bucket #{contents(bucket)}"
          FileUtils.rm_rf(contents(bucket)) if bucket_exist(bucket) && bucket_empty?(bucket)
        rescue => e
          if e.is_a? ::Blobsterix::StorageError
            raise e
          else
            raise ::Blobsterix::StorageError.new("Could not delete bucket")
          end
        end
      end

      def delete_key(bucket, key)
        begin
          Blobsterix.storage_delete(BlobAccess.new(:bucket => bucket, :id => key))
          Blobsterix.wait_for(Proc.new {Blobsterix.cache.invalidate(Blobsterix::BlobAccess.new(:bucket => bucket, :id => key))})

          metaData(bucket, key).delete # if bucket_files(bucket).include? key
        rescue => e
          if e.is_a? ::Blobsterix::StorageError
            raise e
          else
            raise ::Blobsterix::StorageError.new("Could not delete bucket key")
          end
        end
      end

      def name
        @contents_path
      end

      private
        def list_buckets
          BucketList.new do |l|
            Dir.entries("#{contents}").each{|dir|
              l.buckets << Bucket.new(dir, time_string_of(dir)) if File.directory? File.join("#{contents}",dir) and !(dir =='.' || dir == '..')
            }
          end
        end

        def collect_bucket_entries(bucket, b, opts)
          start_path = map_filename(opts[:start_path].to_s.gsub("/", "\\")) if opts[:start_path]
          current_obj = Blobsterix::DirectoryList.each_limit(contents(bucket), :limit => 20, :start_path => start_path) do |path, file|
            if file.to_s.end_with?(".meta")
              false
            else
              b.contents << BucketEntry.create(file, metaData(bucket, file.to_s))
              true
            end
          end
          next_marker = current_obj.current_file.to_s.gsub("\\", "/")
          if current_obj.next
            b.next_marker=next_marker
            b.truncated=true
          end
        end

        def contents(bucket=nil, key=nil)
          initialize_contents
          if bucket
            key ? File.join(@contents, bucket, map_filename(key.gsub("/", "\\"))) : File.join(@contents, bucket)
          else
            @contents
          end
        end

        def initialize_contents
          return if @contents != nil
          begin
            logger.info "Create FileSystem at #{@contents_path}"
            FileUtils.mkdir_p(@contents_path) unless Dir.exist?(@contents_path)
            FileUtils.touch File.join(@contents_path,".keep")
            @contents = @contents_path
          rescue
            raise ::Blobsterix::StorageError.new("Could not connect to FileSystem")
          end
        end

        def map_filename(filename)
          Murmur.map_filename(filename, filename)
        end

        def bucket_empty?(bucket)
          empty = true
          Blobsterix::DirectoryList.each(contents(bucket)) do
            empty = false
            break
          end
          empty
        end

        def metaData(bucket, key)
          Blobsterix::Storage::FileSystemMetaData.new(contents(bucket, key))
        end

        def time_string_of(*file_name)
          File.ctime("#{contents}/#{file_name.flatten.join("/")}").strftime("%Y-%m-%dT%H:%M:%S.000Z")
        end
    end
  end
end