alg-backup 3.0.10
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/.gitignore +2 -0
- data/.infinity_test +7 -0
- data/.rspec +3 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +101 -0
- data/LICENSE.md +24 -0
- data/README.md +276 -0
- data/backup.gemspec +39 -0
- data/bin/backup +260 -0
- data/lib/backup.rb +168 -0
- data/lib/backup/archive.rb +73 -0
- data/lib/backup/cli.rb +50 -0
- data/lib/backup/compressor/base.rb +17 -0
- data/lib/backup/compressor/gzip.rb +61 -0
- data/lib/backup/configuration/base.rb +15 -0
- data/lib/backup/configuration/compressor/base.rb +10 -0
- data/lib/backup/configuration/compressor/gzip.rb +23 -0
- data/lib/backup/configuration/database/base.rb +18 -0
- data/lib/backup/configuration/database/mongodb.rb +37 -0
- data/lib/backup/configuration/database/mysql.rb +37 -0
- data/lib/backup/configuration/database/postgresql.rb +37 -0
- data/lib/backup/configuration/database/redis.rb +35 -0
- data/lib/backup/configuration/encryptor/base.rb +10 -0
- data/lib/backup/configuration/encryptor/gpg.rb +17 -0
- data/lib/backup/configuration/encryptor/open_ssl.rb +26 -0
- data/lib/backup/configuration/helpers.rb +54 -0
- data/lib/backup/configuration/notifier/base.rb +39 -0
- data/lib/backup/configuration/notifier/mail.rb +52 -0
- data/lib/backup/configuration/notifier/twitter.rb +21 -0
- data/lib/backup/configuration/storage/base.rb +18 -0
- data/lib/backup/configuration/storage/cloudfiles.rb +21 -0
- data/lib/backup/configuration/storage/dropbox.rb +29 -0
- data/lib/backup/configuration/storage/ftp.rb +25 -0
- data/lib/backup/configuration/storage/rsync.rb +25 -0
- data/lib/backup/configuration/storage/s3.rb +25 -0
- data/lib/backup/configuration/storage/scp.rb +25 -0
- data/lib/backup/configuration/storage/sftp.rb +25 -0
- data/lib/backup/configuration/syncer/rsync.rb +45 -0
- data/lib/backup/configuration/syncer/s3.rb +33 -0
- data/lib/backup/database/base.rb +33 -0
- data/lib/backup/database/mongodb.rb +137 -0
- data/lib/backup/database/mysql.rb +104 -0
- data/lib/backup/database/postgresql.rb +111 -0
- data/lib/backup/database/redis.rb +105 -0
- data/lib/backup/dependency.rb +84 -0
- data/lib/backup/encryptor/base.rb +17 -0
- data/lib/backup/encryptor/gpg.rb +78 -0
- data/lib/backup/encryptor/open_ssl.rb +67 -0
- data/lib/backup/finder.rb +39 -0
- data/lib/backup/logger.rb +86 -0
- data/lib/backup/model.rb +272 -0
- data/lib/backup/notifier/base.rb +29 -0
- data/lib/backup/notifier/binder.rb +32 -0
- data/lib/backup/notifier/mail.rb +141 -0
- data/lib/backup/notifier/templates/notify_failure.erb +31 -0
- data/lib/backup/notifier/templates/notify_success.erb +16 -0
- data/lib/backup/notifier/twitter.rb +87 -0
- data/lib/backup/storage/base.rb +67 -0
- data/lib/backup/storage/cloudfiles.rb +95 -0
- data/lib/backup/storage/dropbox.rb +87 -0
- data/lib/backup/storage/ftp.rb +114 -0
- data/lib/backup/storage/object.rb +45 -0
- data/lib/backup/storage/rsync.rb +99 -0
- data/lib/backup/storage/s3.rb +108 -0
- data/lib/backup/storage/scp.rb +106 -0
- data/lib/backup/storage/sftp.rb +106 -0
- data/lib/backup/syncer/base.rb +10 -0
- data/lib/backup/syncer/rsync.rb +117 -0
- data/lib/backup/syncer/s3.rb +116 -0
- data/lib/backup/version.rb +43 -0
- data/lib/templates/archive +4 -0
- data/lib/templates/compressor/gzip +4 -0
- data/lib/templates/database/mongodb +10 -0
- data/lib/templates/database/mysql +11 -0
- data/lib/templates/database/postgresql +11 -0
- data/lib/templates/database/redis +10 -0
- data/lib/templates/encryptor/gpg +9 -0
- data/lib/templates/encryptor/openssl +5 -0
- data/lib/templates/notifier/mail +14 -0
- data/lib/templates/notifier/twitter +9 -0
- data/lib/templates/readme +15 -0
- data/lib/templates/storage/cloudfiles +7 -0
- data/lib/templates/storage/dropbox +9 -0
- data/lib/templates/storage/ftp +8 -0
- data/lib/templates/storage/rsync +7 -0
- data/lib/templates/storage/s3 +8 -0
- data/lib/templates/storage/scp +8 -0
- data/lib/templates/storage/sftp +8 -0
- data/lib/templates/syncer/rsync +14 -0
- data/lib/templates/syncer/s3 +12 -0
- data/spec/archive_spec.rb +90 -0
- data/spec/backup_spec.rb +11 -0
- data/spec/compressor/gzip_spec.rb +59 -0
- data/spec/configuration/base_spec.rb +35 -0
- data/spec/configuration/compressor/gzip_spec.rb +28 -0
- data/spec/configuration/database/base_spec.rb +16 -0
- data/spec/configuration/database/mongodb_spec.rb +30 -0
- data/spec/configuration/database/mysql_spec.rb +32 -0
- data/spec/configuration/database/postgresql_spec.rb +32 -0
- data/spec/configuration/database/redis_spec.rb +30 -0
- data/spec/configuration/encryptor/gpg_spec.rb +25 -0
- data/spec/configuration/encryptor/open_ssl_spec.rb +31 -0
- data/spec/configuration/notifier/mail_spec.rb +32 -0
- data/spec/configuration/storage/cloudfiles_spec.rb +34 -0
- data/spec/configuration/storage/dropbox_spec.rb +43 -0
- data/spec/configuration/storage/ftp_spec.rb +40 -0
- data/spec/configuration/storage/rsync_spec.rb +37 -0
- data/spec/configuration/storage/s3_spec.rb +37 -0
- data/spec/configuration/storage/scp_spec.rb +40 -0
- data/spec/configuration/storage/sftp_spec.rb +40 -0
- data/spec/configuration/syncer/rsync_spec.rb +46 -0
- data/spec/configuration/syncer/s3_spec.rb +43 -0
- data/spec/database/base_spec.rb +30 -0
- data/spec/database/mongodb_spec.rb +144 -0
- data/spec/database/mysql_spec.rb +150 -0
- data/spec/database/postgresql_spec.rb +164 -0
- data/spec/database/redis_spec.rb +122 -0
- data/spec/encryptor/gpg_spec.rb +57 -0
- data/spec/encryptor/open_ssl_spec.rb +102 -0
- data/spec/logger_spec.rb +46 -0
- data/spec/model_spec.rb +236 -0
- data/spec/notifier/mail_spec.rb +97 -0
- data/spec/notifier/twitter_spec.rb +86 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/storage/base_spec.rb +33 -0
- data/spec/storage/cloudfiles_spec.rb +102 -0
- data/spec/storage/dropbox_spec.rb +105 -0
- data/spec/storage/ftp_spec.rb +133 -0
- data/spec/storage/object_spec.rb +74 -0
- data/spec/storage/rsync_spec.rb +115 -0
- data/spec/storage/s3_spec.rb +110 -0
- data/spec/storage/scp_spec.rb +129 -0
- data/spec/storage/sftp_spec.rb +125 -0
- data/spec/syncer/rsync_spec.rb +156 -0
- data/spec/syncer/s3_spec.rb +139 -0
- data/spec/version_spec.rb +21 -0
- metadata +217 -0
data/lib/backup/cli.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module CLI
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# Wrapper method for %x[] to run CL commands
|
|
8
|
+
# through a ruby method. This helps with test coverage and
|
|
9
|
+
# improves readability
|
|
10
|
+
def run(command)
|
|
11
|
+
%x[#{command}]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Wrapper method for FileUtils.mkdir_p to create directories
|
|
16
|
+
# through a ruby method. This helps with test coverage and
|
|
17
|
+
# improves readability
|
|
18
|
+
def mkdir(path)
|
|
19
|
+
FileUtils.mkdir_p(path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Wrapper for the FileUtils.rm_rf to remove files and folders
|
|
24
|
+
# through a ruby method. This helps with test coverage and
|
|
25
|
+
# improves readability
|
|
26
|
+
def rm(path)
|
|
27
|
+
FileUtils.rm_rf(path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Tries to find the full path of the specified utility. If the full
|
|
32
|
+
# path is found, it'll return that. Otherwise it'll just return the
|
|
33
|
+
# name of the utility. If the 'utility_path' is defined, it'll check
|
|
34
|
+
# to see if it isn't an empty string, and if it isn't, it'll go ahead and
|
|
35
|
+
# always use that path rather than auto-detecting it
|
|
36
|
+
def utility(name)
|
|
37
|
+
if respond_to?(:utility_path)
|
|
38
|
+
if utility_path.is_a?(String) and not utility_path.empty?
|
|
39
|
+
return utility_path
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if path = %x[which #{name}].chomp and not path.empty?
|
|
44
|
+
return path
|
|
45
|
+
end
|
|
46
|
+
name
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Compressor
|
|
5
|
+
class Base
|
|
6
|
+
include Backup::CLI
|
|
7
|
+
include Backup::Configuration::Helpers
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Logs a message to the console and log file to inform
|
|
11
|
+
# the client that Backup is compressing the archive
|
|
12
|
+
def log!
|
|
13
|
+
Backup::Logger.message "#{ self.class } started compressing the archive."
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Compressor
|
|
5
|
+
class Gzip < Base
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Tells Backup::Compressor::Gzip to compress
|
|
9
|
+
# better rather than faster when set to true
|
|
10
|
+
attr_writer :best
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Tells Backup::Compressor::Gzip to compress
|
|
14
|
+
# faster rather than better when set to true
|
|
15
|
+
attr_writer :fast
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Creates a new instance of Backup::Compressor::Gzip and
|
|
19
|
+
# configures it to either compress faster or better
|
|
20
|
+
def initialize(&block)
|
|
21
|
+
load_defaults!
|
|
22
|
+
|
|
23
|
+
@best ||= false
|
|
24
|
+
@fast ||= false
|
|
25
|
+
|
|
26
|
+
instance_eval(&block) if block_given?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Performs the compression of the packages backup file
|
|
31
|
+
def perform!
|
|
32
|
+
log!
|
|
33
|
+
run("#{ utility(:gzip) } #{ options } '#{ Backup::Model.file }'")
|
|
34
|
+
Backup::Model.extension += '.gz'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# Combines the provided options and returns a gzip options string
|
|
41
|
+
def options
|
|
42
|
+
(best + fast).join("\s")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Returns the gzip option syntax for compressing
|
|
47
|
+
# better when @best is set to true
|
|
48
|
+
def best
|
|
49
|
+
return ['--best'] if @best; []
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Returns the gzip option syntax for compressing
|
|
54
|
+
# faster when @fast is set to true
|
|
55
|
+
def fast
|
|
56
|
+
return ['--fast'] if @fast; []
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Compressor
|
|
6
|
+
class Gzip < Base
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Tells Backup::Compressor::Gzip to compress
|
|
11
|
+
# better rather than faster when set to true
|
|
12
|
+
attr_accessor :best
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Tells Backup::Compressor::Gzip to compress
|
|
16
|
+
# faster rather than better when set to true
|
|
17
|
+
attr_accessor :fast
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Database
|
|
6
|
+
class Base < Backup::Configuration::Base
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Allows the user to specify the path to a "dump" utility
|
|
11
|
+
# in case it cannot be auto-detected by Backup
|
|
12
|
+
attr_accessor :utility_path
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Database
|
|
6
|
+
class MongoDB < Base
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Name of the database that needs to get dumped
|
|
11
|
+
attr_accessor :name
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Credentials for the specified database
|
|
15
|
+
attr_accessor :username, :password
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Connectivity options
|
|
19
|
+
attr_accessor :host, :port
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# IPv6 support (disabled by default)
|
|
23
|
+
attr_accessor :ipv6
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Collections to dump, collections that aren't specified won't get dumped
|
|
27
|
+
attr_accessor :only_collections
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Additional "mongodump" options
|
|
31
|
+
attr_accessor :additional_options
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Database
|
|
6
|
+
class MySQL < Base
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Name of the database that needs to get dumped
|
|
11
|
+
attr_accessor :name
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Credentials for the specified database
|
|
15
|
+
attr_accessor :username, :password
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Connectivity options
|
|
19
|
+
attr_accessor :host, :port, :socket
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Tables to skip while dumping the database
|
|
23
|
+
attr_accessor :skip_tables
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Tables to dump, tables that aren't specified won't get dumped
|
|
27
|
+
attr_accessor :only_tables
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Additional "mysqldump" options
|
|
31
|
+
attr_accessor :additional_options
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Database
|
|
6
|
+
class PostgreSQL < Base
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Name of the database that needs to get dumped
|
|
11
|
+
attr_accessor :name
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Credentials for the specified database
|
|
15
|
+
attr_accessor :username, :password
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Connectivity options
|
|
19
|
+
attr_accessor :host, :port, :socket
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Tables to skip while dumping the database
|
|
23
|
+
attr_accessor :skip_tables
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Tables to dump, tables that aren't specified won't get dumped
|
|
27
|
+
attr_accessor :only_tables
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Additional "pg_dump" options
|
|
31
|
+
attr_accessor :additional_options
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Database
|
|
6
|
+
class Redis < Base
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Name of and path to the database that needs to get dumped
|
|
11
|
+
attr_accessor :name, :path
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Credentials for the specified database
|
|
15
|
+
attr_accessor :password
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Determines whether Backup should invoke the SAVE command through
|
|
19
|
+
# the 'redis-cli' utility to persist the most recent data before
|
|
20
|
+
# copying over the dump file
|
|
21
|
+
attr_accessor :invoke_save
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Connectivity options
|
|
25
|
+
attr_accessor :host, :port, :socket
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Additional "redis-cli" options
|
|
29
|
+
attr_accessor :additional_options
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Encryptor
|
|
6
|
+
class OpenSSL < Base
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# The password that'll be used to encrypt the backup. This
|
|
11
|
+
# password will be required to decrypt the backup later on.
|
|
12
|
+
attr_accessor :password
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Determines whether the 'base64' should be used or not
|
|
16
|
+
attr_accessor :base64
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Determines whether the 'salt' flag should be used
|
|
20
|
+
attr_accessor :salt
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Backup
|
|
4
|
+
module Configuration
|
|
5
|
+
module Helpers
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Finds all the object's getter methods and checks the global
|
|
9
|
+
# configuration for these methods, if they respond then they will
|
|
10
|
+
# assign the object's attribute(s) to that particular global configuration's attribute
|
|
11
|
+
def load_defaults!
|
|
12
|
+
c = self.class.name.split('::')
|
|
13
|
+
configuration = Backup::Configuration.const_get(c[1]).const_get(c[2])
|
|
14
|
+
|
|
15
|
+
getter_methods.each do |attribute|
|
|
16
|
+
if configuration.respond_to?(attribute)
|
|
17
|
+
unless configuration.send(attribute).nil?
|
|
18
|
+
self.send("#{attribute}=", configuration.send(attribute))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# Clears all the defaults that may have been set by the user
|
|
26
|
+
def clear_defaults!
|
|
27
|
+
setter_methods.each do |method|
|
|
28
|
+
self.send(method, nil)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Returns an array of the setter methods (as String)
|
|
34
|
+
def setter_methods
|
|
35
|
+
methods.map do |method|
|
|
36
|
+
method = method.to_s
|
|
37
|
+
method if method =~ /^\w(\w|\d|\_)+\=$/ and method != 'taguri='
|
|
38
|
+
end.compact
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Returns an array of getter methods (as Array)
|
|
43
|
+
def getter_methods
|
|
44
|
+
methods.map do |method|
|
|
45
|
+
method = method.to_s
|
|
46
|
+
if method =~ /^\w(\w|\d|\_)+\=$/ and method != 'taguri='
|
|
47
|
+
method.sub('=','')
|
|
48
|
+
end
|
|
49
|
+
end.compact
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|