defog 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -98,6 +98,11 @@ In addition, the handle allows you to look up the path where the local proxy fil
98
98
 
99
99
  defog.file("key").proxy_path # => Pathname where proxy file is, was, or will be
100
100
 
101
+ You can also iterate through handles of all cloud files, e.g.:
102
+
103
+ defog.each { |handle| puts handle.key }
104
+ defog.each.select { |handle| handle.last_modified < 12.hours.ago }
105
+
101
106
  === Persistence
102
107
 
103
108
  By default, Defog will delete the local proxy when closing a file.
@@ -170,7 +175,14 @@ If this would not free up enough space (because of open proxies or just
170
175
  because the remote is larger than the cache), raises
171
176
  Defog::Error::CacheFull and doesn't actually delete anything.
172
177
 
173
- You can also manually delete an individual persisted file, such as via:
178
+ For writeable proxes, of course Defog doesn't know in advance the size of
179
+ the data you will write into proxy file. As a crude estimate, if the
180
+ remote file already exists, Defog will reserve the same amount of space.
181
+ Instead, you can tell Defog the expected size via:
182
+
183
+ defog.file("key", "w", :size_hint => size-in-bytes)
184
+
185
+ You can also manage the cache manually, by explicitly deleting an individual persisted proxy files, such as via:
174
186
 
175
187
  defog.file("key").proxy_path.unlink
176
188
 
data/lib/defog/file.rb CHANGED
@@ -33,7 +33,7 @@ module Defog
33
33
  class File < ::File
34
34
 
35
35
  def initialize(opts={}, &block) #:nodoc:
36
- opts = opts.keyword_args(:handle => :required, :mode => :required, :persist => :optional)
36
+ opts = opts.keyword_args(:handle => :required, :mode => :required, :persist => :optional, :size_hint => :optional)
37
37
  @handle = opts.handle
38
38
  @persist = opts.persist
39
39
 
@@ -42,21 +42,28 @@ module Defog
42
42
  proxy_path.dirname.mkpath
43
43
  case opts.mode
44
44
  when "r"
45
- create_proxy
45
+ download = true
46
+ @upload = false
47
+ cache_size = @handle.size
46
48
  when "w", "w+"
49
+ download = false
47
50
  @upload = true
51
+ cache_size = opts.size_hint || @handle.size
48
52
  when "r+", "a", "a+"
49
- create_proxy
53
+ download = true
50
54
  @upload = true
55
+ cache_size = [opts.size_hint, @handle.size].compact.max
51
56
  else
52
57
  raise ArgumentError, "Invalid mode #{opts.mode.inspect}"
53
58
  end
54
59
 
60
+ @handle.proxy.manage_cache(cache_size, proxy_path)
61
+ @handle.proxy.reserve_proxy_path(proxy_path)
62
+ download_proxy if download
55
63
  super(proxy_path, opts.mode, &block)
56
64
  end
57
65
 
58
- def create_proxy
59
- @handle.proxy.open_proxy_file(@handle)
66
+ def download_proxy
60
67
  @handle.proxy.fog_wrapper.get_file(@handle.key, @handle.proxy_path)
61
68
  end
62
69
 
@@ -82,11 +89,12 @@ module Defog
82
89
  def close(opts={})
83
90
  opts = opts.keyword_args(:persist => @persist, :synchronize => true)
84
91
  super()
85
- if @handle.proxy_path.exist?
92
+ proxy_path = @handle.proxy_path
93
+ if proxy_path.exist?
86
94
  upload_proxy if @upload and opts.synchronize
87
- @handle.proxy_path.unlink unless opts.persist
95
+ proxy_path.unlink unless opts.persist
88
96
  end
89
- @handle.proxy.close_proxy_file(@handle)
97
+ @handle.proxy.release_proxy_path(proxy_path)
90
98
  end
91
99
  end
92
100
  end
data/lib/defog/handle.rb CHANGED
@@ -93,7 +93,7 @@ module Defog
93
93
  # to suppress deleting the file and so maintain the file after closing. See File#close for more
94
94
  # details.
95
95
  def open(mode, opts={}, &block)
96
- opts = opts.keyword_args(:persist => @proxy.persist)
96
+ opts = opts.keyword_args(:persist => @proxy.persist, :size_hint => :optional)
97
97
  File.open(opts.merge(:handle => self, :mode => mode), &block)
98
98
  end
99
99
 
data/lib/defog/proxy.rb CHANGED
@@ -54,7 +54,7 @@ module Defog
54
54
  @proxy_root = Pathname.new(opts.delete(:proxy_root)) if opts.proxy_root
55
55
  @persist = opts.delete(:persist)
56
56
  @max_cache_size = opts.delete(:max_cache_size)
57
- @open_proxy_paths = Set.new
57
+ @reserved_proxy_paths = Set.new
58
58
 
59
59
  @fog_wrapper = FogWrapper.connect(opts)
60
60
 
@@ -111,39 +111,55 @@ module Defog
111
111
  end
112
112
  end
113
113
 
114
- def open_proxy_file(handle) #:nodoc:
115
- manage_cache(handle) if max_cache_size
116
- @open_proxy_paths << handle.proxy_path
114
+ # Iterate through the cloud storage, yielding a Defog::Handle for each
115
+ # remote file.
116
+ #
117
+ # If no block is given, an enumerator is returned.
118
+ def each
119
+ if block_given?
120
+ @fog_wrapper.fog_directory.files.all.each do |fog_model|
121
+ yield file(fog_model.key)
122
+ end
123
+ else
124
+ to_enum(:each)
125
+ end
117
126
  end
118
127
 
119
- def close_proxy_file(handle) #:nodoc:
120
- @open_proxy_paths.delete handle.proxy_path
128
+ ###############################
129
+ # public-but-internal methods
130
+ #
131
+
132
+ def reserve_proxy_path(proxy_path) #:nodoc:
133
+ @reserved_proxy_paths << proxy_path
121
134
  end
122
135
 
123
- private
136
+ def release_proxy_path(proxy_path) #:nodoc:
137
+ @reserved_proxy_paths.delete proxy_path
138
+ end
124
139
 
125
- def manage_cache(handle)
126
- remote_size = handle.size
127
- proxy_path = handle.proxy_path
140
+ def manage_cache(want_size, proxy_path) #:nodoc:
141
+ return if max_cache_size.nil?
142
+ return if want_size.nil?
143
+ return if want_size <= 0
128
144
 
129
145
  # find available space (not counting current proxy)
130
146
  available = max_cache_size
131
147
  proxy_root.find { |path| available -= path.size if path.file? and path != proxy_path}
132
- return if available >= remote_size
148
+ return if available >= want_size
133
149
 
134
- space_needed = remote_size - available
150
+ space_needed = want_size - available
135
151
 
136
152
  # find all paths in the cache that aren't currently open (not
137
153
  # counting current proxy)
138
154
  candidates = []
139
- proxy_root.find { |path| candidates << path if path.file? and not @open_proxy_paths.include?(path) and path != proxy_path}
155
+ proxy_root.find { |path| candidates << path if path.file? and not @reserved_proxy_paths.include?(path) and path != proxy_path}
140
156
 
141
157
  # take candidates in LRU order until that would be enough space
142
158
  would_free = 0
143
159
  candidates = Set.new(candidates.sort_by(&:atime).take_while{|path| (would_free < space_needed).tap{|condition| would_free += path.size}})
144
160
 
145
161
  # still not enough...?
146
- raise Error::CacheFull, "No room in cache for #{handle.key.inspect}: size=#{remote_size} available=#{available} can_free=#{would_free}, max_cache_size=#{max_cache_size}" if would_free < space_needed
162
+ raise Error::CacheFull, "No room in cache for #{proxy_path.relative_path_from(proxy_root)}: size=#{want_size} available=#{available} can_free=#{would_free} (max_cache_size=#{max_cache_size})" if would_free < space_needed
147
163
 
148
164
  # LRU order may have taken more than needed, if last file was a big
149
165
  # chunk. So take another pass, eliminating files that aren't needed.
data/lib/defog/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Defog
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/spec/proxy_spec.rb CHANGED
@@ -39,6 +39,31 @@ shared_examples "a proxy" do |args|
39
39
  end
40
40
  end
41
41
 
42
+ context "iteration" do
43
+
44
+ before(:each) do
45
+ @proxy = Defog::Proxy.new(args)
46
+ @proxy.fog_directory.files.all.each do |model| model.destroy end
47
+ create_other_remote("i0", 10)
48
+ create_other_remote("i1", 10)
49
+ create_other_remote("i2", 10)
50
+ end
51
+
52
+ it "should iterate through remotes" do
53
+ seen = []
54
+ @proxy.each do |handle|
55
+ seen << handle.key
56
+ end
57
+ seen.should =~ [other_key("i0"), other_key("i1"), other_key("i2")]
58
+ end
59
+
60
+ it "should return an enumerator" do
61
+ @proxy.each.map(&:key).should =~ [other_key("i0"), other_key("i1"), other_key("i2")]
62
+ end
63
+
64
+ end
65
+
66
+
42
67
  context "proxy root location" do
43
68
  it "should default proxy root to tmpdir/defog" do
44
69
  proxy = Defog::Proxy.new(args)
@@ -66,6 +91,10 @@ shared_examples "a proxy" do |args|
66
91
  @proxy.proxy_root.mkpath
67
92
  end
68
93
 
94
+ it "should fail normally when trying to proxy a file that doesn't exist" do
95
+ expect { @proxy.file("nonesuch", "r") }.should raise_error(Defog::Error::NoCloudFile)
96
+ end
97
+
69
98
  it "should raise an error trying to proxy a file larger than the cache" do
70
99
  create_remote("x" * 101)
71
100
  expect { @proxy.file(key, "r") }.should raise_error(Defog::Error::CacheFull)
@@ -92,20 +121,31 @@ shared_examples "a proxy" do |args|
92
121
  other_proxy_path("c").should_not be_exist
93
122
  end
94
123
 
124
+ it "should delete proxies to make room for hinted size" do
125
+ create_other_proxy("a", 10)
126
+ create_other_proxy("b", 30)
127
+ create_other_proxy("c", 40)
128
+ expect { @proxy.file(key, "w", :size_hint => 80) do end }.should_not raise_error(Defog::Error::CacheFull)
129
+ proxy_path.should be_exist
130
+ other_proxy_path("a").should be_exist
131
+ other_proxy_path("b").should_not be_exist
132
+ other_proxy_path("c").should_not be_exist
133
+ end
134
+
95
135
  it "should not delete proxies that are open" do
96
136
  create_other_proxy("a", 20)
97
137
  create_other_proxy("b", 20)
98
138
  create_other_remote("R", 30)
99
- create_other_remote("S", 30)
100
139
  create_remote("x" * 30)
101
- @proxy.file("R", "r") do
102
- @proxy.file("S", "r") do
140
+ @proxy.file(other_key("R"), "r") do
141
+ @proxy.file(other_key("S"), "w") do
142
+ create_other_proxy("S", 30)
103
143
  expect { @proxy.file(key, "r") do end }.should_not raise_error(Defog::Error::CacheFull)
104
144
  proxy_path.should be_exist
105
- other_proxy_path("a").should_not be_exist
106
- other_proxy_path("b").should_not be_exist
107
145
  other_proxy_path("R").should be_exist
108
146
  other_proxy_path("S").should be_exist
147
+ other_proxy_path("a").should_not be_exist
148
+ other_proxy_path("b").should_not be_exist
109
149
  end
110
150
  end
111
151
  end
@@ -113,7 +153,7 @@ shared_examples "a proxy" do |args|
113
153
  it "should delete proxies that are no longer open" do
114
154
  create_other_remote("R", 60)
115
155
  create_remote("z" * 60)
116
- @proxy.file("R", "r") do end
156
+ @proxy.file(other_key("R"), "r") do end
117
157
  other_proxy_path("R").should be_exist
118
158
  expect { @proxy.file(key, "r") do end }.should_not raise_error(Defog::Error::CacheFull)
119
159
  proxy_path.should be_exist
@@ -123,11 +163,11 @@ shared_examples "a proxy" do |args|
123
163
  it "should not delete proxies if there wouldn't be enough space" do
124
164
  create_other_proxy("a", 20)
125
165
  create_other_proxy("b", 20)
126
- create_other_remote("r", 30)
127
- create_other_remote("s", 30)
166
+ create_other_remote("R", 30)
167
+ create_other_remote("S", 30)
128
168
  create_remote("z" * 50)
129
- @proxy.file("r", "r") do
130
- @proxy.file("s", "r") do
169
+ @proxy.file(other_key("R"), "r") do
170
+ @proxy.file(other_key("S"), "r") do
131
171
  expect { @proxy.file(key, "r") do end }.should raise_error(Defog::Error::CacheFull)
132
172
  proxy_path.should_not be_exist
133
173
  other_proxy_path("a").should be_exist
@@ -138,22 +178,28 @@ shared_examples "a proxy" do |args|
138
178
  end
139
179
  end
140
180
 
141
- private
181
+ end
142
182
 
143
- def create_other_proxy(otherkey, size)
144
- other_proxy_path(otherkey).open("w") do |f|
145
- f.write("x" * size)
146
- end
147
- end
183
+ private
148
184
 
149
- def other_proxy_path(otherkey)
150
- @proxy.file(otherkey).proxy_path
151
- end
185
+ def other_key(okey)
186
+ "#{key}-#{okey}"
187
+ end
152
188
 
153
- def create_other_remote(otherkey, size)
154
- @proxy.fog_directory.files.create(:key => otherkey, :body => "x" * size)
189
+ def create_other_proxy(okey, size)
190
+ path = other_proxy_path(okey)
191
+ path.dirname.mkpath
192
+ path.open("w") do |f|
193
+ f.write("x" * size)
155
194
  end
195
+ end
196
+
197
+ def other_proxy_path(okey)
198
+ @proxy.file(other_key(okey)).proxy_path
199
+ end
156
200
 
201
+ def create_other_remote(okey, size)
202
+ @proxy.fog_directory.files.create(:key => other_key(okey), :body => "x" * size)
157
203
  end
158
204
 
159
205
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: defog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-04-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
16
- requirement: &70234406646360 !ruby/object:Gem::Requirement
16
+ requirement: &70100481071360 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70234406646360
24
+ version_requirements: *70100481071360
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: hash_keyword_args
27
- requirement: &70234406645920 !ruby/object:Gem::Requirement
27
+ requirement: &70100481070940 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70234406645920
35
+ version_requirements: *70100481070940
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: fastandand
38
- requirement: &70234406645400 !ruby/object:Gem::Requirement
38
+ requirement: &70100481070520 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70234406645400
46
+ version_requirements: *70100481070520
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rake
49
- requirement: &70234406644900 !ruby/object:Gem::Requirement
49
+ requirement: &70100481070100 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70234406644900
57
+ version_requirements: *70100481070100
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rspec
60
- requirement: &70234406644280 !ruby/object:Gem::Requirement
60
+ requirement: &70100481069680 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70234406644280
68
+ version_requirements: *70100481069680
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: simplecov
71
- requirement: &70234406643420 !ruby/object:Gem::Requirement
71
+ requirement: &70100481069240 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70234406643420
79
+ version_requirements: *70100481069240
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: simplecov-gem-adapter
82
- requirement: &70234406643000 !ruby/object:Gem::Requirement
82
+ requirement: &70100481068720 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70234406643000
90
+ version_requirements: *70100481068720
91
91
  description: Wrapper to fog gem, proxying access to cloud files as local files.
92
92
  email:
93
93
  - ronen@barzel.org