defog 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -43,9 +43,14 @@ module Defog
43
43
  class File < ::File
44
44
 
45
45
  def initialize(opts={}, &block) #:nodoc:
46
- opts = opts.keyword_args(:handle => :required, :mode => :required, :persist => :optional, :size_hint => :optional)
46
+ opts = opts.keyword_args(:handle => :required,
47
+ :mode => :required,
48
+ :persist => :optional,
49
+ :synchronize => { :valid => [:async, true, false], :default => true},
50
+ :size_hint => :optional)
47
51
  @handle = opts.handle
48
52
  @persist = opts.persist
53
+ @synchronize = opts.synchronize
49
54
 
50
55
  key = @handle.key
51
56
  proxy_path = @handle.proxy_path
@@ -75,38 +80,51 @@ module Defog
75
80
  super(proxy_path, opts.mode, &block)
76
81
  end
77
82
 
78
- def download_proxy
79
- @handle.proxy.fog_wrapper.get_file(@handle.key, @handle.proxy_path, @encoding)
80
- end
81
-
82
- def upload_proxy
83
- @handle.proxy.fog_wrapper.put_file(@handle.key, @handle.proxy_path, @encoding)
84
- end
85
-
86
-
87
83
  # Closes the proxy file and synchronizes the cloud storage (if it was
88
84
  # opened as writeable) then deletes the proxy file.
89
85
  #
90
- # Synchronization can be suppressed by passing the option
91
- # :synchronize => false
86
+ # Synchronization (i.e. upload of a proxy) can be controlled by passing the option
87
+ # :synchronize => :async # upload asynchronously in a separate thread
88
+ # :synchronize => true # upload synchronously
89
+ # :synchronize => false # don't upload
92
90
  # Synchronization will also be implicitly suppressed if the proxy file
93
91
  # was deleted before this call, e.g., via <code>::File.unlink(file.path)</code>.
94
92
  #
95
- #
96
93
  # Whether the proxy file gets deleted vs persisted after the close can
97
94
  # be set by passing the option
98
95
  # :persist => true or false
99
- # (This will override the setting of <code>:persist</code> passed to Proxy#file)
96
+ #
97
+ # The :persist and :synchronize values override the settings passed to
98
+ # Handle#open, which in turn overrides the settings passed to Proxy.new
100
99
  #
101
100
  def close(opts={})
102
- opts = opts.keyword_args(:persist => @persist, :synchronize => true)
101
+ opts = opts.keyword_args(:persist => @persist,
102
+ :synchronize => { :valid => [true, false, :async], :default => @synchronize })
103
+ @persist = opts.persist
104
+ @synchronize = opts.synchronize
103
105
  super()
104
- proxy_path = @handle.proxy_path
105
- if proxy_path.exist?
106
- upload_proxy if @upload and opts.synchronize
107
- proxy_path.unlink unless opts.persist
106
+ if @handle.proxy_path.exist?
107
+ if @upload and @synchronize == :async
108
+ Thread.new { wrap_proxy }
109
+ else
110
+ wrap_proxy
111
+ end
108
112
  end
109
- @handle.proxy.release_proxy_path(proxy_path)
113
+ @handle.proxy.release_proxy_path(@handle.proxy_path)
114
+ end
115
+
116
+ def download_proxy #:nodoc:
117
+ @handle.proxy.fog_wrapper.get_file(@handle.key, @handle.proxy_path, @encoding)
118
+ end
119
+
120
+ def upload_proxy #:nodoc:
121
+ @handle.proxy.fog_wrapper.put_file(@handle.key, @handle.proxy_path, @encoding)
110
122
  end
123
+
124
+ def wrap_proxy #:nodoc:
125
+ upload_proxy if @upload and @synchronize
126
+ @handle.proxy_path.unlink unless @persist
127
+ end
128
+
111
129
  end
112
130
  end
@@ -29,6 +29,7 @@ module Defog #:nodoc: all
29
29
  end
30
30
 
31
31
  def put_file(key, path, encoding)
32
+ return if path.exist? and fog_head(key) and Digest::MD5.hexdigest(path.read) == get_md5(key)
32
33
  path.open("r#{encoding}") do |file|
33
34
  fog_directory.files.create(:key => key, :body => file)
34
35
  end
@@ -38,6 +39,8 @@ module Defog #:nodoc: all
38
39
  fog_directory.files.head(key)
39
40
  end
40
41
 
42
+ private
43
+
41
44
  class Local < FogWrapper
42
45
  def provider ; :local ; end
43
46
 
@@ -70,7 +73,7 @@ module Defog #:nodoc: all
70
73
  def initialize(opts={})
71
74
  opts = opts.keyword_args(:aws_access_key_id => :required, :aws_secret_access_key => :required, :region => :optional, :bucket => :required)
72
75
  @location = opts.delete(:bucket)
73
- @fog_connection = Fog::Storage.new(opts.merge(:provider => provider))
76
+ @fog_connection = (@@aws_connection_cache||={})[opts] ||= Fog::Storage.new(opts.merge(:provider => provider))
74
77
  @fog_connection.directories.create :key => @location unless @fog_connection.directories.map(&:key).include? @location
75
78
  @fog_directory = @fog_connection.directories.get(@location)
76
79
  end
@@ -88,19 +88,27 @@ module Defog
88
88
  # Like ::File.open, if called with a block yields the file object to
89
89
  # the block and ensures the file will be closed when leaving the block.
90
90
  #
91
- # Normally upon close the proxy file is synchronized as needed and then deleted.
92
- # Pass
93
- # :persist => true
94
- # to suppress deleting the file and so maintain the file after closing. See File#close for more
95
- # details.
91
+ # Normally the proxy file gets deleted upon close (after synchronized
92
+ # as needed) rather than persisted, although the default behavior can
93
+ # be controlled by Defog::Proxy.new. To specify persistence behavior
94
+ # on a per-file basis, use
95
+ # :persist => true-or-false
96
+ # See File#close for more details.
96
97
  #
97
98
  # If you are managing your cache size, when opening a proxy for writing
98
99
  # you may want to provide a hint as to the expected size of the data:
99
100
  # :size_hint => 500.kilobytes
100
101
  # See README for more details.
101
102
  #
103
+ # Normally upon close of a writeable proxy file, the synchronization
104
+ # happens synchronously and the close will wait, althrough the behavior
105
+ # can be controlled by Defog::Proxy.new. To specify synchronization
106
+ # behavior on a per-file basis, use
107
+ # :synchronize => true-or-false-or-async
108
+ # See File#close for more details.
109
+ #
102
110
  def open(mode, opts={}, &block)
103
- opts = opts.keyword_args(:persist => @proxy.persist, :size_hint => :optional)
111
+ opts = opts.keyword_args(:persist => @proxy.persist, :synchronize => @proxy.synchronize, :size_hint => :optional)
104
112
  File.open(opts.merge(:handle => self, :mode => mode), &block)
105
113
  end
106
114
 
@@ -8,6 +8,7 @@ module Defog
8
8
 
9
9
  attr_reader :proxy_root
10
10
  attr_reader :persist
11
+ attr_reader :synchronize
11
12
  attr_reader :max_cache_size
12
13
  attr_reader :fog_wrapper # :nodoc:
13
14
 
@@ -21,7 +22,9 @@ module Defog
21
22
  # <code>:local</code> and <code>:AWS</code> are supported. When using
22
23
  # <code>:AWS</code>, an additional option <code>:bucket</code> must be
23
24
  # specified; all files proxied by this instance must be in a single
24
- # bucket.
25
+ # bucket. (It's OK to create multiple Defog::Proxy instances with
26
+ # the same access info but different buckets; they will internally
27
+ # share a single Fog::Storage isntance hence AWS connection.)
25
28
  #
26
29
  # By default, each proxy's root directory is placed in a reasonable
27
30
  # safe place, under <code>Rails.root/tmp</code> if Rails is defined
@@ -34,25 +37,41 @@ module Defog
34
37
  # to worry about it. But if you do care, you can specify the option:
35
38
  # :proxy_root => "/root/for/this/proxy/files"
36
39
  #
37
- # You can turn on persistence of local proxy files by specifying
40
+ # You can specify that by default local proxy files will be persisted,
41
+ # by specifying
38
42
  # :persist => true
39
43
  # The persistence behavior can be overriden on a per-file basis when
40
- # opening a proxy (see Defog::Handle#open)
44
+ # opening or closing a proxy (see Defog::Handle#open, Defog::File#close)
41
45
  #
42
46
  # You can enable cache management by specifying a max cache size in
43
47
  # bytes, e.g.
44
48
  # :max_cache_size => 3.gigabytes
45
49
  # See the README for discussion. [Number#gigabytes is defined in
46
50
  # Rails' ActiveSupport core extensions]
51
+ #
52
+ # Normally synchronization (i.e. upload) of changes to local proxy
53
+ # files happens synchronously on close; i.e. Defog::File#close waits
54
+ # until the upload completes. However, you can control synchronization
55
+ # by specifying
56
+ # :synchronize => :async # Synchronize in a separate thread, don't wait
57
+ # :synchronize => false # Don't synchronize at all. Defeats the purpose of Defog
58
+ # :synchronize => true # This is the default behavior
59
+ # The synchronization behavior can be overridden on a per-file basis
60
+ # when opening or closing a proxy (see Defog::Handle#open,
61
+ # Defog::File#close). Note that this applies only to upload of changes to
62
+ # proxy files that are opened as writeable; the download of data to
63
+ # readable proxy files always happens synchronously.
47
64
  def initialize(opts={})
48
65
  opts = opts.keyword_args(:provider => :required,
49
66
  :proxy_root => :optional,
50
67
  :persist => :optional,
68
+ :synchronize => {:valid => [:async, true, false], :default => true},
51
69
  :max_cache_size => :optional,
52
70
  :OTHERS => :optional)
53
71
 
54
72
  @proxy_root = Pathname.new(opts.delete(:proxy_root)) if opts.proxy_root
55
73
  @persist = opts.delete(:persist)
74
+ @synchronize = opts.delete(:synchronize)
56
75
  @max_cache_size = opts.delete(:max_cache_size)
57
76
  @reserved_proxy_paths = Set.new
58
77
 
@@ -1,3 +1,3 @@
1
1
  module Defog
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -114,6 +114,17 @@ shared_examples "create" do
114
114
  expect {remote_body}.should raise_error
115
115
  end
116
116
 
117
+ it "should create remote asynchronously if :synchronize => async" do
118
+ file = @proxy.file(key, @mode)
119
+ create_proxy("upload me in thread")
120
+ Thread.should_receive(:new) { |&block|
121
+ expect {remote_body}.should raise_error
122
+ block.call
123
+ }
124
+ file.close(:synchronize => :async)
125
+ remote_body.should == "upload me in thread"
126
+ end
127
+
117
128
  end
118
129
 
119
130
  shared_examples "update" do
@@ -127,6 +138,18 @@ shared_examples "update" do
127
138
  remote_body.should == "upload me"
128
139
  end
129
140
 
141
+ it "should overwrite remote asynchronously if :synchronize => :async" do
142
+ create_remote("overwrite me")
143
+ file = @proxy.file(key, @mode)
144
+ create_proxy("upload me")
145
+ Thread.should_receive(:new) { |&block|
146
+ remote_body.should == "overwrite me"
147
+ block.call
148
+ }
149
+ file.close(:synchronize => :async)
150
+ remote_body.should == "upload me"
151
+ end
152
+
130
153
  it "should not overwrite remote if proxy is deleted" do
131
154
  create_remote("keep me")
132
155
  @proxy.file(key, @mode) do |file|
@@ -206,8 +229,8 @@ shared_examples "a proxy file" do |proxyargs|
206
229
  it_should_behave_like "read" if mode == "r" or mode == "a+"
207
230
  it_should_behave_like "write" if mode =~ %r{[wa+]}
208
231
  it_should_behave_like "read after write" if mode == "w+"
232
+ it_should_behave_like "create" if mode =~ %r{w}
209
233
  it_should_behave_like "append" if mode =~ %r{a}
210
- it_should_behave_like "create" if mode =~ %r{wa}
211
234
  it_should_behave_like "update" if mode =~ %r{[wa+]}
212
235
  it_should_behave_like "persistence"
213
236
  end
@@ -39,6 +39,20 @@ shared_examples "a proxy" do |args|
39
39
  end
40
40
  end
41
41
 
42
+ context "settings" do
43
+ it "should set default for :persist => true" do
44
+ @proxy = Defog::Proxy.new(args.merge(:persist => true))
45
+ Defog::File.should_receive(:open).with(hash_including :persist => true)
46
+ @proxy.file(key, "w") do end
47
+ end
48
+ it "should set default for :synchronize => :async" do
49
+ @proxy = Defog::Proxy.new(args.merge(:synchronize => :async))
50
+ Defog::File.should_receive(:open).with(hash_including :synchronize => :async)
51
+ @proxy.file(key, "w") do end
52
+ end
53
+ end
54
+
55
+
42
56
  context "iteration" do
43
57
 
44
58
  before(:each) do
@@ -232,6 +246,25 @@ describe Defog::Proxy do
232
246
  it "should use the bucket name as the location" do
233
247
  Defog::Proxy.new(args).location.should == args[:bucket]
234
248
  end
249
+
250
+ it "should share fog connection with same bucket" do
251
+ proxy1 = Defog::Proxy.new(args)
252
+ proxy2 = Defog::Proxy.new(args)
253
+ proxy1.fog_connection.should be_equal proxy2.fog_connection
254
+ end
255
+
256
+ it "should share fog connection with different bucket" do
257
+ proxy1 = Defog::Proxy.new(args)
258
+ proxy2 = Defog::Proxy.new(args.merge(:bucket => "other"))
259
+ proxy1.fog_connection.should be_equal proxy2.fog_connection
260
+ end
261
+
262
+ it "should not share fog connection with different connection args" do
263
+ proxy1 = Defog::Proxy.new(args)
264
+ proxy2 = Defog::Proxy.new(args.merge(:aws_access_key_id => "other"))
265
+ proxy1.fog_connection.should_not be_equal proxy2.fog_connection
266
+ end
267
+
235
268
  end
236
269
 
237
270
  it "should raise error on bad provider" do
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.3.2
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-29 00:00:00.000000000 Z
12
+ date: 2012-04-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
16
- requirement: &70315023604540 !ruby/object:Gem::Requirement
16
+ requirement: &70345231086320 !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: *70315023604540
24
+ version_requirements: *70345231086320
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: hash_keyword_args
27
- requirement: &70315023604100 !ruby/object:Gem::Requirement
27
+ requirement: &70345231085600 !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: *70315023604100
35
+ version_requirements: *70345231085600
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: fastandand
38
- requirement: &70315023603660 !ruby/object:Gem::Requirement
38
+ requirement: &70345231084580 !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: *70315023603660
46
+ version_requirements: *70345231084580
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rake
49
- requirement: &70315023603220 !ruby/object:Gem::Requirement
49
+ requirement: &70345231082860 !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: *70315023603220
57
+ version_requirements: *70345231082860
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rspec
60
- requirement: &70315023602800 !ruby/object:Gem::Requirement
60
+ requirement: &70345231005400 !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: *70315023602800
68
+ version_requirements: *70345231005400
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: simplecov
71
- requirement: &70315023602380 !ruby/object:Gem::Requirement
71
+ requirement: &70345231001760 !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: *70315023602380
79
+ version_requirements: *70345231001760
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: simplecov-gem-adapter
82
- requirement: &70315023601960 !ruby/object:Gem::Requirement
82
+ requirement: &70345231000720 !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: *70315023601960
90
+ version_requirements: *70345231000720
91
91
  description: Wrapper to fog gem, proxying access to cloud files as local files.
92
92
  email:
93
93
  - ronen@barzel.org