filefm 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.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Sergio Rubio
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,107 @@
1
+ # FileFM
2
+
3
+ Dead simple file uploads and downloads from/to:
4
+
5
+ * HTTP
6
+ * Rackspace Cloudfiles
7
+ * Openstack Swift
8
+
9
+ ## API
10
+
11
+ ### Downloadinf files
12
+
13
+ *Swift*
14
+
15
+ FileFM.download "swift://swift-server.com/container1/foofile",
16
+ :progressbar => true,
17
+ :secure => false,
18
+ :destination => '/tmp/myfoofile',
19
+ :username => 'admin:admin',
20
+ :password => 'admin'
21
+
22
+ *Cloudfiles*
23
+
24
+ FileFM.download "cloudfiles://container1/foofile",
25
+ :progressbar => true,
26
+ :destination => '/tmp/myfoofile',
27
+ :username => 'foo-user',
28
+ :password => 'RACKSPACE-API-KEY-HERE'
29
+
30
+ *HTTP*
31
+
32
+ FileFM.download "http://imaginary-server/foo-linux.iso",
33
+ :progressbar => true,
34
+ :destination => '/tmp/ubuntu.iso'
35
+
36
+ ### Uploading files
37
+
38
+ *Swift*
39
+
40
+ FileFM.upload "myfoo-file.txt,
41
+ "swift://swift-server.com/container1/myfoo-file.txt",
42
+ :username => "admin:admin",
43
+ :password => "admin",
44
+ :secure => false, # no SSL
45
+ :progressbar => true # print progress while uploading
46
+
47
+ Using v2.0 authentication style (keystone)
48
+
49
+ FileFM::Uploaders::Swift.upload("/home/user/my-file",
50
+ "swift://swift.myserver.com/container1/my-file",
51
+ {
52
+ :username => "tenant:username",
53
+ :password => "secret",
54
+ :auth_url => "http://my-keystone-server:5000/v2.0/tokens",
55
+ :progressbar => true
56
+ })
57
+
58
+
59
+ *Cloudfiles*
60
+
61
+ FileFM.upload "myfoo-file.txt",
62
+ "cloudfiles://container1/foofile",
63
+ :progressbar => true,
64
+ :username => 'foo-user',
65
+ :password => 'RACKSPACE-API-KEY-HERE'
66
+
67
+ ## Command line
68
+
69
+
70
+ *Swift*
71
+
72
+ filefm upload --insecure --user admin:admin \
73
+ --password secret \
74
+ /home/user/my-file \
75
+ swift://server/container1/my-file
76
+
77
+ Using v2.0 authentication style (keystone)
78
+
79
+ filefm upload -A http://my-keystone-server:5000/v2.0/tokens \
80
+ --insecure -u admin:admin -p ADMIN \
81
+ /path/to/my/file \
82
+ swift://swift.myserver.com/container1/my-file
83
+
84
+ *Rackspace*
85
+
86
+ filefm upload --user <rackspace-username> \
87
+ --password <RACKSPACE_API> \
88
+ <path-to-file> \
89
+ cloudfiles://container1/foo.tmp
90
+
91
+ *HTTP*
92
+
93
+ filefm download http://my-server.com/my-big-file
94
+
95
+
96
+ ## DEBUGGING ENABLED
97
+
98
+ DEBUG=yes filefm upload --insecure --user admin:admin \
99
+ --password secret \
100
+ /home/user/my-file \
101
+ swift://server/container1/my-file
102
+
103
+ # Copyright
104
+
105
+ Copyright (c) 2012 Sergio Rubio. See LICENSE.txt for
106
+ further details.
107
+
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'filefm'
5
+
6
+ require 'rake'
7
+
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
11
+ gem.version = FileFM::VERSION
12
+ gem.name = "filefm"
13
+ gem.homepage = "http://github.com/rubiojr/filefm"
14
+ gem.license = "MIT"
15
+ gem.summary = %Q{Simple library to download/upload files}
16
+ gem.description = %Q{Simple library to download/upload files}
17
+ gem.email = "rubiojr@frameos.org"
18
+ gem.authors = ["Sergio Rubio"]
19
+ # dependencies defined in Gemfile
20
+ gem.add_runtime_dependency 'clamp'
21
+ gem.add_runtime_dependency 'fog'
22
+ gem.add_runtime_dependency 'progressbar'
23
+ gem.add_runtime_dependency 'rest-client'
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ task :default => :build
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'filefm'
4
+ require 'clamp'
5
+ require 'uri'
6
+
7
+ trap("INT") do
8
+ $stderr.puts "\nAborting."
9
+ exit 1
10
+ end
11
+
12
+ class UploadCommand < Clamp::Command
13
+
14
+ option ["-u","--user"], "PASSWORD", "User name when auth required"
15
+ option ["-p","--password"], "PASSWORD", "Password when auth required"
16
+ option ["-A","--auth-url"], "URL", "Optional authentication URL", :attribute_name => :auth_url
17
+ option "--insecure", :flag, "Secure uploads using SSL", :default => false
18
+ option "--no-progressbar", :flag, "Do not display progress", :default => false
19
+
20
+ parameter "SOURCE", "File to upload", :attribute_name => :source
21
+ parameter "TARGET", "Destination of the file", :attribute_name => :target
22
+
23
+ def execute
24
+ uri = URI.parse(target)
25
+ begin
26
+ FileFM.upload source,
27
+ target,
28
+ :username => user,
29
+ :password => password,
30
+ :secure => !insecure?,
31
+ :auth_url => auth_url,
32
+ :progressbar => !no_progressbar?
33
+ rescue => e
34
+ puts e.class
35
+ puts e.backtrace
36
+ puts "ERROR: #{e.message}"
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ class DownloadCommand < Clamp::Command
43
+
44
+ option "--no-progressbar", :flag, "Do not display progress", :default => false
45
+ option ["-u","--user"], "PASSWORD", "User name when auth required"
46
+ option ["-p","--password"], "PASSWORD", "Password when auth required"
47
+ option ["-d","--destination"], "PATH", "Path where the file will be saved"
48
+ option "--insecure", :flag, "Secure download using SSL", :default => true
49
+
50
+ parameter "FILES ...", "the thing to say", :attribute_name => :files
51
+
52
+ def execute
53
+ file = files.join(" ")
54
+ begin
55
+ FileFM.download file, :progressbar => !no_progressbar?,
56
+ :secure => !insecure?,
57
+ :username => user,
58
+ :destination => destination,
59
+ :password => password
60
+ rescue => e
61
+ puts "ERROR: #{e.message}"
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ class MainCommand < Clamp::Command
68
+
69
+ subcommand "download", "Download a file", DownloadCommand
70
+ subcommand "upload", "Upload a file", UploadCommand
71
+
72
+ end
73
+
74
+ MainCommand.run
@@ -0,0 +1,8 @@
1
+ require 'filefm'
2
+
3
+ # Swift
4
+ # download
5
+ FileFM.download "swift://swift-server/containerfoo/foo.tmp", :username => 'admin:admin', :password => 'secret', :progressbar => true
6
+
7
+ # upload
8
+
@@ -0,0 +1,54 @@
1
+ require 'progressbar'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'filefm/streaming_uploader'
5
+ require 'filefm/config'
6
+ require 'restclient'
7
+ require 'fileutils'
8
+ require 'multi_json'
9
+ require 'fog'
10
+ require 'logger'
11
+
12
+ module FileFM
13
+
14
+ if !defined? Log or Log.nil?
15
+ Log = Logger.new($stdout)
16
+ Log.formatter = proc do |severity, datetime, progname, msg|
17
+ "[FileFM] #{severity}: #{msg}\n"
18
+ end
19
+ Log.level = Logger::INFO unless ENV["DEBUG"].eql? "yes"
20
+ Log.debug "Initializing logger"
21
+ end
22
+
23
+ VERSION="0.1"
24
+
25
+ def self.download(link, opts={})
26
+ uri = URI.parse link
27
+ if uri.scheme =~ /^http/
28
+ require 'filefm/downloaders/http'
29
+ FileFM::Downloaders::HTTP.download link, opts
30
+ elsif uri.scheme =~ /^swift/
31
+ require 'filefm/downloaders/swift'
32
+ FileFM::Downloaders::Swift.download link, opts
33
+ elsif uri.scheme =~ /^cloudfiles/
34
+ require 'filefm/downloaders/cloudfiles'
35
+ FileFM::Downloaders::Cloudfiles.download link, opts
36
+ end
37
+
38
+ end
39
+
40
+ def self.upload(source, destination, options = {})
41
+ uri = URI.parse destination
42
+ if uri.scheme =~ /^swift/
43
+ uri = URI.parse(destination)
44
+ # swift://swift-server/container/object
45
+ require 'filefm/uploaders/swift'
46
+ FileFM::Uploaders::Swift.upload source, destination, options
47
+ elsif uri.scheme =~ /^cloudfiles/
48
+ require 'filefm/uploaders/cloudfiles'
49
+ FileFM::Uploaders::Cloudfiles.upload source, destination, options
50
+ end
51
+ end
52
+
53
+ end
54
+
@@ -0,0 +1,33 @@
1
+ module FileFM
2
+ require 'yaml'
3
+
4
+ class Config
5
+
6
+ def self.file=(file)
7
+ @file = file
8
+ end
9
+
10
+ def self.provider=(provider)
11
+ @provider = provider
12
+ end
13
+
14
+ def self.provider
15
+ @provider || :default
16
+ end
17
+
18
+ def self.file
19
+ @file || ENV["HOME"] + "/.filefm"
20
+ end
21
+
22
+ def self.load
23
+ return nil if not File.exist?(file)
24
+ @config ||= YAML.load_file file
25
+ end
26
+
27
+ def self.[](key)
28
+ self.load[provider][key]
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,91 @@
1
+ module FileFM
2
+ module Downloaders
3
+ class Cloudfiles
4
+ def self.download(link, opts = {})
5
+ require 'fog'
6
+ require 'fog/rackspace/storage'
7
+
8
+ uri = URI.parse(link)
9
+ container = uri.host
10
+ object = link.gsub(/^.*#{container}\//, "")
11
+ auth_url = "https://auth.api.rackspacecloud.com/v1.0"
12
+
13
+ if (not opts[:username] or not opts[:password])
14
+ raise "Invalid Credentials"
15
+ end
16
+ secure = true
17
+ scheme = secure ? "https" : "http"
18
+ username = opts[:username]
19
+ password = opts[:password]
20
+
21
+ #puts "#{scheme}://#{uri.host}/auth/v1.0"
22
+ conn = Fog::Storage.new({
23
+ :provider => 'Rackspace',
24
+ :rackspace_username => username,
25
+ :rackspace_api_key => password
26
+ })
27
+
28
+ out = RestClient.get auth_url, 'X-Auth-User' => username, 'X-Auth-Key' => password
29
+ storage_url = out.headers[:x_storage_url]
30
+ auth_token = out.headers[:x_auth_token]
31
+ raise "Error authenticating" unless out.code == 204
32
+
33
+ o = {
34
+ :location => nil,
35
+ :size => nil,
36
+ :filename => nil
37
+ }.merge(opts)
38
+
39
+ @location = o[:location] ||= ""
40
+ @filename = o[:filename]
41
+ progress = 0
42
+
43
+ headers = {
44
+ "User-Agent" => "FileFM #{VERSION}",
45
+ "X-Auth-Token" => auth_token
46
+ }
47
+
48
+ c = conn.directories.get container
49
+ raise "Container not found" if c.nil?
50
+
51
+ o = c.files.get object
52
+ @size = o.content_length
53
+
54
+ uri = URI.parse storage_url
55
+ http = Net::HTTP.new(uri.host, uri.port)
56
+ http.use_ssl = true if uri.scheme == "https"
57
+ http.open_timeout = 3 # seconds
58
+ http.read_timeout = 3 # seconds
59
+ path = uri.path + "/#{container}/#{object}"
60
+ request = Net::HTTP::Get.new(path)
61
+
62
+ request.initialize_http_header headers
63
+
64
+ puts "unknown file size for #{@filename} but downloading..." if @size.nil?
65
+
66
+ dest_file = opts[:destination] || File.basename(object)
67
+
68
+ response = http.request(request) do |response|
69
+ if opts[:progressbar]
70
+ bar = ProgressBar.new("Progress", @size.to_i) unless @size.nil?
71
+ bar.format_arguments=[:title, :percentage, :bar, :stat_for_file_transfer] unless @size.nil?
72
+ end
73
+
74
+ File.open(dest_file, "wb") do |file|
75
+ response.read_body do |segment|
76
+ if opts[:progressbar]
77
+ progress += segment.length
78
+ bar.set(progress) unless @size.nil?
79
+ end
80
+ file.write(segment)
81
+ end
82
+ end
83
+ end
84
+
85
+ FileUtils.rm dest_file unless response.is_a? Net::HTTPOK
86
+ raise "Error downlading file: #{response.class.to_s}" unless response.is_a? Net::HTTPOK
87
+ end
88
+ end
89
+ end
90
+ end
91
+
@@ -0,0 +1,59 @@
1
+ module FileFM
2
+ module Downloaders
3
+ class HTTP
4
+ def self.download(link, opts = {})
5
+ o = {
6
+ :location => nil,
7
+ :size => nil,
8
+ :filename => nil
9
+ }.merge(opts)
10
+
11
+ @link = link
12
+ @size = o[:size]
13
+ @location = o[:location] ||= ""
14
+ @filename = o[:filename]
15
+ @progress = 0
16
+
17
+ uri = URI.parse(@link.to_s)
18
+ http = Net::HTTP.new(uri.host, uri.port)
19
+ http.use_ssl = true if uri.scheme == "https"
20
+ http.open_timeout = 3 # seconds
21
+ http.read_timeout = 3 # seconds
22
+ request = Net::HTTP::Get.new(uri.request_uri)
23
+ request.initialize_http_header({"User-Agent" => "FileFM #{VERSION}"})
24
+
25
+ head = http.request_head(URI.escape(uri.request_uri))
26
+ case head
27
+ when Net::HTTPForbidden
28
+ @size = nil #no content-length no progress bar
29
+ else
30
+ @size = head['content-length'] if @size.nil? && head['content-length'].to_i > 1024
31
+ end
32
+
33
+ #puts "unknown file size for #{@filename} but downloading..." if @size.nil?
34
+ #puts @link.to_s
35
+
36
+ dest_file = @location + (@filename ||= File.basename(uri.path))
37
+ response = http.request(request) do |response|
38
+ if opts[:progressbar]
39
+ bar = ProgressBar.new("Progress", @size.to_i) unless @size.nil?
40
+ bar.format_arguments=[:title, :percentage, :bar, :stat_for_file_transfer] unless @size.nil?
41
+ end
42
+
43
+ File.open(dest_file, "wb") do |file|
44
+ response.read_body do |segment|
45
+ if opts[:progressbar]
46
+ @progress += segment.length
47
+ bar.set(@progress) unless @size.nil?
48
+ end
49
+ file.write(segment)
50
+ end
51
+ end
52
+ end
53
+ FileUtils.rm dest_file unless response.is_a? Net::HTTPOK
54
+ raise "Error downlading file: #{response.class.to_s}" unless response.is_a? Net::HTTPOK
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,93 @@
1
+ module FileFM
2
+ module Downloaders
3
+ class Swift
4
+ def self.download(link, opts = {})
5
+ require 'fog'
6
+ require 'fog/rackspace/storage'
7
+
8
+ uri = URI.parse(link)
9
+ container = uri.path.split("/")[1]
10
+ object = uri.path.split("/")[2..-1].join("/")
11
+
12
+ if (not opts[:username] or not opts[:password])
13
+ raise "Invalid Credentials"
14
+ end
15
+ secure = opts[:secure] == true
16
+ scheme = secure ? "https" : "http"
17
+ username = opts[:username]
18
+ password = opts[:password]
19
+
20
+ #puts "#{scheme}://#{uri.host}/auth/v1.0"
21
+ conn = Fog::Storage.new({
22
+ :provider => 'Rackspace',
23
+ :rackspace_username => username,
24
+ :rackspace_api_key => password,
25
+ :rackspace_auth_url => "#{scheme}://#{uri.host}/auth/v1.0"
26
+ })
27
+
28
+
29
+ out = RestClient.get "#{scheme}://#{uri.host}/auth/v1.0", 'X-Storage-User' => username, 'X-Storage-Pass' => password
30
+ storage_url = out.headers[:x_storage_url]
31
+ auth_token = out.headers[:x_auth_token]
32
+ raise "Error authenticating" unless out.code == 200
33
+
34
+ o = {
35
+ :location => nil,
36
+ :size => nil,
37
+ :filename => nil
38
+ }.merge(opts)
39
+
40
+ @link = storage_url + "#{uri.path}"
41
+ @size = o[:size]
42
+ @location = o[:location] ||= ""
43
+ @filename = o[:filename]
44
+ @progress = 0
45
+
46
+ headers = {
47
+ "User-Agent" => "FileFM #{VERSION}",
48
+ "X-Auth-Token" => auth_token
49
+ }
50
+
51
+ uri = URI.parse storage_url + uri.path
52
+ http = Net::HTTP.new(uri.host, uri.port)
53
+ http.use_ssl = true if uri.scheme == "https"
54
+ http.open_timeout = 3 # seconds
55
+ http.read_timeout = 3 # seconds
56
+ request = Net::HTTP::Get.new(uri.request_uri)
57
+
58
+ request.initialize_http_header headers
59
+
60
+ container = conn.directories.get container
61
+ raise "Container not found" if container.nil?
62
+
63
+ object = container.files.get object
64
+ @size = object.content_length
65
+
66
+ puts "unknown file size for #{@filename} but downloading..." if @size.nil?
67
+ #puts @link.to_s
68
+
69
+ dest_file = @location + (@filename ||= File.basename(uri.path))
70
+ response = http.request(request) do |response|
71
+ if opts[:progressbar]
72
+ bar = ProgressBar.new("Progress", @size.to_i) unless @size.nil?
73
+ bar.format_arguments=[:title, :percentage, :bar, :stat_for_file_transfer] unless @size.nil?
74
+ end
75
+
76
+ File.open(dest_file, "wb") do |file|
77
+ response.read_body do |segment|
78
+ if opts[:progressbar]
79
+ @progress += segment.length
80
+ bar.set(@progress) unless @size.nil?
81
+ end
82
+ file.write(segment)
83
+ end
84
+ end
85
+ end
86
+
87
+ FileUtils.rm dest_file unless response.is_a? Net::HTTPOK
88
+ raise "Error downlading file: #{response.class.to_s}" unless response.is_a? Net::HTTPOK
89
+ end
90
+ end
91
+ end
92
+ end
93
+
@@ -0,0 +1,157 @@
1
+ module FileFM
2
+
3
+ # StreamingUploader Adapted by Sergio Rubio <rubiojr@frameos.org>
4
+ #
5
+ # inspired by Opscode Chef StreamingCookbookUploader chef/streaming_cookbook_uploader.rb
6
+ # http://opscode.com
7
+ #
8
+ # inspired by/cargo-culted from http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
9
+ # On Apr 6, 2010, at 3:00 PM, Stanislav Vitvitskiy wrote:
10
+ #
11
+ # It's free to use / modify / distribute. No need to mention anything. Just copy/paste and use.
12
+ #
13
+ # Regards,
14
+ # Stan
15
+
16
+
17
+ require 'net/http'
18
+
19
+ class StreamingUploader
20
+
21
+ class << self
22
+
23
+ def put(to_url, params = {}, &block)
24
+ boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ'
25
+ parts = []
26
+ content_file = nil
27
+
28
+ unless params.nil? || params.empty?
29
+ params.each do |key, value|
30
+ if value.kind_of?(File)
31
+ content_file = value
32
+ filepath = value.path
33
+ filename = File.basename(filepath)
34
+ parts << StringPart.new( "--" + boundary + "\r\n" +
35
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" +
36
+ "Content-Type: application/octet-stream\r\n\r\n")
37
+ parts << StreamPart.new(value, File.size(filepath))
38
+ parts << StringPart.new("\r\n")
39
+ else
40
+ parts << StringPart.new( "--" + boundary + "\r\n" +
41
+ "Content-Disposition: form-data; Content-Type:application/json; name=\"" + key.to_s + "\"\r\n\r\n")
42
+ parts << StringPart.new(value.to_s + "\r\n")
43
+ end
44
+ end
45
+ parts << StringPart.new("--" + boundary + "--\r\n")
46
+ end
47
+
48
+ body_stream = MultipartStream.new(parts, block)
49
+
50
+ url = URI.parse(to_url)
51
+
52
+ headers = { 'Content-Length' => body_stream.size.to_s, 'Content-Type' => 'application/json' }.merge(params[:headers])
53
+
54
+ req = Net::HTTP::Put.new(url.path, headers)
55
+ if ENV["DEBUG"] == "yes"
56
+ puts "HEADERS: " + headers.inspect
57
+ puts "HOST: " + url.host
58
+ puts "PORT: " + url.port.to_s
59
+ puts "PATH: " + url.path
60
+ end
61
+
62
+ req.content_length = body_stream.size
63
+ req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty?
64
+ req.body_stream = body_stream
65
+
66
+ http = Net::HTTP.new(url.host, url.port)
67
+ http.use_ssl = true if url.scheme == "https"
68
+ #http.verify_mode = OpenSSL::SSL::VERIFY_NONE if url.scheme == "https"
69
+ res = http.request(req)
70
+ res
71
+ end
72
+
73
+ end
74
+
75
+ class StreamPart
76
+ def initialize(stream, size)
77
+ @stream, @size = stream, size
78
+ end
79
+
80
+ def size
81
+ @size
82
+ end
83
+
84
+ # read the specified amount from the stream
85
+ def read(offset, how_much)
86
+ @stream.read(how_much)
87
+ end
88
+ end
89
+
90
+ class StringPart
91
+ def initialize(str)
92
+ @str = str
93
+ end
94
+
95
+ def size
96
+ @str.length
97
+ end
98
+
99
+ # read the specified amount from the string startiung at the offset
100
+ def read(offset, how_much)
101
+ @str[offset, how_much]
102
+ end
103
+ end
104
+
105
+ class MultipartStream
106
+ def initialize(parts, blk = nil)
107
+ @callback = nil
108
+ if blk
109
+ @callback = blk
110
+ end
111
+ @parts = parts
112
+ @part_no = 0
113
+ @part_offset = 0
114
+ end
115
+
116
+ def size
117
+ @parts.inject(0) {|size, part| size + part.size}
118
+ end
119
+
120
+ def read(how_much)
121
+ @callback.call(how_much) if @callback
122
+ return nil if @part_no >= @parts.size
123
+
124
+ how_much_current_part = @parts[@part_no].size - @part_offset
125
+
126
+ how_much_current_part = if how_much_current_part > how_much
127
+ how_much
128
+ else
129
+ how_much_current_part
130
+ end
131
+
132
+ how_much_next_part = how_much - how_much_current_part
133
+
134
+ current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
135
+
136
+ # recurse into the next part if the current one was not large enough
137
+ if how_much_next_part > 0
138
+ @part_no += 1
139
+ @part_offset = 0
140
+ next_part = read(how_much_next_part)
141
+ current_part + if next_part
142
+ next_part
143
+ else
144
+ ''
145
+ end
146
+ else
147
+ @part_offset += how_much_current_part
148
+ current_part
149
+ end
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+
156
+ end
157
+
@@ -0,0 +1,59 @@
1
+ module FileFM
2
+ module Uploaders
3
+ class Cloudfiles
4
+
5
+ def self.upload(source, destination, options)
6
+ uri = URI.parse(destination)
7
+ container = uri.host
8
+ object = uri.path
9
+
10
+ if (not options[:username] or not options[:password])
11
+ raise "Invalid Credentials"
12
+ end
13
+ secure = options[:secure] == true
14
+ scheme = "https"
15
+ username = options[:username]
16
+ password = options[:password]
17
+
18
+ #puts "#{scheme}://#{uri.host}/auth/v1.0"
19
+ out = RestClient.get "#{scheme}://auth.api.rackspacecloud.com/v1.0", 'X-Auth-User' => username, 'X-Auth-Key' => password
20
+ storage_url = out.headers[:x_storage_url]
21
+ auth_token = out.headers[:x_auth_token]
22
+ raise "Error authenticating" unless out.code == 204
23
+
24
+ begin
25
+ out = RestClient.get storage_url + "/#{container}", 'X-Auth-Token' => auth_token
26
+ rescue
27
+ raise "Error accessing the container: #{container}"
28
+ end
29
+
30
+ if options[:progressbar]
31
+ pbar = ProgressBar.new "Progress", 100
32
+ fsize = File.size(source)
33
+ count = 0
34
+ end
35
+
36
+ headers = { 'X-Auth-Token' => auth_token, 'Content-Type' => "application/json" }
37
+ path = storage_url + "/#{container}#{object}"
38
+
39
+ res = FileFM::StreamingUploader.put(
40
+ path,
41
+ :headers => { 'X-Auth-Token' => auth_token }, :file => File.open(source)
42
+ ) do |size|
43
+ if block_given?
44
+ yield size
45
+ elsif options[:progressbar]
46
+ count += size
47
+ per = (100*count)/fsize
48
+ pbar.set per
49
+ else
50
+ end
51
+ end
52
+ if options[:progressbar]
53
+ pbar.finish
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,109 @@
1
+ module FileFM
2
+ module Uploaders
3
+ class Swift
4
+
5
+ def self.authenticate_keystone(username, tenant, api_key, auth_url)
6
+ Log.debug "Using Swift keystone authentication"
7
+ rackspace_auth_url = auth_url
8
+ uri = URI.parse(rackspace_auth_url)
9
+ connection = Fog::Connection.new(rackspace_auth_url, false)
10
+ req_body= {
11
+ 'auth' => {
12
+ 'passwordCredentials' => {
13
+ 'username' => username,
14
+ 'password' => api_key
15
+ }
16
+ }
17
+ }
18
+ req_body['auth']['tenantName'] = tenant
19
+
20
+ response = connection.request({
21
+ :expects => [200, 204],
22
+ :headers => {'Content-Type' => 'application/json'},
23
+ :body => MultiJson.encode(req_body),
24
+ :host => uri.host,
25
+ :method => 'POST',
26
+ :path => (uri.path and not uri.path.empty?) ? uri.path : 'v2.0'
27
+ })
28
+ body=MultiJson.decode(response.body)
29
+
30
+ if svc = body['access']['serviceCatalog'].detect{ |x| x['name'] == 'swift' }
31
+ mgmt_url = svc['endpoints'].detect{|x| x['publicURL']}['publicURL']
32
+ token = body['access']['token']['id']
33
+ r = {
34
+ "X-Storage-Url" => mgmt_url,
35
+ "X-Auth-Token" => token,
36
+ "X-Server-Management-Url" => svc['endpoints'].detect{|x| x['adminURL']}['adminURL']
37
+ }
38
+ return r
39
+ else
40
+ raise "Unable to parse service catalog."
41
+ end
42
+ end
43
+
44
+ def self.upload(source, destination, options = {})
45
+ uri = URI.parse(destination)
46
+ if uri.path.split("/").size == 2
47
+ uri.path = File.join(uri.path,File.basename(source))
48
+ end
49
+ if (not options[:username] or not options[:password])
50
+ raise "Invalid Credentials"
51
+ end
52
+ secure = options[:secure] == true
53
+ scheme = secure ? "https" : "http"
54
+ username = options[:username]
55
+ password = options[:password]
56
+
57
+ if options[:auth_url]
58
+ auth_url = options[:auth_url]
59
+ username,tenant = username.split(":")
60
+ out = authenticate_keystone(username, tenant, password, auth_url)
61
+ storage_url = out["X-Storage-Url"]
62
+ auth_token = out["X-Auth-Token"]
63
+ else
64
+ Log.debug "Using Swift legacy auth"
65
+ Log.debug "Legacy auth URL #{scheme}://#{uri.host}/auth/v1.0"
66
+ out = RestClient.get "#{scheme}://#{uri.host}/auth/v1.0", 'X-Storage-User' => username, 'X-Storage-Pass' => password
67
+ storage_url = out.headers[:x_storage_url]
68
+ auth_token = out.headers[:x_auth_token]
69
+ raise "Error authenticating" unless out.code == 200
70
+ end
71
+
72
+ Log.debug "authentication OK"
73
+ Log.debug "X-Storage-Url: #{storage_url}"
74
+
75
+ begin
76
+ container = "#{uri.path.split("/")[1]}"
77
+ out = RestClient.get storage_url + "/#{container}", 'X-Storage-User' => username, 'X-Auth-Token' => auth_token
78
+ rescue
79
+ raise "Error accessing the container: #{container}"
80
+ end
81
+
82
+ if options[:progressbar]
83
+ pbar = ProgressBar.new "Progress", 100
84
+ fsize = File.size(source)
85
+ count = 0
86
+ end
87
+
88
+ res = FileFM::StreamingUploader.put(
89
+ storage_url + uri.path,
90
+ :headers => { 'X-Auth-Token' => auth_token }, :file => File.open(source)
91
+ ) do |size|
92
+ if block_given?
93
+ yield size
94
+ elsif options[:progressbar]
95
+ count += size
96
+ per = (100*count)/fsize
97
+ per = 100 if per > 100
98
+ pbar.set per
99
+ else
100
+ end
101
+ end
102
+ if options[:progressbar]
103
+ pbar.finish
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,12 @@
1
+ :default:
2
+ :swift_user: admin:admin
3
+ :swift_password: lkajsdflkj
4
+ :cloudfiles_key: lakjsldkfj
5
+ :cloudfiles_user: fooser
6
+
7
+ :testing:
8
+ :swift_user: admin:admin
9
+ :swift_password: lkajsdflkjaldf
10
+ :cloudfiles_key: lkjaslkfjlakd
11
+ :cloudfiles_user: fooser
12
+
@@ -0,0 +1,49 @@
1
+ Shindo.tests('Test Config') do
2
+
3
+ def set_file f
4
+ FileFM::Config.file = f
5
+ end
6
+
7
+ tests("checking settings") do
8
+ returns(true, 'default file') do
9
+ FileFM::Config.file = nil
10
+ FileFM::Config.file == ENV["HOME"] + '/.filefm'
11
+ end
12
+ returns(true, 'changed file') do
13
+ FileFM::Config.file = '/foo/bar'
14
+ FileFM::Config.file == '/foo/bar'
15
+ end
16
+ returns(true, 'default provider is default') do
17
+ FileFM::Config.provider == :default
18
+ end
19
+ returns(true, 'set the provider to testing') do
20
+ FileFM::Config.provider = :testing
21
+ FileFM::Config.provider == :testing
22
+ end
23
+ end
24
+
25
+ tests("check loading") do
26
+ returns(true, 'with non existent file') do
27
+ FileFM::Config.file = '/foo/bar'
28
+ FileFM::Config.load.nil?
29
+ end
30
+ returns(true, 'with valid file returns Hash') do
31
+ FileFM::Config.file = test_config
32
+ FileFM::Config.load.kind_of? Hash
33
+ end
34
+ end
35
+
36
+ tests("config keys") do
37
+ FileFM::Config.file = test_config
38
+
39
+ returns true, 'have valid swift_user key' do
40
+ FileFM::Config[:swift_user] == 'admin:admin'
41
+ end
42
+ returns true, 'have valid swift_password key' do
43
+ FileFM::Config[:swift_user] == 'admin:admin'
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
@@ -0,0 +1,4 @@
1
+ Shindo.tests('Test Swift Downloader') do
2
+
3
+ end
4
+
@@ -0,0 +1,5 @@
1
+ require 'filefm'
2
+
3
+ def test_config
4
+ File.join(File.dirname(__FILE__), 'config.yml')
5
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: filefm
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sergio Rubio
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: shoulda
16
+ requirement: &23328280 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *23328280
25
+ - !ruby/object:Gem::Dependency
26
+ name: bundler
27
+ requirement: &23326980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *23326980
36
+ - !ruby/object:Gem::Dependency
37
+ name: jeweler
38
+ requirement: &23325780 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.6.4
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *23325780
47
+ - !ruby/object:Gem::Dependency
48
+ name: rcov
49
+ requirement: &23325260 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *23325260
58
+ - !ruby/object:Gem::Dependency
59
+ name: clamp
60
+ requirement: &23324520 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *23324520
69
+ - !ruby/object:Gem::Dependency
70
+ name: fog
71
+ requirement: &23323720 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *23323720
80
+ - !ruby/object:Gem::Dependency
81
+ name: progressbar
82
+ requirement: &23340420 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *23340420
91
+ - !ruby/object:Gem::Dependency
92
+ name: rest-client
93
+ requirement: &23339580 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: *23339580
102
+ description: Simple library to download/upload files
103
+ email: rubiojr@frameos.org
104
+ executables:
105
+ - filefm
106
+ extensions: []
107
+ extra_rdoc_files:
108
+ - LICENSE.txt
109
+ - README.md
110
+ files:
111
+ - .document
112
+ - Gemfile
113
+ - LICENSE.txt
114
+ - README.md
115
+ - Rakefile
116
+ - bin/filefm
117
+ - examples/basics.rb
118
+ - lib/filefm.rb
119
+ - lib/filefm/config.rb
120
+ - lib/filefm/downloaders/cloudfiles.rb
121
+ - lib/filefm/downloaders/http.rb
122
+ - lib/filefm/downloaders/swift.rb
123
+ - lib/filefm/streaming_uploader.rb
124
+ - lib/filefm/uploaders/cloudfiles.rb
125
+ - lib/filefm/uploaders/swift.rb
126
+ - tests/config.yml
127
+ - tests/config/config_tests.rb
128
+ - tests/downloaders/swift_tests.rb
129
+ - tests/helper.rb
130
+ homepage: http://github.com/rubiojr/filefm
131
+ licenses:
132
+ - MIT
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 1.8.17
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: Simple library to download/upload files
155
+ test_files: []