defog 0.2.0 → 0.3.0

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.
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