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 +13 -1
- data/lib/defog/file.rb +16 -8
- data/lib/defog/handle.rb +1 -1
- data/lib/defog/proxy.rb +30 -14
- data/lib/defog/version.rb +1 -1
- data/spec/proxy_spec.rb +67 -21
- metadata +15 -15
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
92
|
+
proxy_path = @handle.proxy_path
|
93
|
+
if proxy_path.exist?
|
86
94
|
upload_proxy if @upload and opts.synchronize
|
87
|
-
|
95
|
+
proxy_path.unlink unless opts.persist
|
88
96
|
end
|
89
|
-
@handle.proxy.
|
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
|
-
@
|
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
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
120
|
-
|
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
|
-
|
136
|
+
def release_proxy_path(proxy_path) #:nodoc:
|
137
|
+
@reserved_proxy_paths.delete proxy_path
|
138
|
+
end
|
124
139
|
|
125
|
-
def manage_cache(
|
126
|
-
|
127
|
-
|
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 >=
|
148
|
+
return if available >= want_size
|
133
149
|
|
134
|
-
space_needed =
|
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 @
|
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 #{
|
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
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", "
|
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("
|
127
|
-
create_other_remote("
|
166
|
+
create_other_remote("R", 30)
|
167
|
+
create_other_remote("S", 30)
|
128
168
|
create_remote("z" * 50)
|
129
|
-
@proxy.file("
|
130
|
-
@proxy.file("
|
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
|
-
|
181
|
+
end
|
142
182
|
|
143
|
-
|
144
|
-
other_proxy_path(otherkey).open("w") do |f|
|
145
|
-
f.write("x" * size)
|
146
|
-
end
|
147
|
-
end
|
183
|
+
private
|
148
184
|
|
149
|
-
|
150
|
-
|
151
|
-
|
185
|
+
def other_key(okey)
|
186
|
+
"#{key}-#{okey}"
|
187
|
+
end
|
152
188
|
|
153
|
-
|
154
|
-
|
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.
|
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: &
|
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: *
|
24
|
+
version_requirements: *70100481071360
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: hash_keyword_args
|
27
|
-
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: *
|
35
|
+
version_requirements: *70100481070940
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: fastandand
|
38
|
-
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: *
|
46
|
+
version_requirements: *70100481070520
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rake
|
49
|
-
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: *
|
57
|
+
version_requirements: *70100481070100
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rspec
|
60
|
-
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: *
|
68
|
+
version_requirements: *70100481069680
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: simplecov
|
71
|
-
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: *
|
79
|
+
version_requirements: *70100481069240
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: simplecov-gem-adapter
|
82
|
-
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: *
|
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
|