backup 3.0.23 → 3.0.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. data/Gemfile.lock +42 -45
  2. data/Guardfile +7 -4
  3. data/README.md +10 -7
  4. data/backup.gemspec +2 -2
  5. data/lib/backup.rb +27 -97
  6. data/lib/backup/archive.rb +14 -6
  7. data/lib/backup/cli/helpers.rb +52 -49
  8. data/lib/backup/cli/utility.rb +9 -1
  9. data/lib/backup/compressor/base.rb +10 -4
  10. data/lib/backup/compressor/bzip2.rb +22 -26
  11. data/lib/backup/compressor/custom.rb +53 -0
  12. data/lib/backup/compressor/gzip.rb +22 -23
  13. data/lib/backup/compressor/lzma.rb +15 -13
  14. data/lib/backup/compressor/pbzip2.rb +20 -17
  15. data/lib/backup/config.rb +6 -3
  16. data/lib/backup/configuration.rb +33 -0
  17. data/lib/backup/configuration/helpers.rb +114 -28
  18. data/lib/backup/configuration/store.rb +24 -0
  19. data/lib/backup/database/base.rb +0 -6
  20. data/lib/backup/database/mongodb.rb +27 -11
  21. data/lib/backup/database/mysql.rb +19 -14
  22. data/lib/backup/database/postgresql.rb +16 -11
  23. data/lib/backup/database/redis.rb +7 -11
  24. data/lib/backup/database/riak.rb +3 -6
  25. data/lib/backup/dependency.rb +5 -11
  26. data/lib/backup/model.rb +14 -5
  27. data/lib/backup/notifier/campfire.rb +3 -16
  28. data/lib/backup/notifier/hipchat.rb +1 -7
  29. data/lib/backup/notifier/mail.rb +1 -1
  30. data/lib/backup/packager.rb +29 -19
  31. data/lib/backup/pipeline.rb +110 -0
  32. data/lib/backup/storage/dropbox.rb +4 -7
  33. data/lib/backup/syncer/base.rb +8 -4
  34. data/lib/backup/syncer/cloud/base.rb +247 -0
  35. data/lib/backup/syncer/cloud/cloud_files.rb +78 -0
  36. data/lib/backup/syncer/cloud/s3.rb +68 -0
  37. data/lib/backup/syncer/rsync/base.rb +1 -4
  38. data/lib/backup/syncer/rsync/local.rb +9 -5
  39. data/lib/backup/syncer/rsync/pull.rb +1 -1
  40. data/lib/backup/syncer/rsync/push.rb +10 -5
  41. data/lib/backup/version.rb +1 -1
  42. data/spec-live/.gitignore +6 -0
  43. data/spec-live/README +7 -0
  44. data/spec-live/backups/config.rb +153 -0
  45. data/spec-live/backups/config.yml.template +43 -0
  46. data/spec-live/compressor/custom_spec.rb +30 -0
  47. data/spec-live/compressor/gzip_spec.rb +30 -0
  48. data/spec-live/notifier/mail_spec.rb +85 -0
  49. data/spec-live/spec_helper.rb +85 -0
  50. data/spec-live/storage/dropbox_spec.rb +151 -0
  51. data/spec-live/storage/local_spec.rb +83 -0
  52. data/spec-live/storage/scp_spec.rb +193 -0
  53. data/spec-live/syncer/cloud/s3_spec.rb +124 -0
  54. data/spec/archive_spec.rb +86 -31
  55. data/spec/cleaner_spec.rb +8 -0
  56. data/spec/cli/helpers_spec.rb +200 -75
  57. data/spec/cli/utility_spec.rb +11 -3
  58. data/spec/compressor/base_spec.rb +31 -10
  59. data/spec/compressor/bzip2_spec.rb +212 -57
  60. data/spec/compressor/custom_spec.rb +106 -0
  61. data/spec/compressor/gzip_spec.rb +212 -57
  62. data/spec/compressor/lzma_spec.rb +75 -35
  63. data/spec/compressor/pbzip2_spec.rb +93 -52
  64. data/spec/configuration/helpers_spec.rb +406 -0
  65. data/spec/configuration/store_spec.rb +39 -0
  66. data/spec/configuration_spec.rb +62 -0
  67. data/spec/database/base_spec.rb +19 -10
  68. data/spec/database/mongodb_spec.rb +195 -70
  69. data/spec/database/mysql_spec.rb +183 -64
  70. data/spec/database/postgresql_spec.rb +167 -53
  71. data/spec/database/redis_spec.rb +121 -46
  72. data/spec/database/riak_spec.rb +96 -27
  73. data/spec/dependency_spec.rb +2 -0
  74. data/spec/encryptor/base_spec.rb +10 -0
  75. data/spec/encryptor/gpg_spec.rb +29 -13
  76. data/spec/encryptor/open_ssl_spec.rb +40 -21
  77. data/spec/logger_spec.rb +4 -0
  78. data/spec/model_spec.rb +19 -2
  79. data/spec/notifier/base_spec.rb +32 -17
  80. data/spec/notifier/campfire_spec.rb +63 -45
  81. data/spec/notifier/hipchat_spec.rb +79 -56
  82. data/spec/notifier/mail_spec.rb +82 -46
  83. data/spec/notifier/prowl_spec.rb +53 -32
  84. data/spec/notifier/twitter_spec.rb +62 -41
  85. data/spec/packager_spec.rb +95 -36
  86. data/spec/pipeline_spec.rb +259 -0
  87. data/spec/spec_helper.rb +6 -5
  88. data/spec/storage/base_spec.rb +61 -41
  89. data/spec/storage/cloudfiles_spec.rb +69 -45
  90. data/spec/storage/dropbox_spec.rb +158 -36
  91. data/spec/storage/ftp_spec.rb +69 -45
  92. data/spec/storage/local_spec.rb +47 -23
  93. data/spec/storage/ninefold_spec.rb +55 -31
  94. data/spec/storage/rsync_spec.rb +67 -50
  95. data/spec/storage/s3_spec.rb +65 -41
  96. data/spec/storage/scp_spec.rb +65 -41
  97. data/spec/storage/sftp_spec.rb +65 -41
  98. data/spec/syncer/base_spec.rb +91 -4
  99. data/spec/syncer/cloud/base_spec.rb +511 -0
  100. data/spec/syncer/cloud/cloud_files_spec.rb +181 -0
  101. data/spec/syncer/cloud/s3_spec.rb +174 -0
  102. data/spec/syncer/rsync/base_spec.rb +46 -66
  103. data/spec/syncer/rsync/local_spec.rb +55 -26
  104. data/spec/syncer/rsync/pull_spec.rb +15 -4
  105. data/spec/syncer/rsync/push_spec.rb +59 -52
  106. data/templates/cli/utility/compressor/bzip2 +1 -4
  107. data/templates/cli/utility/compressor/custom +11 -0
  108. data/templates/cli/utility/compressor/gzip +1 -4
  109. data/templates/cli/utility/compressor/lzma +3 -0
  110. data/templates/cli/utility/compressor/pbzip2 +3 -0
  111. data/templates/cli/utility/database/mysql +4 -1
  112. data/templates/cli/utility/syncer/cloud_files +17 -19
  113. data/templates/cli/utility/syncer/s3 +18 -20
  114. metadata +38 -92
  115. data/lib/backup/configuration/base.rb +0 -15
  116. data/lib/backup/configuration/compressor/base.rb +0 -9
  117. data/lib/backup/configuration/compressor/bzip2.rb +0 -23
  118. data/lib/backup/configuration/compressor/gzip.rb +0 -23
  119. data/lib/backup/configuration/compressor/lzma.rb +0 -23
  120. data/lib/backup/configuration/compressor/pbzip2.rb +0 -28
  121. data/lib/backup/configuration/database/base.rb +0 -19
  122. data/lib/backup/configuration/database/mongodb.rb +0 -49
  123. data/lib/backup/configuration/database/mysql.rb +0 -42
  124. data/lib/backup/configuration/database/postgresql.rb +0 -41
  125. data/lib/backup/configuration/database/redis.rb +0 -39
  126. data/lib/backup/configuration/database/riak.rb +0 -29
  127. data/lib/backup/configuration/encryptor/base.rb +0 -9
  128. data/lib/backup/configuration/encryptor/gpg.rb +0 -17
  129. data/lib/backup/configuration/encryptor/open_ssl.rb +0 -32
  130. data/lib/backup/configuration/notifier/base.rb +0 -28
  131. data/lib/backup/configuration/notifier/campfire.rb +0 -25
  132. data/lib/backup/configuration/notifier/hipchat.rb +0 -41
  133. data/lib/backup/configuration/notifier/mail.rb +0 -112
  134. data/lib/backup/configuration/notifier/presently.rb +0 -25
  135. data/lib/backup/configuration/notifier/prowl.rb +0 -23
  136. data/lib/backup/configuration/notifier/twitter.rb +0 -21
  137. data/lib/backup/configuration/storage/base.rb +0 -18
  138. data/lib/backup/configuration/storage/cloudfiles.rb +0 -25
  139. data/lib/backup/configuration/storage/dropbox.rb +0 -58
  140. data/lib/backup/configuration/storage/ftp.rb +0 -29
  141. data/lib/backup/configuration/storage/local.rb +0 -17
  142. data/lib/backup/configuration/storage/ninefold.rb +0 -20
  143. data/lib/backup/configuration/storage/rsync.rb +0 -29
  144. data/lib/backup/configuration/storage/s3.rb +0 -25
  145. data/lib/backup/configuration/storage/scp.rb +0 -25
  146. data/lib/backup/configuration/storage/sftp.rb +0 -25
  147. data/lib/backup/configuration/syncer/base.rb +0 -10
  148. data/lib/backup/configuration/syncer/cloud.rb +0 -23
  149. data/lib/backup/configuration/syncer/cloud_files.rb +0 -30
  150. data/lib/backup/configuration/syncer/rsync/base.rb +0 -28
  151. data/lib/backup/configuration/syncer/rsync/local.rb +0 -11
  152. data/lib/backup/configuration/syncer/rsync/pull.rb +0 -11
  153. data/lib/backup/configuration/syncer/rsync/push.rb +0 -31
  154. data/lib/backup/configuration/syncer/s3.rb +0 -23
  155. data/lib/backup/notifier/presently.rb +0 -88
  156. data/lib/backup/syncer/cloud.rb +0 -187
  157. data/lib/backup/syncer/cloud_files.rb +0 -56
  158. data/lib/backup/syncer/s3.rb +0 -47
  159. data/spec/configuration/base_spec.rb +0 -35
  160. data/spec/configuration/compressor/bzip2_spec.rb +0 -29
  161. data/spec/configuration/compressor/gzip_spec.rb +0 -29
  162. data/spec/configuration/compressor/lzma_spec.rb +0 -29
  163. data/spec/configuration/compressor/pbzip2_spec.rb +0 -32
  164. data/spec/configuration/database/base_spec.rb +0 -17
  165. data/spec/configuration/database/mongodb_spec.rb +0 -56
  166. data/spec/configuration/database/mysql_spec.rb +0 -53
  167. data/spec/configuration/database/postgresql_spec.rb +0 -53
  168. data/spec/configuration/database/redis_spec.rb +0 -50
  169. data/spec/configuration/database/riak_spec.rb +0 -35
  170. data/spec/configuration/encryptor/gpg_spec.rb +0 -26
  171. data/spec/configuration/encryptor/open_ssl_spec.rb +0 -35
  172. data/spec/configuration/notifier/base_spec.rb +0 -32
  173. data/spec/configuration/notifier/campfire_spec.rb +0 -32
  174. data/spec/configuration/notifier/hipchat_spec.rb +0 -44
  175. data/spec/configuration/notifier/mail_spec.rb +0 -71
  176. data/spec/configuration/notifier/presently_spec.rb +0 -35
  177. data/spec/configuration/notifier/prowl_spec.rb +0 -29
  178. data/spec/configuration/notifier/twitter_spec.rb +0 -35
  179. data/spec/configuration/storage/cloudfiles_spec.rb +0 -41
  180. data/spec/configuration/storage/dropbox_spec.rb +0 -38
  181. data/spec/configuration/storage/ftp_spec.rb +0 -44
  182. data/spec/configuration/storage/local_spec.rb +0 -29
  183. data/spec/configuration/storage/ninefold_spec.rb +0 -32
  184. data/spec/configuration/storage/rsync_spec.rb +0 -41
  185. data/spec/configuration/storage/s3_spec.rb +0 -38
  186. data/spec/configuration/storage/scp_spec.rb +0 -41
  187. data/spec/configuration/storage/sftp_spec.rb +0 -41
  188. data/spec/configuration/syncer/cloud_files_spec.rb +0 -44
  189. data/spec/configuration/syncer/rsync/base_spec.rb +0 -33
  190. data/spec/configuration/syncer/rsync/local_spec.rb +0 -10
  191. data/spec/configuration/syncer/rsync/pull_spec.rb +0 -10
  192. data/spec/configuration/syncer/rsync/push_spec.rb +0 -43
  193. data/spec/configuration/syncer/s3_spec.rb +0 -38
  194. data/spec/notifier/presently_spec.rb +0 -181
  195. data/spec/syncer/cloud_files_spec.rb +0 -192
  196. data/spec/syncer/s3_spec.rb +0 -192
  197. data/templates/cli/utility/notifier/presently +0 -13
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Pipeline
5
+ include Backup::CLI::Helpers
6
+
7
+ attr_reader :stderr, :errors
8
+
9
+ def initialize
10
+ @commands = []
11
+ @errors = []
12
+ @stderr = ''
13
+ end
14
+
15
+ ##
16
+ # Adds a command to be executed in the pipeline.
17
+ # Each command will be run in the order in which it was added,
18
+ # with it's output being piped to the next command.
19
+ def <<(command)
20
+ @commands << command
21
+ end
22
+
23
+ ##
24
+ # Runs the command line from `#pipeline` and collects STDOUT/STDERR.
25
+ # STDOUT is then parsed to determine the exit status of each command.
26
+ # For each command with a non-zero exit status, a SystemCallError is
27
+ # created and added to @errors. All STDERR output is set in @stderr.
28
+ #
29
+ # Note that there is no accumulated STDOUT from the commands themselves.
30
+ # Also, the last command should not attempt to write to STDOUT.
31
+ # Any output on STDOUT from the final command will be sent to STDERR.
32
+ # This in itself will not cause #run to fail, but will log warnings
33
+ # when all commands exit with non-zero status.
34
+ #
35
+ # Use `#success?` to determine if all commands in the pipeline succeeded.
36
+ # If `#success?` returns `false`, use `#error_messages` to get an error report.
37
+ def run
38
+ Open4.popen4(pipeline) do |pid, stdin, stdout, stderr|
39
+ pipestatus = stdout.read.gsub("\n", '').split(':').sort
40
+ pipestatus.each do |status|
41
+ index, exitstatus = status.split('|').map(&:to_i)
42
+ if exitstatus > 0
43
+ command = command_name(@commands[index])
44
+ @errors << SystemCallError.new(
45
+ "'#{ command }' returned exit code: #{ exitstatus }", exitstatus
46
+ )
47
+ end
48
+ end
49
+ @stderr = stderr.read.strip
50
+ end
51
+ Logger.warn(stderr_messages) if success? && stderr_messages
52
+ rescue Exception => e
53
+ raise Errors::Pipeline::ExecutionError.wrap(e)
54
+ end
55
+
56
+ def success?
57
+ @errors.empty?
58
+ end
59
+
60
+ ##
61
+ # Returns a multi-line String, reporting all STDERR messages received
62
+ # from the commands in the pipeline (if any), along with the SystemCallError
63
+ # (Errno) message for each command which had a non-zero exit status.
64
+ #
65
+ # Each error is wrapped by Backup::Errors to provide formatting.
66
+ def error_messages
67
+ @error_messages ||= (stderr_messages || '') +
68
+ "The following system errors were returned:\n" +
69
+ @errors.map {|err| Errors::Error.wrap(err).message }.join("\n")
70
+ end
71
+
72
+ private
73
+
74
+ ##
75
+ # Each command is added as part of the pipeline, grouped with an `echo`
76
+ # command to pass along the command's index in @commands and it's exit status.
77
+ # The command's STDERR is redirected to FD#4, and the `echo` command to
78
+ # report the "index|exit status" is redirected to FD#3.
79
+ # Each command's STDOUT will be connected to the STDIN of the next subshell.
80
+ # The entire pipeline is run within a container group, which redirects
81
+ # FD#3 to STDOUT and FD#4 to STDERR so these can be collected.
82
+ # FD#1 is redirected to STDERR so that any output from the final command
83
+ # on STDOUT will generate warnings, since the final command should not
84
+ # attempt to write to STDOUT, as this would interfere with collecting
85
+ # the exit statuses.
86
+ #
87
+ # There is no guarantee as to the order of this output, which is why the
88
+ # command's index in @commands is passed along with it's exit status.
89
+ # And, if multiple commands output messages on STDERR, those messages
90
+ # may be interleaved. Interleaving of the "index|exit status" outputs
91
+ # should not be an issue, given the small byte size of the data being written.
92
+ def pipeline
93
+ parts = []
94
+ @commands.each_with_index do |command, index|
95
+ parts << %Q[{ #{ command } 2>&4 ; echo "#{ index }|$?:" >&3 ; }]
96
+ end
97
+ %Q[{ #{ parts.join(' | ') } } 3>&1 1>&2 4>&2]
98
+ end
99
+
100
+ def stderr_messages
101
+ @stderr_messages ||= @stderr.empty? ? false : <<-EOS.gsub(/^ +/, ' ')
102
+ Pipeline STDERR Messages:
103
+ (Note: may be interleaved if multiple commands returned error messages)
104
+
105
+ #{ @stderr }
106
+ EOS
107
+ end
108
+
109
+ end
110
+ end
@@ -23,13 +23,10 @@ module Backup
23
23
  # Path to where the backups will be stored
24
24
  attr_accessor :path
25
25
 
26
- # Deprecated as of v3.0.21 - for move to official 'dropbox-sdk' gem (v1.1)
27
- def timeout=(value)
28
- if value
29
- Logger.warn "[DEPRECATED] Backup::Storage::Dropbox.timeout=\n" +
30
- " is deprecated and will be removed at some point."
31
- end
32
- end
26
+ attr_deprecate :email, :version => '3.0.17'
27
+ attr_deprecate :password, :version => '3.0.17'
28
+
29
+ attr_deprecate :timeout, :version => '3.0.21'
33
30
 
34
31
  ##
35
32
  # Creates a new instance of the storage object
@@ -6,10 +6,6 @@ module Backup
6
6
  include Backup::CLI::Helpers
7
7
  include Backup::Configuration::Helpers
8
8
 
9
- ##
10
- # Directories to sync
11
- attr_accessor :directories
12
-
13
9
  ##
14
10
  # Path to store the synced files/directories to
15
11
  attr_accessor :path
@@ -18,6 +14,14 @@ module Backup
18
14
  # Flag for mirroring the files/directories
19
15
  attr_accessor :mirror
20
16
 
17
+ def initialize
18
+ load_defaults!
19
+
20
+ @path ||= 'backups'
21
+ @mirror ||= false
22
+ @directories = Array.new
23
+ end
24
+
21
25
  ##
22
26
  # Syntactical suger for the DSL for adding directories
23
27
  def directories(&block)
@@ -0,0 +1,247 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ # Only load the Fog gem, along with the Parallel gem, when the
5
+ # Backup::Syncer::Cloud class is loaded
6
+ Backup::Dependency.load('fog')
7
+ Backup::Dependency.load('parallel')
8
+
9
+ module Backup
10
+ module Syncer
11
+ module Cloud
12
+ class Base < Syncer::Base
13
+
14
+ ##
15
+ # Create a Mutex to synchronize certain parts of the code
16
+ # in order to prevent race conditions or broken STDOUT.
17
+ MUTEX = Mutex.new
18
+
19
+ ##
20
+ # Concurrency setting - defaults to false, but can be set to:
21
+ # - :threads
22
+ # - :processes
23
+ attr_accessor :concurrency_type
24
+
25
+ ##
26
+ # Concurrency level - the number of threads or processors to use.
27
+ # Defaults to 2.
28
+ attr_accessor :concurrency_level
29
+
30
+ ##
31
+ # Instantiates a new Cloud Syncer object for either
32
+ # the Cloud::S3 or Cloud::CloudFiles Syncer.
33
+ #
34
+ # Pre-configured defaults specified in either
35
+ # Configuration::Syncer::Cloud::S3 or
36
+ # Configuration::Syncer::Cloud::CloudFiles
37
+ # are set via a super() call to Syncer::Base.
38
+ #
39
+ # If not specified in the pre-configured defaults,
40
+ # the Cloud specific defaults are set here before evaluating
41
+ # any block provided in the user's configuration file.
42
+ def initialize
43
+ super
44
+
45
+ @concurrency_type ||= false
46
+ @concurrency_level ||= 2
47
+ end
48
+
49
+ ##
50
+ # Performs the Sync operation
51
+ def perform!
52
+ Logger.message("#{ syncer_name } started the syncing process:")
53
+
54
+ @directories.each do |directory|
55
+ SyncContext.new(
56
+ File.expand_path(directory), repository_object, @path
57
+ ).sync! @mirror, @concurrency_type, @concurrency_level
58
+ end
59
+
60
+ Logger.message("#{ syncer_name } Syncing Complete!")
61
+ end
62
+
63
+ private
64
+
65
+ class SyncContext
66
+ attr_reader :directory, :bucket, :path, :remote_base
67
+
68
+ ##
69
+ # Creates a new SyncContext object which handles a single directory
70
+ # from the Syncer::Base @directories array.
71
+ def initialize(directory, bucket, path)
72
+ @directory, @bucket, @path = directory, bucket, path
73
+ @remote_base = File.join(path, File.basename(directory))
74
+ end
75
+
76
+ ##
77
+ # Performs the sync operation using the provided techniques
78
+ # (mirroring/concurrency).
79
+ def sync!(mirror = false, concurrency_type = false, concurrency_level = 2)
80
+ block = Proc.new { |relative_path| sync_file relative_path, mirror }
81
+
82
+ case concurrency_type
83
+ when FalseClass
84
+ all_file_names.each &block
85
+ when :threads
86
+ Parallel.each all_file_names,
87
+ :in_threads => concurrency_level, &block
88
+ when :processes
89
+ Parallel.each all_file_names,
90
+ :in_processes => concurrency_level, &block
91
+ else
92
+ raise Errors::Syncer::Cloud::ConfigurationError,
93
+ "Unknown concurrency_type setting: #{ concurrency_type.inspect }"
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ ##
100
+ # Gathers all the relative paths to the local files
101
+ # and merges them with the , removing
102
+ # duplicate keys if any, and sorts the in alphabetical order.
103
+ def all_file_names
104
+ @all_file_names ||= (local_files.keys | remote_files.keys).sort
105
+ end
106
+
107
+ ##
108
+ # Returns a Hash of local files, validated to ensure the path
109
+ # does not contain invalid UTF-8 byte sequences.
110
+ # The keys are the filesystem paths, relative to @directory.
111
+ # The values are the LocalFile objects for that given file.
112
+ def local_files
113
+ @local_files ||= begin
114
+ hash = {}
115
+ local_hashes.lines.map do |line|
116
+ LocalFile.new(@directory, line)
117
+ end.compact.each do |file|
118
+ hash.merge!(file.relative_path => file)
119
+ end
120
+ hash
121
+ end
122
+ end
123
+
124
+ ##
125
+ # Returns a String of file paths and their md5 hashes.
126
+ def local_hashes
127
+ MUTEX.synchronize {
128
+ Logger.message("\s\sGenerating checksums for '#{ @directory }'")
129
+ }
130
+ `find #{ @directory } -print0 | xargs -0 openssl md5 2> /dev/null`
131
+ end
132
+
133
+ ##
134
+ # Returns a Hash of remote files
135
+ # The keys are the remote paths, relative to @remote_base
136
+ # The values are the Fog file objects for that given file
137
+ def remote_files
138
+ @remote_files ||= begin
139
+ hash = {}
140
+ @bucket.files.all(:prefix => @remote_base).each do |file|
141
+ hash.merge!(file.key.sub("#{ @remote_base }/", '') => file)
142
+ end
143
+ hash
144
+ end
145
+ end
146
+
147
+ ##
148
+ # Performs a sync operation on a file. When mirroring is enabled
149
+ # and a local file has been removed since the last sync, it will also
150
+ # remove it from the remote location. It will no upload files that
151
+ # have not changed since the last sync. Checks are done using an md5
152
+ # hash. If a file has changed, or has been newly added, the file will
153
+ # be transferred/overwritten.
154
+ def sync_file(relative_path, mirror)
155
+ local_file = local_files[relative_path]
156
+ remote_file = remote_files[relative_path]
157
+ remote_path = File.join(@remote_base, relative_path)
158
+
159
+ if local_file && File.exist?(local_file.path)
160
+ unless remote_file && remote_file.etag == local_file.md5
161
+ MUTEX.synchronize {
162
+ Logger.message("\s\s[transferring] '#{ remote_path }'")
163
+ }
164
+ File.open(local_file.path, 'r') do |file|
165
+ @bucket.files.create(
166
+ :key => remote_path,
167
+ :body => file
168
+ )
169
+ end
170
+ else
171
+ MUTEX.synchronize {
172
+ Logger.message("\s\s[skipping] '#{ remote_path }'")
173
+ }
174
+ end
175
+ elsif remote_file
176
+ if mirror
177
+ MUTEX.synchronize {
178
+ Logger.message("\s\s[removing] '#{ remote_path }'")
179
+ }
180
+ remote_file.destroy
181
+ else
182
+ MUTEX.synchronize {
183
+ Logger.message("\s\s[leaving] '#{ remote_path }'")
184
+ }
185
+ end
186
+ end
187
+ end
188
+ end # class SyncContext
189
+
190
+ class LocalFile
191
+ attr_reader :path, :relative_path, :md5
192
+
193
+ ##
194
+ # Return a new LocalFile object if it's valid.
195
+ # Otherwise, log a warning and return nil.
196
+ def self.new(*args)
197
+ local_file = super(*args)
198
+ if local_file.invalid?
199
+ MUTEX.synchronize {
200
+ Logger.warn(
201
+ "\s\s[skipping] #{ local_file.path }\n" +
202
+ "\s\sPath Contains Invalid UTF-8 byte sequences"
203
+ )
204
+ }
205
+ return nil
206
+ end
207
+ local_file
208
+ end
209
+
210
+ ##
211
+ # Creates a new LocalFile object using the given directory and line
212
+ # from the md5 hash checkup. This object figures out the path,
213
+ # relative_path and md5 hash for the file.
214
+ def initialize(directory, line)
215
+ @invalid = false
216
+ @directory = sanitize(directory)
217
+ @path, @md5 = sanitize(line).chomp.
218
+ match(/^MD5\(([^\)]+)\)= (\w+)$/).captures
219
+ @relative_path = @path.sub(@directory + '/', '')
220
+ end
221
+
222
+ def invalid?
223
+ @invalid
224
+ end
225
+
226
+ private
227
+
228
+ ##
229
+ # Sanitize string and replace any invalid UTF-8 characters.
230
+ # If replacements are made, flag the LocalFile object as invalid.
231
+ def sanitize(str)
232
+ str.each_char.map do |char|
233
+ begin
234
+ char if !!char.unpack('U')
235
+ rescue
236
+ @invalid = true
237
+ "\xEF\xBF\xBD" # => "\uFFFD"
238
+ end
239
+ end.join
240
+ end
241
+
242
+ end # class LocalFile
243
+
244
+ end # class Base < Syncer::Base
245
+ end # module Cloud
246
+ end
247
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Syncer
5
+ module Cloud
6
+ class CloudFiles < Base
7
+
8
+ ##
9
+ # Rackspace CloudFiles Credentials
10
+ attr_accessor :api_key, :username
11
+
12
+ ##
13
+ # Rackspace CloudFiles Container
14
+ attr_accessor :container
15
+
16
+ ##
17
+ # Rackspace AuthURL allows you to connect
18
+ # to a different Rackspace datacenter
19
+ # - https://auth.api.rackspacecloud.com (Default: US)
20
+ # - https://lon.auth.api.rackspacecloud.com (UK)
21
+ attr_accessor :auth_url
22
+
23
+ ##
24
+ # Improve performance and avoid data transfer costs
25
+ # by setting @servicenet to `true`
26
+ # This only works if Backup runs on a Rackspace server
27
+ attr_accessor :servicenet
28
+
29
+ ##
30
+ # Instantiates a new Cloud::CloudFiles Syncer.
31
+ #
32
+ # Pre-configured defaults specified in
33
+ # Configuration::Syncer::Cloud::CloudFiles
34
+ # are set via a super() call to Cloud::Base,
35
+ # which in turn will invoke Syncer::Base.
36
+ #
37
+ # Once pre-configured defaults and Cloud specific defaults are set,
38
+ # the block from the user's configuration file is evaluated.
39
+ def initialize(&block)
40
+ super
41
+
42
+ instance_eval(&block) if block_given?
43
+ @path = path.sub(/^\//, '')
44
+ end
45
+
46
+ private
47
+
48
+ ##
49
+ # Established and creates a new Fog storage object for CloudFiles.
50
+ def connection
51
+ @connection ||= Fog::Storage.new(
52
+ :provider => provider,
53
+ :rackspace_username => username,
54
+ :rackspace_api_key => api_key,
55
+ :rackspace_auth_url => auth_url,
56
+ :rackspace_servicenet => servicenet
57
+ )
58
+ end
59
+
60
+ ##
61
+ # Creates a new @repository_object (container).
62
+ # Fetches it from Cloud Files if it already exists,
63
+ # otherwise it will create it first and fetch use that instead.
64
+ def repository_object
65
+ @repository_object ||= connection.directories.get(container) ||
66
+ connection.directories.create(:key => container)
67
+ end
68
+
69
+ ##
70
+ # This is the provider that Fog uses for the Cloud Files
71
+ def provider
72
+ "Rackspace"
73
+ end
74
+
75
+ end # class Cloudfiles < Base
76
+ end # module Cloud
77
+ end
78
+ end