asset_cloud 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +13 -0
  6. data/Rakefile +24 -0
  7. data/asset_cloud.gemspec +24 -0
  8. data/lib/asset_cloud.rb +54 -0
  9. data/lib/asset_cloud/asset.rb +187 -0
  10. data/lib/asset_cloud/asset_extension.rb +42 -0
  11. data/lib/asset_cloud/base.rb +247 -0
  12. data/lib/asset_cloud/bucket.rb +39 -0
  13. data/lib/asset_cloud/buckets/active_record_bucket.rb +57 -0
  14. data/lib/asset_cloud/buckets/blackhole_bucket.rb +23 -0
  15. data/lib/asset_cloud/buckets/bucket_chain.rb +84 -0
  16. data/lib/asset_cloud/buckets/file_system_bucket.rb +79 -0
  17. data/lib/asset_cloud/buckets/invalid_bucket.rb +28 -0
  18. data/lib/asset_cloud/buckets/memory_bucket.rb +42 -0
  19. data/lib/asset_cloud/buckets/versioned_memory_bucket.rb +33 -0
  20. data/lib/asset_cloud/callbacks.rb +63 -0
  21. data/lib/asset_cloud/free_key_locator.rb +28 -0
  22. data/lib/asset_cloud/metadata.rb +29 -0
  23. data/lib/asset_cloud/validations.rb +52 -0
  24. data/spec/active_record_bucket_spec.rb +95 -0
  25. data/spec/asset_extension_spec.rb +103 -0
  26. data/spec/asset_spec.rb +177 -0
  27. data/spec/base_spec.rb +114 -0
  28. data/spec/blackhole_bucket_spec.rb +41 -0
  29. data/spec/bucket_chain_spec.rb +158 -0
  30. data/spec/callbacks_spec.rb +125 -0
  31. data/spec/file_system_spec.rb +74 -0
  32. data/spec/files/products/key.txt +1 -0
  33. data/spec/files/versioned_stuff/foo +1 -0
  34. data/spec/find_free_key_spec.rb +39 -0
  35. data/spec/memory_bucket_spec.rb +52 -0
  36. data/spec/spec_helper.rb +5 -0
  37. data/spec/validations_spec.rb +53 -0
  38. data/spec/versioned_memory_bucket_spec.rb +36 -0
  39. metadata +151 -0
@@ -0,0 +1,39 @@
1
+ module AssetCloud
2
+ class AssetNotFoundError < StandardError
3
+ def initialize(key, version=nil)
4
+ super(version ? "Could not find version #{version} of asset #{key}" : "Could not find asset #{key}")
5
+ end
6
+ end
7
+
8
+ class Bucket
9
+ attr_reader :name
10
+ attr_accessor :cloud
11
+
12
+ def initialize(cloud, name)
13
+ @cloud, @name = cloud, name
14
+ end
15
+
16
+ def ls(key = nil)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def read(key)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def write(key, data)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def delete(key)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # versioning
33
+ #
34
+ # implement #read_version(key, version) and #versions(key) in subclasses
35
+ def versioned?
36
+ respond_to?(:read_version)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
1
+ module AssetCloud
2
+ class ActiveRecordBucket < AssetCloud::Bucket
3
+ class_attribute :key_attribute, :value_attribute
4
+ self.key_attribute = 'key'
5
+ self.value_attribute = 'value'
6
+
7
+ def ls(key=name)
8
+ col = records.connection.quote_column_name(self.key_attribute)
9
+ records.all(:conditions => ["#{col} LIKE ?", "#{key}%"]).map do |r|
10
+ cloud[r.send(self.key_attribute)]
11
+ end
12
+ end
13
+
14
+ def read(key)
15
+ find_record!(key).send(self.value_attribute)
16
+ end
17
+
18
+ def write(key, value)
19
+ record = records.send("find_or_initialize_by_#{self.key_attribute}", key.to_s)
20
+ record.send("#{self.value_attribute}=", value)
21
+ record.save!
22
+ end
23
+
24
+ def delete(key)
25
+ if record = find_record(key)
26
+ record.destroy
27
+ end
28
+ end
29
+
30
+ def stat(key)
31
+ if record = find_record(key)
32
+ AssetCloud::Metadata.new(true, record.send(self.value_attribute).size, record.created_at, record.updated_at)
33
+ else
34
+ AssetCloud::Metadata.new(false)
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ # override to return @cloud.user.assets or some other ActiveRecord Enumerable
41
+ # which responds to .connection, .find, etc.
42
+ #
43
+ # model must have columns for this class's key_attribute and value_attribute,
44
+ # plus created_at and updated_at.
45
+ def records
46
+ raise NotImplementedError
47
+ end
48
+
49
+ def find_record(key)
50
+ records.first(:conditions => {self.key_attribute => key.to_s})
51
+ end
52
+
53
+ def find_record!(key)
54
+ find_record(key) or raise(AssetCloud::AssetNotFoundError, key)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ module AssetCloud
2
+ class BlackholeBucket < Bucket
3
+ def ls(namespace = nil)
4
+ []
5
+ end
6
+
7
+ def read(key)
8
+ nil
9
+ end
10
+
11
+ def write(key, data)
12
+ nil
13
+ end
14
+
15
+ def delete(key)
16
+ nil
17
+ end
18
+
19
+ def stat(key)
20
+ Metadata.new(false)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,84 @@
1
+ module AssetCloud
2
+ class BucketChain < Bucket
3
+ # returns a new Bucket class which writes to each given Bucket
4
+ # but only uses the first one for reading
5
+ def self.chain(*klasses)
6
+ Class.new(self) do
7
+ attr_reader :chained_buckets
8
+ define_method 'initialize' do |cloud, name|
9
+ super(cloud, name)
10
+ @chained_buckets = klasses.map {|klass| klass.new(cloud,name)}
11
+ end
12
+ end
13
+ end
14
+
15
+
16
+ def ls(key=nil)
17
+ first_possible_bucket {|b| b.ls(key)}
18
+ end
19
+ def read(key)
20
+ first_possible_bucket {|b| b.read(key)}
21
+ end
22
+ def stat(key=nil)
23
+ first_possible_bucket {|b| b.stat(key)}
24
+ end
25
+ def read_version(key, version)
26
+ first_possible_bucket {|b| b.read_version(key, version)}
27
+ end
28
+ def versions(key)
29
+ first_possible_bucket {|b| b.versions(key)}
30
+ end
31
+
32
+
33
+ def write(key, data)
34
+ every_bucket_with_transaction_on_key(key) {|b| b.write(key, data)}
35
+ end
36
+ def delete(key)
37
+ every_bucket_with_transaction_on_key(key) {|b| b.delete(key)}
38
+ end
39
+
40
+ def respond_to?(sym)
41
+ @chained_buckets.any? {|b| b.respond_to?(sym)}
42
+ end
43
+ def method_missing(sym, *args)
44
+ first_possible_bucket {|b| b.send(sym, *args)}
45
+ end
46
+
47
+ private
48
+
49
+ def first_possible_bucket(&block)
50
+ @chained_buckets.each do |bucket|
51
+ begin
52
+ return yield(bucket)
53
+ rescue NoMethodError, NotImplementedError => e
54
+ nil
55
+ end
56
+ end
57
+ end
58
+
59
+ def every_bucket_with_transaction_on_key(key, i=0, &block)
60
+ return unless bucket = @chained_buckets[i]
61
+
62
+ old_value = begin
63
+ bucket.read(key)
64
+ rescue AssetCloud::AssetNotFoundError
65
+ nil
66
+ end
67
+ result = yield(bucket)
68
+
69
+ begin
70
+ every_bucket_with_transaction_on_key(key, i+1, &block)
71
+ return result
72
+ rescue StandardError => e
73
+ if old_value
74
+ bucket.write(key, old_value)
75
+ else
76
+ bucket.delete(key)
77
+ end
78
+ raise e
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,79 @@
1
+ module AssetCloud
2
+
3
+ class FileSystemBucket < Bucket
4
+
5
+ def ls(key = nil)
6
+ objects = []
7
+ base_path = File.join(path_for(key), '*')
8
+
9
+ Dir.glob(base_path).each do |f|
10
+ next unless File.file?(f)
11
+ objects.push cloud[relative_path_for(f)]
12
+ end
13
+ objects
14
+ end
15
+
16
+ def read(key)
17
+ File.read(path_for(key))
18
+ rescue Errno::ENOENT => e
19
+ raise AssetCloud::AssetNotFoundError, key
20
+ end
21
+
22
+ def delete(key)
23
+ File.delete(path_for(key))
24
+ rescue Errno::ENOENT
25
+ end
26
+
27
+ def write(key, data)
28
+ full_path = path_for(key)
29
+
30
+ retried = false
31
+
32
+ begin
33
+ File.open(full_path, "wb+") { |fp| fp << data }
34
+ true
35
+ rescue Errno::ENOENT => e
36
+ if retried == false
37
+ directory = File.dirname(full_path)
38
+ FileUtils.mkdir_p(File.dirname(full_path))
39
+ retried = true
40
+ retry
41
+ else
42
+ raise
43
+ end
44
+ false
45
+ end
46
+ end
47
+
48
+ def stat(key)
49
+ begin
50
+ stat = File.stat(path_for(key))
51
+ Metadata.new(true, stat.size, stat.ctime, stat.mtime)
52
+ rescue Errno::ENOENT => e
53
+ Metadata.new(false)
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def path_for(key)
60
+ cloud.path_for(key)
61
+ end
62
+
63
+ def path
64
+ cloud.path
65
+ end
66
+
67
+ private
68
+
69
+ def remove_full_path_regexp
70
+ @regexp ||= /^#{path}\//
71
+ end
72
+
73
+ def relative_path_for(f)
74
+ f.sub(remove_full_path_regexp, '')
75
+ end
76
+ end
77
+
78
+
79
+ end
@@ -0,0 +1,28 @@
1
+ module AssetCloud
2
+ class InvalidBucketError < StandardError
3
+ end
4
+
5
+ class InvalidBucket < Bucket
6
+ Error = "No such namespace: %s".freeze
7
+
8
+ def ls(namespace)
9
+ raise InvalidBucketError, Error % key
10
+ end
11
+
12
+ def read(key)
13
+ raise InvalidBucketError, Error % key
14
+ end
15
+
16
+ def write(key, data)
17
+ raise InvalidBucketError, Error % key
18
+ end
19
+
20
+ def delete(key)
21
+ raise InvalidBucketError, Error % key
22
+ end
23
+
24
+ def stat(key)
25
+ raise InvalidBucketError, Error % key
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ module AssetCloud
2
+
3
+ class MemoryBucket < Bucket
4
+
5
+ def initialize(*args)
6
+ super
7
+ @memory = {}
8
+ end
9
+
10
+ def ls(prefix=nil)
11
+ results = []
12
+ @memory.each do |k,v|
13
+ results.push(cloud[k]) if prefix.nil? || k.starts_with?(prefix)
14
+ end
15
+ results
16
+ end
17
+
18
+ def read(key)
19
+ raise AssetCloud::AssetNotFoundError, key unless @memory.has_key?(key)
20
+ @memory[key]
21
+ end
22
+
23
+ def delete(key)
24
+ @memory.delete(key)
25
+ end
26
+
27
+ def write(key, data)
28
+ @memory[key] = data
29
+
30
+ true
31
+ end
32
+
33
+ def stat(key)
34
+ return Metadata.non_existing unless @memory.has_key?(key)
35
+
36
+ Metadata.new(true, read(key).size)
37
+ end
38
+
39
+ end
40
+
41
+
42
+ end
@@ -0,0 +1,33 @@
1
+ module AssetCloud
2
+
3
+ class VersionedMemoryBucket < MemoryBucket
4
+
5
+ def read(key)
6
+ raise AssetCloud::AssetNotFoundError, key unless @memory.has_key?(key)
7
+ read_version(key, latest_version(key))
8
+ end
9
+
10
+ def write(key, data)
11
+ @memory[key] ||= []
12
+ @memory[key] << data
13
+ true
14
+ end
15
+
16
+ def read_version(key, version)
17
+ @memory[key][version - 1]
18
+ end
19
+
20
+ def versions(key)
21
+ (1..latest_version(key)).to_a
22
+ end
23
+
24
+ private
25
+
26
+ def latest_version(key)
27
+ @memory[key].size
28
+ end
29
+
30
+ end
31
+
32
+
33
+ end
@@ -0,0 +1,63 @@
1
+ require 'class_inheritable_attributes'
2
+
3
+ module AssetCloud
4
+ module Callbacks
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def callback_methods(*symbols)
9
+ symbols.each do |method|
10
+ code = <<-"end_eval"
11
+
12
+ def self.before_#{method}(*callbacks, &block)
13
+ callbacks << block if block_given?
14
+ write_inheritable_array(:before_#{method}, callbacks)
15
+ end
16
+
17
+ def self.after_#{method}(*callbacks, &block)
18
+ callbacks << block if block_given?
19
+ write_inheritable_array(:after_#{method}, callbacks)
20
+ end
21
+
22
+
23
+ def #{method}_with_callbacks(*args)
24
+ if execute_callbacks(:before_#{method}, args)
25
+ result = #{method}_without_callbacks(*args)
26
+ execute_callbacks(:after_#{method}, args)
27
+ end
28
+ result
29
+ end
30
+
31
+ alias_method_chain :#{method}, 'callbacks'
32
+ end_eval
33
+
34
+ self.class_eval code, __FILE__, __LINE__
35
+ end
36
+ end
37
+ end
38
+
39
+ def execute_callbacks(symbol, args)
40
+ callbacks_for(symbol).each do |callback|
41
+
42
+ result = case callback
43
+ when Symbol
44
+ self.send(callback, *args)
45
+ when Proc, Method
46
+ callback.call(self, *args)
47
+ else
48
+ if callback.respond_to?(method)
49
+ callback.send(method, self, *args)
50
+ else
51
+ raise StandardError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
52
+ end
53
+ end
54
+ return false if result == false
55
+ end
56
+ true
57
+ end
58
+
59
+ def callbacks_for(symbol)
60
+ self.class.send(symbol) || []
61
+ end
62
+ end
63
+ end