uricp 0.0.1

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.
Files changed (47) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +16 -0
  4. data/README.md +31 -0
  5. data/README.rdoc +23 -0
  6. data/Rakefile +66 -0
  7. data/bin/segment_upload +66 -0
  8. data/bin/uricp +103 -0
  9. data/features/auth_download.feature +106 -0
  10. data/features/cacheable_from_uri.feature +71 -0
  11. data/features/documented_options.feature +73 -0
  12. data/features/remote_upload.feature +49 -0
  13. data/features/segmented_upload.feature +27 -0
  14. data/features/segmented_upload_options.feature +65 -0
  15. data/features/step_definitions/orbit_steps.rb +194 -0
  16. data/features/step_definitions/uricp_steps.rb +34 -0
  17. data/features/support/env.rb +20 -0
  18. data/features/userid_download.feature +21 -0
  19. data/lib/segment_upload.rb +38 -0
  20. data/lib/uricp/curl_primitives.rb +45 -0
  21. data/lib/uricp/orbit_auth.rb +59 -0
  22. data/lib/uricp/segmenter.rb +88 -0
  23. data/lib/uricp/strategy/cache_common.rb +48 -0
  24. data/lib/uricp/strategy/cached_get.rb +48 -0
  25. data/lib/uricp/strategy/cleaner.rb +28 -0
  26. data/lib/uricp/strategy/common.rb +108 -0
  27. data/lib/uricp/strategy/local_convert.rb +36 -0
  28. data/lib/uricp/strategy/local_convert_common.rb +29 -0
  29. data/lib/uricp/strategy/local_link.rb +31 -0
  30. data/lib/uricp/strategy/piped_cache.rb +40 -0
  31. data/lib/uricp/strategy/piped_cache_convert.rb +45 -0
  32. data/lib/uricp/strategy/piped_compress.rb +30 -0
  33. data/lib/uricp/strategy/piped_decompress.rb +35 -0
  34. data/lib/uricp/strategy/piped_local_compress.rb +30 -0
  35. data/lib/uricp/strategy/piped_local_get.rb +29 -0
  36. data/lib/uricp/strategy/piped_local_put.rb +29 -0
  37. data/lib/uricp/strategy/piped_remote_get.rb +38 -0
  38. data/lib/uricp/strategy/pruner.rb +22 -0
  39. data/lib/uricp/strategy/remote_put.rb +47 -0
  40. data/lib/uricp/strategy/segmented_remote_put.rb +52 -0
  41. data/lib/uricp/strategy/sweeper.rb +28 -0
  42. data/lib/uricp/uri_strategy.rb +47 -0
  43. data/lib/uricp/version.rb +4 -0
  44. data/lib/uricp.rb +56 -0
  45. data/spec/something_spec.rb +5 -0
  46. data/uricp.gemspec +32 -0
  47. metadata +281 -0
@@ -0,0 +1,65 @@
1
+ Feature: Documented Help for Segmented Upload
2
+ In order to work out what segment_upload does
3
+ As a command line user
4
+ I want to get detailed usage information
5
+
6
+ Scenario: Basic UI
7
+ When I get help for "segment_upload"
8
+ Then the exit status should be 0
9
+ And the banner should include the version
10
+ And the banner should document that this app takes options
11
+ And the following options should be documented:
12
+ |--version|
13
+ |--auth-token|
14
+ |--auth-user|
15
+ |--auth-key|
16
+ |--from|
17
+ |--segment-size|
18
+ And the option "dry-run" should be documented which is negatable
19
+ And the banner should document that this app's arguments are:
20
+ |to_uri|which is required|
21
+
22
+ Scenario: auth token and auth-user are mutually exclusive
23
+ When I run `segment_upload --auth-token abcdef --auth-user xyz --auth-key abc http://source file:///target`
24
+ Then the exit status should not be 0
25
+ And the stderr should contain "needless argument"
26
+
27
+ Scenario: auth user needs auth key
28
+ When I run `segment_upload --auth-user xyz http://source`
29
+ Then the exit status should not be 0
30
+ And the stderr should contain "missing argument"
31
+
32
+ Scenario: auth key needs auth user
33
+ When I run `segment_upload --auth-user xyz http://source`
34
+ Then the exit status should not be 0
35
+ And the stderr should contain "missing argument"
36
+
37
+ Scenario: needs some sort of authentication
38
+ When I run `segment_upload http://source`
39
+ Then the exit status should not be 0
40
+ And the stderr should contain "missing argument"
41
+
42
+ Scenario: no http should fail
43
+ When I run `segment_upload ftp://source`
44
+ Then the exit status should not be 0
45
+ And the stderr should contain "unsupported url"
46
+
47
+ Scenario: authentication with normal http should fail
48
+ When I run `segment_upload --auth-user cli-xxxxx --auth-key fred https://foss-lab-manual.googlecode.com/files`
49
+ Then the exit status should not be 0
50
+ And the stderr should contain "Cannot authenticate"
51
+
52
+ Scenario: bad authentication should fail against orbit
53
+ When I run `segment_upload --auth-user cli-xxxxx --auth-key fred https://orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
54
+ Then the exit status should not be 0
55
+ And the stderr should contain "Cannot authenticate"
56
+
57
+ Scenario: should not accept plain number for segment size
58
+ When I run `segment_upload --segment-size 100 --auth-token abcdef https:///orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
59
+ Then the exit status should not be 0
60
+ And the stderr should contain "Unparseable filesize"
61
+
62
+ Scenario: should trap attempts to open a command
63
+ When I run `segment_upload --segment-size 100MB --auth-token abcdef --from ' |fred' https:///orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
64
+ Then the exit status should not be 0
65
+ And the stderr should contain "invalid argument"
@@ -0,0 +1,194 @@
1
+ require 'inifile'
2
+
3
+ def obtain_credentials
4
+ config = IniFile.load(ENV['HOME']+ '/.brightbox/config') ||
5
+ raise(RuntimeError, "No brightbox config in home directory. Can't obtain auth token for tests")
6
+ section = config.sections.first
7
+ [config[section]['client_id'], config[section]['secret']]
8
+ end
9
+
10
+ def fetch_orbit_token
11
+ @clientid, @key = obtain_credentials
12
+ cmd = "curl -I https://orbit.brightbox.com/v1 -H 'X-Auth-User: #{@clientid}' -H 'X-Auth-Key: #{@key}'"
13
+ run_simple(unescape(cmd))
14
+ stdout_from(cmd).each_line do |line|
15
+ key, value = line.strip.split(/\s*:\s*/,2)
16
+ @current_auth_token = value if key == 'X-Auth-Token'
17
+ @current_storage_url = value if key == 'X-Storage-Url'
18
+ end
19
+ end
20
+
21
+ def create_download_file(filetype, name, container)
22
+ upload_url = File.join(@current_storage_url, container, name)
23
+ tempfile = '/tmp/fred55322'
24
+ FileUtils.rm_f(Dir.glob(tempfile+'*'))
25
+ format = 'raw'
26
+ format = 'qcow2' if filetype == 'qcow2'
27
+ cmd = "qemu-img create -q -f #{format} #{tempfile} 1G"
28
+ system(unescape(cmd))
29
+ cmd = "cat #{tempfile}"
30
+ if filetype == 'lz4'
31
+ cmd += " | lz4c "
32
+ end
33
+ cmd += "| curl --silent --fail -T - -H 'X-Auth-Token: #{@current_auth_token}' #{upload_url} "
34
+ system(unescape(cmd))
35
+ end
36
+
37
+ def create_container(name)
38
+ url = File.join(@current_storage_url, name)
39
+ cmd = "curl --silent --fail -I -H 'X-Auth-Token: #{@current_auth_token}' #{url}>/dev/null || curl --silent --fail -I -H 'X-Auth-Token: #{@current_auth_token}' #{url} -X PUT"
40
+ system(unescape(cmd))
41
+ end
42
+
43
+ Given(/^a container called "([^"]*)"$/) do |container|
44
+ create_container(container)
45
+ end
46
+
47
+ Given(/^a cache of "(.*?)" from container "(.*?)" at "(.*?)"$/) do |name, container, cache|
48
+ url = File.join(@current_storage_url, container, name)
49
+ target = File.join(cache, 'cache', name)
50
+ steps %{
51
+ When I successfully run `curl --fail --silent -o #{target} -H 'X-Auth-Token: #{@current_auth_token}' #{url}`
52
+ }
53
+ end
54
+
55
+ Given /^a (\d+) byte lz4 file named "([^"]*)"$/ do |file_size, file_name|
56
+ write_fixed_size_file(file_name, file_size.to_i)
57
+ system("lz4c #{file_name}")
58
+ File.rename file_name+'.lz4', file_name
59
+ end
60
+
61
+ When(/^I store "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, container, source|
62
+ steps %{
63
+ When I store "#{name}" with options "" into container "#{container}" from "#{source}"
64
+ }
65
+ end
66
+
67
+ When(/^I retrieve "([^"]*)" with options "([^"]*)" from container "([^"]*)" into "([^"]*)"$/) do |name, runoptions, container, target|
68
+ url = File.join(@current_storage_url, container, name)
69
+ steps %{
70
+ When I successfully run `uricp #{runoptions} --auth-token #{@current_auth_token} #{url} #{target}`
71
+ }
72
+ end
73
+
74
+ When(/^I retrieve "([^"]*)" from container "([^"]*)" into "([^"]*)" with a userid$/) do |name, container, target|
75
+ url = File.join(@current_storage_url, container, name)
76
+ steps %{
77
+ When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' #{url} #{target}`
78
+ }
79
+ end
80
+
81
+ When(/^I store "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, container, source|
82
+ @current_container = container
83
+ @current_name = name
84
+ url = File.join(@current_storage_url, container, name)
85
+ steps %{
86
+ When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' #{source} #{url}`
87
+ }
88
+ end
89
+
90
+ When(/^I retrieve "([^"]*)" from container "([^"]*)" into "([^"]*)"$/) do |name, container, target|
91
+ steps %{
92
+ When I retrieve "#{name}" with options "" from container "#{container}" into "#{target}"
93
+ }
94
+ end
95
+
96
+ When(/^I store "([^"]*)" with options "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, runoptions, container, source|
97
+ @current_container = container
98
+ @current_name = name
99
+ url = File.join(@current_storage_url, container, name)
100
+ steps %{
101
+ When I successfully run `uricp #{runoptions} --auth-token #{@current_auth_token} #{source} #{url}`
102
+ }
103
+ end
104
+
105
+ When(/^I store "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, segment_size, container, source|
106
+ @current_container = container
107
+ @current_name = name
108
+ url = File.join(@current_storage_url, container, name)
109
+ steps %{
110
+ When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' --segment-size '#{segment_size}' #{source} #{url}`
111
+ }
112
+ end
113
+
114
+ When(/^I store "([^"]*)" with segment size "([^"]*)" and options "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, segment_size, runoptions, container, source|
115
+ @current_container = container
116
+ @current_name = name
117
+ url = File.join(@current_storage_url, container, name)
118
+ steps %{
119
+ When I successfully run `uricp #{runoptions} --auth-user '#{@clientid}' --auth-key '#{@key}' --segment-size '#{segment_size}' #{source} #{url}`
120
+ }
121
+ end
122
+
123
+ When(/^I upload "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, segment_size, container, source|
124
+ @current_container = container
125
+ @current_name = name
126
+ url = File.join(@current_storage_url, container, name)
127
+ steps %{
128
+ When I successfully run `segment_upload --from #{source} --auth-token #{@current_auth_token} --segment-size #{segment_size} #{url}`
129
+ }
130
+ end
131
+
132
+ When(/^I upload "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)" as a stream$/) do |name, segment_size, container, source|
133
+ @current_container = container
134
+ @current_name = name
135
+ url = File.join(@current_storage_url, container, name)
136
+ steps %{
137
+ When I successfully run `sh -c 'cat #{source} | segment_upload --auth-token #{@current_auth_token} --segment-size #{segment_size} #{url}'`
138
+ }
139
+ end
140
+
141
+ Then(/^a (\d+) byte entry should exist in container "(.*?)" called "(.*?)"$/) do |size, container, name|
142
+ url = File.join(@current_storage_url, container, name)
143
+ command = "curl -I --fail --silent -H 'X-Auth-Token:#{@current_auth_token}' #{url}"
144
+ steps %{
145
+ When I successfully run `#{command}`
146
+ Then the output should match /\\sContent-Length: #{size}\\s/
147
+ }
148
+ end
149
+
150
+ Then(/^the container "(.*?)" should contain (\d+) entries$/) do |container, quantity|
151
+ url = File.join(@current_storage_url, container)
152
+ command = "curl -I --fail --silent -H 'X-Auth-Token:#{@current_auth_token}' #{url}"
153
+ steps %{
154
+ When I successfully run `#{command}`
155
+ Then the output should match /\\sX-Container-Object-Count: #{quantity}\\s/
156
+ }
157
+ end
158
+
159
+ Then(/^an lz4 compressed entry should exist in container "(.*?)" called "(.*?)"$/) do |container, name|
160
+ url = File.join(@current_storage_url, container, name)
161
+ cmd="curl -r 0-3 --fail --silent -H X-Auth-Token:#{@current_auth_token} #{url}"
162
+ run_simple(unescape(cmd))
163
+ assert_exact_output([0x184D2204].pack('V'), stdout_from(cmd))
164
+ end
165
+
166
+ Then(/^a qcow2 entry should exist in container "(.*?)" called "(.*?)"$/) do |container, name|
167
+ url = File.join(@current_storage_url, container, name)
168
+ cmd="curl -r 0-3 --fail --silent -H X-Auth-Token:#{@current_auth_token} #{url}"
169
+ run_simple(unescape(cmd))
170
+ assert_exact_output(['QFI',0xfb].pack('a3C'), stdout_from(cmd))
171
+ end
172
+
173
+ Before('@orbit') do
174
+ fetch_orbit_token
175
+ end
176
+
177
+ Before('@orbitdownloads') do
178
+ $orbit_setup ||= false
179
+ unless $orbit_setup
180
+ create_container('test')
181
+ create_download_file('qcow2', 'img-qcow2', 'test')
182
+ create_download_file('lz4', 'img-lz4cy', 'test')
183
+ $orbit_setup = true
184
+ end
185
+ end
186
+
187
+ After('@orbit') do
188
+ if @current_auth_token && @current_container
189
+ steps %{
190
+ When I successfully run `swift --os-storage-url=#{@current_storage_url} --os-auth-token=#{@current_auth_token} delete #{@current_container} #{@current_name}`
191
+ }
192
+ @current_container = @current_name = nil
193
+ end
194
+ end
@@ -0,0 +1,34 @@
1
+ Then(/^the file named "(.*?)" should have a file format of "(.*?)"$/) do |filename, format|
2
+ case format
3
+ when 'lz4'
4
+ assert_exact_output([0x184D2204].pack('V'),
5
+ File.open(filename, 'rb') {|f| f.read(4) })
6
+ when 'qcow2v3', 'qcow3'
7
+ steps %{
8
+ When I successfully run `qemu-img info #{filename}`
9
+ Then the output from "qemu-img info #{filename}" should contain "file format: qcow2"
10
+ And the output from "qemu-img info #{filename}" should contain "compat: 1.1"
11
+ }
12
+ else
13
+ assert_no_partial_output([0x184D2204].pack('V'),
14
+ File.open(filename, 'rb') {|f| f.read(4) })
15
+ steps(%{
16
+ When I successfully run `qemu-img info #{filename}`
17
+ Then the output from "qemu-img info #{filename}" should contain "file format: #{format}"
18
+ And the output from "qemu-img info #{filename}" should not contain "compat: 1.1"
19
+ })
20
+ end
21
+ end
22
+
23
+ Given(/^a correctly initialised cache at "(.*?)"$/) do |basedir|
24
+ steps %{
25
+ Given a directory named "#{basedir}/cache"
26
+ And a directory named "#{basedir}/temp"
27
+ }
28
+ end
29
+
30
+ Given(/^an empty directory named "([^"]*)"$/) do |dir_name|
31
+ create_dir(dir_name)
32
+ FileUtils.rm_rf(File.join(dir_name, '.'), :secure => true)
33
+ end
34
+
@@ -0,0 +1,20 @@
1
+ require 'aruba/cucumber'
2
+ require 'methadone/cucumber'
3
+ require 'fileutils'
4
+
5
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
6
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
7
+
8
+ Before do
9
+ # Using "announce" causes massive warnings on 1.9.2
10
+ @puts = true
11
+ @original_rubylib = ENV['RUBYLIB']
12
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
13
+ end
14
+
15
+ After do
16
+ ENV['RUBYLIB'] = @original_rubylib
17
+
18
+ FileUtils.rm_rf(Dir.glob('/tmp/uricp/*'))
19
+
20
+ end
@@ -0,0 +1,21 @@
1
+ @orbit
2
+ @orbitdownloads
3
+ Feature: User authenticated download of images from orbit
4
+ In order to download images from orbit
5
+ As a command line user
6
+ I want to retrieve the URI via an optional cache and copy correctly to target in the right format using a user id directly
7
+
8
+ Background:
9
+ Given an empty directory named "/tmp/uricp"
10
+ And the default aruba timeout is 15 seconds
11
+ And a container called "test"
12
+
13
+ Scenario: qcow download no conversion, no cache with userid
14
+ When I retrieve "img-qcow2" from container "test" into "file:///tmp/uricp/srv-test1" with a userid
15
+ Then a file named "/tmp/uricp/srv-test1" should exist
16
+ And the file named "/tmp/uricp/srv-test1" should have a file format of "qcow2v3"
17
+
18
+ Scenario: direct upload with userid
19
+ Given a 102400 byte file named "/tmp/uricp/srv-testy"
20
+ When I store "img-usrid" into container "test" from "file:///tmp/uricp/srv-testy" with a userid
21
+ Then a 102400 byte entry should exist in container "test" called "img-usrid"
@@ -0,0 +1,38 @@
1
+ require "uricp/version"
2
+ require "uricp/curl_primitives"
3
+ require "uricp/orbit_auth"
4
+ require "uricp/segmenter"
5
+
6
+ module Uricp
7
+
8
+ UnsupportedURLtype = Class.new(ArgumentError)
9
+
10
+ end
11
+
12
+ #Monkey patch a copy_stream facility in using 'sendfile'
13
+ unless IO.respond_to? :copy_stream
14
+ require 'sendfile'
15
+
16
+ def IO.copy_stream(src, dst, copy_length=nil, offset=nil)
17
+ unless src.stat.pipe?
18
+ current_pos = src.pos
19
+ count = dst.sendfile(src, offset || current_pos, copy_length)
20
+ src.seek(count, IO::SEEK_CUR)
21
+ return count
22
+ else
23
+ amount = copy_length.to_i
24
+ buf_size = 2**16
25
+ buffer=""
26
+ while amount > 0 do
27
+ src.read(buf_size, buffer)
28
+ amount_read = buffer.length
29
+ dst.write(buffer)
30
+ amount -= amount_read
31
+ break if src.eof?
32
+ end
33
+ return copy_length.to_i - amount
34
+ end
35
+ rescue EOFError
36
+ 0
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ module Uricp::CurlPrimitives
2
+
3
+ attr_reader :options
4
+
5
+ def from
6
+ options['from_uri']
7
+ end
8
+
9
+ def from=(target)
10
+ options['from_uri'] = target
11
+ end
12
+
13
+ def to
14
+ options['to_uri']
15
+ end
16
+
17
+ def to=(target)
18
+ options['to_uri'] = target
19
+ end
20
+
21
+ def curl_command
22
+ "curl --fail --silent"
23
+ end
24
+
25
+ def authentication
26
+ "-H X-Auth-Token:#{options['auth-token']}" if http_authentication?
27
+ end
28
+
29
+ def http_authentication?
30
+ options['auth-token']
31
+ end
32
+
33
+ def curl_upload_from(source, destination = to)
34
+ "#{curl_command} #{authentication} -T #{source} #{destination.to_s};"
35
+ end
36
+
37
+ def curl_download_to_pipe
38
+ "#{curl_command} #{authentication} #{from.to_s} |"
39
+ end
40
+
41
+ def curl_manifest(object_manifest, destination = to)
42
+ "#{curl_command} #{authentication} -X PUT -H 'X-Object-Manifest: #{object_manifest}' #{destination.to_s} --data-binary ''"
43
+ end
44
+
45
+ end
@@ -0,0 +1,59 @@
1
+ require 'open-uri'
2
+ module Uricp
3
+
4
+ class OrbitAuth
5
+
6
+ AuthenticationFailure = Class.new(ArgumentError)
7
+
8
+ def initialize(auth_uri, auth_id, auth_key)
9
+ auth_uri.open(
10
+ 'X-Auth-User' => auth_id,
11
+ 'X-Auth-Key' => auth_key,
12
+ 'Range' => 'bytes=0-0'
13
+ ) do |uri|
14
+ @storage_url = uri.meta['x-storage-url']
15
+ @token = uri.meta['x-auth-token']
16
+ end
17
+ rescue OpenURI::HTTPError => e
18
+ raise AuthenticationFailure, "Cannot authenticate against #{auth_uri}"
19
+ end
20
+
21
+ attr_reader :storage_url, :token
22
+
23
+ def self.validate_options(options)
24
+ if options['auth-token'] && (options['auth-key'] || options['auth-user'])
25
+ raise ::OptionParser::NeedlessArgument,
26
+ "use either key based or token based authentication"
27
+ end
28
+ if options['auth-key'].nil? ^ options['auth-user'].nil?
29
+ raise ::OptionParser::MissingArgument,
30
+ "'auth-user' requires 'auth-key'"
31
+ end
32
+ if (options['auth-token'] || options['auth-user']) && options['auth_uri'].nil?
33
+ raise ::OptionParser::NeedlessArgument,
34
+ "authentication is for http uris only"
35
+ end
36
+ end
37
+
38
+ def self.add_auth_token(options)
39
+ if options['auth-user']
40
+ orbit_credentials = self.new(options['auth_uri'],
41
+ options['auth-user'], options['auth-key'])
42
+ options['auth-token'] = orbit_credentials.token
43
+ options.delete('auth-key')
44
+ options.delete('auth-user')
45
+ end
46
+ end
47
+
48
+ def self.add_auth_to_optionparser(app)
49
+ app.on("--auth-token AUTH_TOKEN",
50
+ "Use AUTH_TOKEN for non-local requests")
51
+ app.on("--auth-user AUTH_USER",
52
+ "Use AUTH_USER for authentication")
53
+ app.on("--auth-key AUTH_KEY",
54
+ "Use AUTH_KEY for authentication")
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,88 @@
1
+ require 'pathname'
2
+
3
+ module Uricp
4
+
5
+ class Segmenter
6
+
7
+ include Methadone::CLILogging
8
+ include Methadone::SH
9
+ include Uricp::CurlPrimitives
10
+
11
+ def initialize(options)
12
+ @options = options
13
+ source = options['from']
14
+ if @source = source && open(source)
15
+ @stream = File.pipe?(source) || File.chardev?(source)
16
+ else
17
+ @source = STDIN
18
+ @stream = true
19
+ end
20
+ split_path
21
+ end
22
+
23
+ def split_path
24
+ elements = Pathname.new(to.path).enum_for(:each_filename).to_a
25
+ elements.shift
26
+ @account=elements.shift
27
+ @container=elements.shift
28
+ @object_path=File.join(elements)
29
+ end
30
+
31
+ def upload
32
+ if large_upload?
33
+ segmented_upload
34
+ else
35
+ sh! curl_upload_from(options['from']),
36
+ :on_fail => "Upload to #{to} failed"
37
+ end
38
+ end
39
+
40
+ def segment_size
41
+ options['segment-size'].to_i
42
+ end
43
+
44
+ def stream?
45
+ @stream
46
+ end
47
+
48
+ def large_upload?
49
+ stream? || @source.stat.size > segment_size
50
+ end
51
+
52
+ def manifest_suffix
53
+ @manifest_suffix ||= [Time.now.to_f, @source.stat.size, segment_size].join('/')
54
+ end
55
+
56
+ def object_manifest
57
+ @object_manifest ||= File.join(@container, @object_path, manifest_suffix)+'/'
58
+ end
59
+
60
+ def segmented_upload
61
+ debug "#{self.class.name}: Large upload detected - segmenting into #{segment_size} byte chunks."
62
+ suffix = 0
63
+ until @source.eof?
64
+ debug "#{self.class.name}: Uploading segment #{suffix}"
65
+ upload_segment(suffix)
66
+ suffix = suffix.next
67
+ end
68
+ add_manifest
69
+ end
70
+
71
+ def upload_segment(segment_number)
72
+ segment_name = File.join(to.to_s, manifest_suffix, '%08d' % segment_number)
73
+ debug "Uploading with #{curl_upload_from('-', segment_name)}"
74
+ open('|'+curl_upload_from('-', segment_name), 'w') do |destination|
75
+ copy_length = IO.copy_stream(@source, destination, segment_size)
76
+ debug "#{self.class.name}: Uploaded #{copy_length} bytes to #{segment_name}"
77
+ end
78
+ end
79
+
80
+ def add_manifest
81
+ debug "Adding DLO object_manifest #{object_manifest}"
82
+ sh! curl_manifest(object_manifest),
83
+ :on_fail => "Upload to #{to} failed"
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,48 @@
1
+ require 'fileutils'
2
+
3
+ module Uricp::Strategy
4
+
5
+ module CacheCommon
6
+
7
+ def validate_cache!
8
+ raise Uricp::MissingCache,
9
+ "No cache found at #{cache_root}. Expected a 'cache' and 'temp' directory" unless cache_exists?
10
+ raise Uricp::MissingCache,
11
+ "No cache name found" unless cache_name
12
+ end
13
+
14
+ def in_cache?
15
+ File.readable? cache_file
16
+ end
17
+
18
+ def cache_root
19
+ options['cache']
20
+ end
21
+
22
+ def cache_name
23
+ options['cache_name']
24
+ end
25
+
26
+ def cache_exists?
27
+ cache_root && %w{temp cache}.all? do |d|
28
+ File.directory?(File.join(cache_root, d))
29
+ end
30
+ end
31
+
32
+ def temp_cache_file
33
+ @temp_cache_file ||= File.join(cache_root, 'temp', cache_name)
34
+ end
35
+
36
+ def temp_cache_uri
37
+ URI.join('file:///', temp_cache_file)
38
+ end
39
+
40
+ def cache_file
41
+ @cache_file ||= File.join(cache_root, 'cache', cache_name)
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+
@@ -0,0 +1,48 @@
1
+ require 'uri'
2
+
3
+ module Uricp::Strategy
4
+
5
+ class CachedGet
6
+
7
+ include Uricp::Strategy::Common
8
+ include Uricp::Strategy::CacheCommon
9
+
10
+ def appropriate?
11
+ if cache_root
12
+ validate_cache!
13
+ if in_cache? || file_source?
14
+ return proposal
15
+ else
16
+ debug "#{self.class.name}: no cache entry for #{options['from_uri']}"
17
+ end
18
+ else
19
+ debug "#{self.class.name}: not appropriate"
20
+ end
21
+ false
22
+ end
23
+
24
+ def command
25
+ ":;"
26
+ end
27
+
28
+ def proposal
29
+ @proposed_options = options.dup
30
+ unless file_source?
31
+ @proposed_options['from_uri'] = URI.join('file:///', cache_file)
32
+ end
33
+ @proposed_options.delete('cache')
34
+ @proposed_options.delete('cache_name')
35
+ if conversion_required?
36
+ @proposed_options['source-format'] =
37
+ File.open(@proposed_options['from_uri'].path) {|f| encoding(f)}
38
+ if @proposed_options['source-format'] == @proposed_options['target-format']
39
+ @proposed_options.delete('source-format')
40
+ @proposed_options.delete('target-format')
41
+ end
42
+ end
43
+ self
44
+ end
45
+
46
+ end
47
+
48
+ end