filefm 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []