backup 3.0.23 → 3.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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