cloudsync 1.0.4 → 2.0.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.
- data/VERSION +1 -1
- data/cloudsync.gemspec +1 -1
- data/lib/cloudsync/backend/base.rb +5 -13
- data/lib/cloudsync/backend/s3.rb +18 -12
- data/lib/cloudsync/backend/sftp.rb +39 -35
- data/lib/cloudsync/file.rb +45 -21
- data/lib/cloudsync/sync_manager.rb +2 -4
- metadata +4 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/cloudsync.gemspec
CHANGED
@@ -3,24 +3,16 @@ require 'tempfile'
|
|
3
3
|
module Cloudsync
|
4
4
|
module Backend
|
5
5
|
class Base
|
6
|
-
attr_accessor :store, :sync_manager, :name, :
|
6
|
+
attr_accessor :store, :sync_manager, :name, :upload_prefix
|
7
7
|
|
8
8
|
def initialize(opts = {})
|
9
9
|
@sync_manager = opts[:sync_manager]
|
10
10
|
@name = opts[:name]
|
11
11
|
@backend_type = opts[:backend] || self.class.to_s.split("::").last
|
12
|
+
@download_prefix = opts[:download_prefix] || ""
|
13
|
+
@upload_prefix = opts[:upload_prefix] || ""
|
12
14
|
end
|
13
15
|
|
14
|
-
def upload_prefix
|
15
|
-
{:bucket => @bucket, :prefix => @prefix}
|
16
|
-
end
|
17
|
-
|
18
|
-
def upload_prefix_path
|
19
|
-
if @bucket && @prefix
|
20
|
-
"#{@bucket}/#{@prefix}"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
16
|
# copy
|
25
17
|
def copy(file, to_backend)
|
26
18
|
start_copy = Time.now
|
@@ -37,7 +29,7 @@ module Cloudsync
|
|
37
29
|
end
|
38
30
|
|
39
31
|
def to_s
|
40
|
-
"#{@name}[:#{@backend_type}/#{
|
32
|
+
"#{@name}[:#{@backend_type}/#{@upload_prefix}]"
|
41
33
|
end
|
42
34
|
|
43
35
|
# needs_update?
|
@@ -92,7 +84,7 @@ module Cloudsync
|
|
92
84
|
end
|
93
85
|
|
94
86
|
private
|
95
|
-
|
87
|
+
|
96
88
|
def dry_run?
|
97
89
|
return false unless @sync_manager
|
98
90
|
@sync_manager.dry_run?
|
data/lib/cloudsync/backend/s3.rb
CHANGED
@@ -26,12 +26,12 @@ module Cloudsync
|
|
26
26
|
|
27
27
|
def download(file)
|
28
28
|
start_time = Time.now
|
29
|
-
$LOGGER.info("Downloading file #{file}")
|
29
|
+
$LOGGER.info("Downloading file #{file} (#{file.path})")
|
30
30
|
|
31
31
|
tempfile = file.tempfile
|
32
32
|
|
33
33
|
if !dry_run?
|
34
|
-
@store.interface.get(file.bucket, file.
|
34
|
+
@store.interface.get(file.bucket, file.download_path) do |chunk|
|
35
35
|
tempfile.write chunk
|
36
36
|
end
|
37
37
|
end
|
@@ -57,7 +57,7 @@ module Cloudsync
|
|
57
57
|
get_obj_from_store(file).delete
|
58
58
|
|
59
59
|
if bucket = @store.bucket(file.bucket)
|
60
|
-
bucket.key(file.
|
60
|
+
bucket.key(file.download_path).delete
|
61
61
|
|
62
62
|
if delete_bucket_if_empty && bucket.keys.empty?
|
63
63
|
$LOGGER.debug("Deleting empty bucket '#{bucket.name}'")
|
@@ -68,9 +68,9 @@ module Cloudsync
|
|
68
68
|
$LOGGER.error("Caught error: #{e} trying to delete #{file}")
|
69
69
|
end
|
70
70
|
|
71
|
-
def files_to_sync(upload_prefix=
|
71
|
+
def files_to_sync(upload_prefix="")
|
72
72
|
$LOGGER.info("Getting files to sync [#{self}]")
|
73
|
-
|
73
|
+
|
74
74
|
buckets_to_sync(upload_prefix).inject([]) do |files, bucket|
|
75
75
|
objects_from_bucket(bucket, upload_prefix).collect do |key|
|
76
76
|
files << Cloudsync::File.from_s3_obj(key, self.to_s)
|
@@ -81,18 +81,23 @@ module Cloudsync
|
|
81
81
|
|
82
82
|
private
|
83
83
|
|
84
|
-
def buckets_to_sync(upload_prefix)
|
85
|
-
|
86
|
-
|
84
|
+
def buckets_to_sync(upload_prefix="")
|
85
|
+
bucket_name = upload_prefix.split("/").first
|
86
|
+
if bucket_name
|
87
|
+
[@store.bucket(bucket_name, true)]
|
87
88
|
else
|
88
89
|
@store.buckets
|
89
90
|
end
|
90
91
|
end
|
91
92
|
|
92
|
-
def objects_from_bucket(bucket, upload_prefix)
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
def objects_from_bucket(bucket, upload_prefix="")
|
94
|
+
prefix_parts = upload_prefix.split("/")
|
95
|
+
prefix_parts.shift
|
96
|
+
prefix = prefix_parts.join("/")
|
97
|
+
|
98
|
+
if !prefix.empty?
|
99
|
+
bucket.keys(:prefix => prefix)
|
100
|
+
else
|
96
101
|
bucket.keys
|
97
102
|
end
|
98
103
|
end
|
@@ -107,6 +112,7 @@ module Cloudsync
|
|
107
112
|
end
|
108
113
|
|
109
114
|
def get_obj_from_store(file)
|
115
|
+
$LOGGER.debug("gofs, buck: #{file.bucket}. upload path: #{file.upload_path}")
|
110
116
|
if bucket = @store.bucket(file.bucket)
|
111
117
|
key = bucket.key(file.upload_path)
|
112
118
|
return key if key.exists?
|
@@ -8,27 +8,22 @@ module Cloudsync::Backend
|
|
8
8
|
|
9
9
|
def initialize(options = {})
|
10
10
|
@host = options[:host]
|
11
|
-
@base_path = options[:base_path]
|
12
11
|
@username = options[:username]
|
13
12
|
@password = options[:password]
|
14
|
-
prefix_parts = options[:upload_prefix].split("/")
|
15
|
-
|
16
|
-
@bucket = prefix_parts.shift
|
17
|
-
@prefix = prefix_parts.join("/")
|
18
13
|
|
19
14
|
super
|
20
15
|
end
|
21
16
|
|
22
17
|
# download
|
23
18
|
def download(file)
|
24
|
-
$LOGGER.info("Downloading #{file}")
|
19
|
+
$LOGGER.info("Downloading #{file} from #{file.download_path}")
|
25
20
|
tempfile = file.tempfile
|
26
21
|
|
27
22
|
if !dry_run?
|
28
23
|
Net::SSH.start(@host, @username, :password => @password) do |ssh|
|
29
24
|
ssh.sftp.connect do |sftp|
|
30
25
|
begin
|
31
|
-
sftp.download!(
|
26
|
+
sftp.download!(file.download_path, tempfile)
|
32
27
|
rescue RuntimeError => e
|
33
28
|
if e.message =~ /permission denied/
|
34
29
|
tempfile.close
|
@@ -46,12 +41,12 @@ module Cloudsync::Backend
|
|
46
41
|
|
47
42
|
# put
|
48
43
|
def put(file, local_filepath)
|
49
|
-
$LOGGER.info("Putting #{file} to #{self}")
|
44
|
+
$LOGGER.info("Putting #{file} to #{self} (#{file.upload_path})")
|
50
45
|
return if dry_run?
|
51
46
|
|
52
47
|
Net::SSH.start(@host, @username, :password => @password) do |ssh|
|
53
48
|
ssh.sftp.connect do |sftp|
|
54
|
-
sftp.upload!(local_filepath,
|
49
|
+
sftp.upload!(local_filepath, file.upload_path)
|
55
50
|
end
|
56
51
|
end
|
57
52
|
end
|
@@ -63,7 +58,7 @@ module Cloudsync::Backend
|
|
63
58
|
|
64
59
|
Net::SSH.start(@host, @username, :password => @password) do |ssh|
|
65
60
|
ssh.sftp.connect do |sftp|
|
66
|
-
sftp.remove!(
|
61
|
+
sftp.remove!(file.download_path)
|
67
62
|
end
|
68
63
|
end
|
69
64
|
end
|
@@ -73,62 +68,71 @@ module Cloudsync::Backend
|
|
73
68
|
files = []
|
74
69
|
Net::SSH.start(@host, @username, :password => @password) do |ssh|
|
75
70
|
ssh.sftp.connect do |sftp|
|
76
|
-
filepaths = sftp.dir.glob(@
|
71
|
+
filepaths = sftp.dir.glob(@download_prefix, "**/**").collect {|entry| entry.name}
|
77
72
|
|
78
73
|
files = filepaths.collect do |filepath|
|
79
|
-
attrs = sftp.stat!(
|
74
|
+
attrs = sftp.stat!(local_filepath_from_filepath(filepath))
|
80
75
|
next unless attrs.file?
|
81
76
|
|
82
77
|
e_tag = ssh.exec!(md5sum_cmd(filepath)).split(" ").first
|
83
78
|
Cloudsync::File.new \
|
84
|
-
:
|
85
|
-
:
|
86
|
-
:
|
87
|
-
:
|
88
|
-
:
|
89
|
-
:
|
79
|
+
:path => filepath,
|
80
|
+
:upload_prefix => @upload_prefix,
|
81
|
+
:download_prefix => @download_prefix,
|
82
|
+
:size => attrs.size,
|
83
|
+
:last_modified => attrs.mtime,
|
84
|
+
:e_tag => e_tag,
|
85
|
+
:backend => self.to_s,
|
86
|
+
:backend_type => Cloudsync::Backend::Sftp
|
90
87
|
end.compact
|
91
88
|
end
|
92
89
|
end
|
93
90
|
files
|
94
91
|
end
|
95
92
|
|
96
|
-
def absolute_path(path)
|
97
|
-
|
98
|
-
end
|
93
|
+
# def absolute_path(path)
|
94
|
+
# @download_prefix + "/" + path
|
95
|
+
# end
|
99
96
|
|
100
97
|
private
|
101
98
|
|
102
99
|
def md5sum_cmd(filepath)
|
103
|
-
Escape.shell_command(["md5sum","#{
|
100
|
+
Escape.shell_command(["md5sum","#{local_filepath_from_filepath(filepath)}"])
|
101
|
+
end
|
102
|
+
|
103
|
+
def local_filepath_from_filepath(filepath)
|
104
|
+
stripped_path = filepath.sub(/^#{@upload_prefix}\/?/,"")
|
105
|
+
if @download_prefix
|
106
|
+
"#{@download_prefix}/#{stripped_path}"
|
107
|
+
else
|
108
|
+
stripped_path
|
109
|
+
end
|
104
110
|
end
|
105
111
|
|
106
112
|
# get_file_from_store
|
107
113
|
def get_file_from_store(file)
|
108
|
-
|
109
|
-
|
110
|
-
$LOGGER.debug("Looking for local filepath: #{local_filepath}")
|
111
|
-
$LOGGER.debug("Abs filepath: #{absolute_path(local_filepath)}")
|
112
|
-
|
114
|
+
$LOGGER.debug("Looking for local filepath: #{local_filepath_from_filepath(file.full_download_path)}")
|
115
|
+
|
113
116
|
sftp_file = nil
|
114
117
|
Net::SSH.start(@host, @username, :password => @password) do |ssh|
|
115
118
|
ssh.sftp.connect do |sftp|
|
116
119
|
begin
|
117
|
-
attrs = sftp.stat!(
|
120
|
+
attrs = sftp.stat!(local_filepath_from_filepath(file.full_download_path))
|
118
121
|
rescue Net::SFTP::StatusException => e
|
119
122
|
break if e.message =~ /no such file/
|
120
123
|
raise
|
121
124
|
end
|
122
125
|
break unless attrs.file?
|
123
126
|
|
124
|
-
e_tag = ssh.exec!(md5sum_cmd(filepath)).split(" ").first
|
125
127
|
sftp_file = Cloudsync::File.new \
|
126
|
-
:
|
127
|
-
:
|
128
|
-
:
|
129
|
-
:
|
130
|
-
:
|
131
|
-
:
|
128
|
+
:path => file.download_path,
|
129
|
+
:upload_prefix => @upload_prefix,
|
130
|
+
:download_prefix => @download_prefix,
|
131
|
+
:size => attrs.size,
|
132
|
+
:last_modified => attrs.mtime,
|
133
|
+
:e_tag => ssh.exec!(md5sum_cmd(file.download_path)).split(" ").first,
|
134
|
+
:backend => self.to_s,
|
135
|
+
:backend_type => Cloudsync::Backend::Sftp
|
132
136
|
end
|
133
137
|
end
|
134
138
|
sftp_file
|
data/lib/cloudsync/file.rb
CHANGED
@@ -1,51 +1,55 @@
|
|
1
1
|
module Cloudsync
|
2
2
|
class File
|
3
|
-
attr_accessor :
|
4
|
-
alias_method :container, :bucket
|
5
|
-
alias_method :container=, :bucket=
|
3
|
+
attr_accessor :path, :size, :last_modified, :e_tag, :backend
|
6
4
|
|
7
|
-
def initialize(options={})
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
5
|
+
def initialize(options = {})
|
6
|
+
@path = options[:path]
|
7
|
+
@size = options[:size]
|
8
|
+
@last_modified = options[:last_modified]
|
9
|
+
@e_tag = options[:e_tag]
|
10
|
+
@backend = options[:backend]
|
11
|
+
@upload_prefix = options[:upload_prefix]
|
12
|
+
@download_prefix = options[:download_prefix]
|
13
|
+
@backend_type = options[:backend_type]
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.from_s3_obj(obj, backend=nil)
|
17
17
|
return nil if obj.nil?
|
18
18
|
new({
|
19
|
-
:
|
19
|
+
:upload_prefix => obj.bucket.name,
|
20
20
|
:path => obj.name,
|
21
21
|
:size => obj.size,
|
22
22
|
:last_modified => obj.last_modified.to_i,
|
23
23
|
:e_tag => obj.e_tag.gsub('"',''),
|
24
|
-
:backend => backend
|
24
|
+
:backend => backend,
|
25
|
+
:backend_type => Cloudsync::Backend::S3})
|
25
26
|
end
|
26
27
|
|
27
28
|
def self.from_cf_info(container, path, hash, backend)
|
28
|
-
new({
|
29
|
+
new({
|
30
|
+
:upload_prefix => container.name,
|
29
31
|
:path => path,
|
30
32
|
:size => hash[:bytes],
|
31
33
|
:last_modified => hash[:last_modified].to_gm_time.to_i,
|
32
34
|
:e_tag => hash[:hash],
|
33
|
-
:backend => backend
|
35
|
+
:backend => backend,
|
36
|
+
:backend_type => Cloudsync::Backend::CloudFiles })
|
34
37
|
end
|
35
38
|
|
36
39
|
def self.from_cf_obj(obj, backend=nil)
|
37
40
|
return nil if obj.nil?
|
38
41
|
new({
|
39
|
-
:
|
42
|
+
:upload_prefix => obj.container.name,
|
40
43
|
:path => obj.name,
|
41
44
|
:size => obj.bytes.to_i,
|
42
45
|
:last_modified => obj.last_modified.to_i,
|
43
46
|
:e_tag => obj.etag,
|
44
|
-
:backend => backend
|
47
|
+
:backend => backend,
|
48
|
+
:backend_type => Cloudsync::Backend::CloudFiles})
|
45
49
|
end
|
46
50
|
|
47
51
|
def to_s
|
48
|
-
"#{
|
52
|
+
"#{full_upload_path}"
|
49
53
|
end
|
50
54
|
|
51
55
|
def unique_filename
|
@@ -56,14 +60,34 @@ module Cloudsync
|
|
56
60
|
[bucket,path].join("/")
|
57
61
|
end
|
58
62
|
|
63
|
+
def bucket
|
64
|
+
@bucket ||= begin
|
65
|
+
@upload_prefix.split("/").first
|
66
|
+
end
|
67
|
+
end
|
68
|
+
alias_method :container, :bucket
|
69
|
+
|
59
70
|
def upload_path
|
60
|
-
|
61
|
-
|
62
|
-
else
|
71
|
+
without_bucket_path = @upload_prefix.sub(/^#{bucket}\/?/,"")
|
72
|
+
if without_bucket_path.empty?
|
63
73
|
@path
|
74
|
+
else
|
75
|
+
without_bucket_path + "/" + @path
|
64
76
|
end
|
65
77
|
end
|
66
78
|
|
79
|
+
def download_path
|
80
|
+
@download_prefix ? "#{@download_prefix}/#{@path}" : @path
|
81
|
+
end
|
82
|
+
|
83
|
+
def upload_path_without_bucket
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def full_download_path
|
88
|
+
[bucket, download_path].join("/")
|
89
|
+
end
|
90
|
+
|
67
91
|
def full_upload_path
|
68
92
|
[bucket, upload_path].join("/")
|
69
93
|
end
|
@@ -72,4 +96,4 @@ module Cloudsync
|
|
72
96
|
Tempfile.new(unique_filename)
|
73
97
|
end
|
74
98
|
end
|
75
|
-
end
|
99
|
+
end
|
@@ -74,14 +74,13 @@ module Cloudsync
|
|
74
74
|
|
75
75
|
$LOGGER.info("Prune from #{from_backend} to #{to_backend} started at #{prune_start = Time.now}. Dry-run? #{!!dry_run?}")
|
76
76
|
|
77
|
-
from_backend_files = [] # from_backend.files_to_sync(to_backend.upload_prefix)
|
78
77
|
to_backend_files = to_backend.files_to_sync(from_backend.upload_prefix)
|
79
78
|
total_files = to_backend_files.size
|
80
79
|
last_decile_complete = 0
|
81
80
|
|
82
81
|
to_backend_files.each_with_index do |file, index|
|
83
82
|
$LOGGER.debug("Checking if file #{file} exists on [#{from_backend}]")
|
84
|
-
if found_file = from_backend.find_file_from_list_or_store(file
|
83
|
+
if found_file = from_backend.find_file_from_list_or_store(file)
|
85
84
|
$LOGGER.debug("Keeping file #{file} because it was found on #{from_backend}.")
|
86
85
|
file_stats[:skipped] << file
|
87
86
|
else
|
@@ -108,12 +107,11 @@ module Cloudsync
|
|
108
107
|
$LOGGER.info("Sync from #{from_backend} to #{to_backend} started at #{sync_start = Time.now}. Mode: #{mode}. Dry-run? #{!!dry_run?}")
|
109
108
|
|
110
109
|
from_backend_files = from_backend.files_to_sync(to_backend.upload_prefix)
|
111
|
-
to_backend_files = [] # to_backend.files_to_sync(from_backend.upload_prefix)
|
112
110
|
total_files = from_backend_files.size
|
113
111
|
last_decile_complete = 0
|
114
112
|
|
115
113
|
from_backend_files.each_with_index do |file, index|
|
116
|
-
if (mode == :sync_all || to_backend.needs_update?(file
|
114
|
+
if (mode == :sync_all || to_backend.needs_update?(file))
|
117
115
|
file_stats[:copied] << file
|
118
116
|
from_backend.copy(file, to_backend)
|
119
117
|
else
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudsync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version:
|
9
|
+
- 0
|
10
|
+
version: 2.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Cory Forsyth
|