defog 0.3.2 → 0.4.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.
@@ -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