tiny_backup 0.0.0 → 0.1.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.
- checksums.yaml +7 -0
- data/lib/configure.rb +74 -0
- data/lib/tiny_backup.rb +112 -176
- data/lib/version.rb +3 -0
- metadata +8 -8
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2562bb50b04adc3aaf11fe40065fb8a02b89978b
|
4
|
+
data.tar.gz: 1572b687adee86d841f166122a5eb830bdef3ffc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 82dacfa4785f06dffc09e96845cf747ca83b9adac230870675ec3896054b2d2f8f0d49883802edd45607e4498196f598b89a2ba05959b165eb7179eb21257557
|
7
|
+
data.tar.gz: 3f9e1e98fcf364397c379bee209b4377a2eb267e9186d0255ba284ee2a7dfa5a3f536abb3445c34206a433967c1ae01cb49af0999f222409ea32c045bc547217
|
data/lib/configure.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module TinyBackup
|
2
|
+
# Set global settings for TinyBackup like TinyBackup.configure {|config| config.max_versions = 100 }
|
3
|
+
def self.configure &block
|
4
|
+
yield @config ||= Config.new
|
5
|
+
end
|
6
|
+
|
7
|
+
# Global settings for TinyBackup
|
8
|
+
def config
|
9
|
+
@config ||= Config.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class Config
|
13
|
+
# The folder name that is used to keep the backup files and temporary files.
|
14
|
+
# @return [String] defaults to "db/backup"
|
15
|
+
attr_accessor :backup_folder
|
16
|
+
|
17
|
+
# The date format that will be appended and used to time-stamp the backup files. Can be any valid date format similar with the ISO C and POSIX directives.
|
18
|
+
# @return [String] defaults to "%d-%m-%Y_%H:%M"
|
19
|
+
attr_accessor :date_format
|
20
|
+
|
21
|
+
# The prefix used to name each .diff file that contains changes from the last version. Automatically generated +version_number+ will be appended to the +version_prefix+.
|
22
|
+
# @return [String] defaults to "ver_"
|
23
|
+
attr_accessor :version_prefix
|
24
|
+
|
25
|
+
# The prefix used to name the starting .zip file that contains a schema.rb file and a .csv file for each non-empty table.
|
26
|
+
# @return [String] defaults to "origin"
|
27
|
+
attr_accessor :zip_prefix
|
28
|
+
|
29
|
+
# Maximum number of versions that can exist. If this number is exceeded, the first .diff version is merged in the starting .zip file and cannot be reversed.
|
30
|
+
# @return [Integer] defaults to 32 (if backup is done daily, keep the last month versions)
|
31
|
+
attr_accessor :max_versions
|
32
|
+
|
33
|
+
# Maximum number of rows fetched by a single query to avoid memory crashes. If your table has more rows it will be used multiple query to fetch all the data.
|
34
|
+
# @return [Integer] defaults to 100000
|
35
|
+
attr_accessor :per_page
|
36
|
+
|
37
|
+
# true if the debug info will be provided in the process.
|
38
|
+
# @return [Boolean] defaults to false
|
39
|
+
attr_accessor :silent
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@backup_folder = "db/backup"
|
43
|
+
@date_format = "%d-%m-%Y_%H:%M"
|
44
|
+
@version_prefix = "ver_"
|
45
|
+
@zip_prefix = "origin"
|
46
|
+
@max_versions = 32
|
47
|
+
@per_page = 100000
|
48
|
+
@silent = false
|
49
|
+
end
|
50
|
+
|
51
|
+
def backup_folder= val
|
52
|
+
@backup_folder = val.to_s[0..-2] if val.to_s.last == "/"
|
53
|
+
end
|
54
|
+
|
55
|
+
def backup_folder
|
56
|
+
unless @backup_folder_exist ||= File.directory?(@backup_folder)
|
57
|
+
FileUtils.mkdir(@backup_folder)
|
58
|
+
@backup_folder_exist = true
|
59
|
+
end
|
60
|
+
|
61
|
+
@backup_folder
|
62
|
+
end
|
63
|
+
|
64
|
+
def date_format= val
|
65
|
+
begin
|
66
|
+
Time.now.strftime(val).to_datetime
|
67
|
+
rescue
|
68
|
+
raise ArgumentError, "invalid date format"
|
69
|
+
end
|
70
|
+
|
71
|
+
@date_format = val
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/tiny_backup.rb
CHANGED
@@ -10,96 +10,18 @@ end
|
|
10
10
|
require 'rake'
|
11
11
|
require 'csv'
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
# Returns a *hash* with the following keys backup_folder, date_format, version_prefix, zip_prefix, max_versions, per_page
|
17
|
-
def config
|
18
|
-
{
|
19
|
-
backup_folder: backup_folder,
|
20
|
-
date_format: date_format,
|
21
|
-
version_prefix: version_prefix,
|
22
|
-
zip_prefix: zip_prefix,
|
23
|
-
max_versions: max_versions,
|
24
|
-
per_page: per_page
|
25
|
-
}
|
26
|
-
end
|
27
|
-
|
28
|
-
# Set configuration settings
|
29
|
-
#
|
30
|
-
# +options+ is a *hash* with the following keys backup_folder, date_format, version_prefix, zip_prefix, max_versions, per_page
|
31
|
-
def config= options
|
32
|
-
if options[:date_format].present?
|
33
|
-
# fall-back if the date_format is invalid
|
34
|
-
begin
|
35
|
-
Time.now.strftime(options[:date_format]).to_datetime
|
36
|
-
rescue
|
37
|
-
raise ArgumentError, "invalid date format"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
options.slice(*config.keys).each do |k, v|
|
42
|
-
instance_variable_set "@#{k}", ([:max_versions, :per_page].include?(k) ? v.to_i : v.to_s)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Returns a *string* of the folder that is used to keep the backup files and temporary files
|
47
|
-
def backup_folder
|
48
|
-
this_backup_folder = @backup_folder || "db/backup"
|
49
|
-
|
50
|
-
# the backup_folder will not have any slash at the end
|
51
|
-
this_backup_folder = this_backup_folder.to_s[0..-2] if this_backup_folder.to_s.last == "/"
|
52
|
-
|
53
|
-
# create the folder if doesn't exist
|
54
|
-
FileUtils.mkdir(this_backup_folder) unless File.directory?(this_backup_folder)
|
55
|
-
|
56
|
-
return this_backup_folder
|
57
|
-
end
|
58
|
-
|
59
|
-
# Returns a *string* of the date format that will be appended and used to time-stamp the backup files. Can be any valid date format similar with the ISO C and POSIX directives
|
60
|
-
#
|
61
|
-
# *Default* *value*: %d-%m-%Y_%H:%M
|
62
|
-
def date_format
|
63
|
-
@date_format || "%d-%m-%Y_%H:%M"
|
64
|
-
end
|
13
|
+
require 'configure'
|
14
|
+
require 'version'
|
65
15
|
|
66
|
-
|
67
|
-
#
|
68
|
-
# *Default* *value*: +ver_+
|
69
|
-
def version_prefix
|
70
|
-
@version_prefix || "ver_"
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns a *string* of the prefix used to name the starting .zip file that contains a schema.rb file and a .csv file for each non-empty table
|
74
|
-
#
|
75
|
-
# *Default* *value*: +origin+
|
76
|
-
def zip_prefix
|
77
|
-
@zip_prefix || "origin"
|
78
|
-
end
|
79
|
-
|
80
|
-
# Returns an *integer* of maximum number of versions that can exist. If this number is exceeded, the first .diff version is merged in the starting .zip file and cannot be reversed
|
81
|
-
#
|
82
|
-
# *Default* *value*: +32+ (if backup is done daily, keep the last month versions)
|
83
|
-
def max_versions
|
84
|
-
@max_versions || 32
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns an *integer* of maximum number of rows fetched by a single query to avoid memory crashes. If your table has more rows it will be used multiple query to fetch all the data
|
88
|
-
#
|
89
|
-
# *Default* *value*: +100000+
|
90
|
-
def per_page
|
91
|
-
@per_page || 100000
|
92
|
-
end
|
16
|
+
module TinyBackup
|
93
17
|
|
94
|
-
# Create a backup of the current database and choose automatically to create a .zip or .diff file
|
95
|
-
#
|
96
|
-
# *Lock* *until* *is* *done*
|
18
|
+
# Create a backup of the current database and choose automatically to create a .zip or .diff file.
|
97
19
|
def backup_now
|
98
20
|
# if the resource is locked, we skip to ensure block
|
99
21
|
lock
|
100
22
|
locked_by_this_method = true
|
101
23
|
|
102
|
-
|
24
|
+
tmp_files = []
|
103
25
|
nvf = new_version_filename
|
104
26
|
|
105
27
|
stream = StringIO.new
|
@@ -109,21 +31,19 @@ module TinyBackup
|
|
109
31
|
if nvf.split(".").last == "zip"
|
110
32
|
# there is no .zip file so we must create one and add schema.rb and .csv file for each table
|
111
33
|
# TODO: use a much better compression like Zlib::BEST_COMPRESSION to reduce the zip size, but this will consume processing power
|
112
|
-
ZIPLIB.open("#{backup_folder}/#{nvf}", ZIPLIB::CREATE) do |f|
|
113
|
-
puts "-- backup_schema\n"
|
34
|
+
ZIPLIB.open("#{config.backup_folder}/#{nvf}", ZIPLIB::CREATE) do |f|
|
114
35
|
t_benchmark = Benchmark.ms do
|
115
36
|
f.get_output_stream("schema.rb") do |ff|
|
116
37
|
ff.write schema_rb
|
117
|
-
|
38
|
+
tmp_files << ff if ZIPOLD
|
118
39
|
end
|
119
40
|
end
|
120
|
-
puts " -> #{'%.4f' % (t_benchmark/1000)}s\n"
|
41
|
+
puts "-- backup_schema\n -> #{'%.4f' % (t_benchmark/1000)}s\n" if !config.silent
|
121
42
|
|
122
43
|
ActiveRecord::Base.connection.tables.each do |table|
|
123
44
|
next if table == "schema_migrations"
|
124
45
|
query_count = ActiveRecord::Base.connection.execute("SELECT COUNT(*) FROM #{table}").first.first
|
125
46
|
|
126
|
-
puts "-- backup_table(\"#{table}\")\n"
|
127
47
|
t_benchmark = Benchmark.ms do
|
128
48
|
if query_count > 0
|
129
49
|
rows = []
|
@@ -131,34 +51,33 @@ module TinyBackup
|
|
131
51
|
|
132
52
|
loop do
|
133
53
|
break if query_index >= query_count
|
134
|
-
query = ActiveRecord::Base.connection.execute("SELECT * FROM #{table} LIMIT #{per_page} OFFSET #{query_index}")
|
54
|
+
query = ActiveRecord::Base.connection.execute("SELECT * FROM #{table} LIMIT #{config.per_page} OFFSET #{query_index}")
|
135
55
|
rows << add_query(query.fields) if query_index == 0
|
136
56
|
query.each { |row| rows << add_query(row) }
|
137
|
-
query_index += per_page
|
57
|
+
query_index += config.per_page
|
138
58
|
end
|
139
59
|
|
140
60
|
f.get_output_stream("#{table}.csv") do |ff|
|
141
61
|
ff.write rows.join
|
142
|
-
|
62
|
+
tmp_files << ff if ZIPOLD
|
143
63
|
end
|
144
64
|
end
|
145
65
|
end
|
146
|
-
puts " -> #{'%.4f' % (t_benchmark/1000)}s\n"
|
66
|
+
puts "-- backup_table(\"#{table}\")\n -> #{'%.4f' % (t_benchmark/1000)}s\n" if !config.silent
|
147
67
|
end
|
148
68
|
end
|
149
69
|
|
150
70
|
else
|
151
71
|
# a new .diff file is created with the diff between origin_zip and tmp_origin_zip(made by merging all the versions into origin)
|
152
|
-
tmp_origin_zip = "#{backup_folder}/#{compact_original(:all)}"
|
72
|
+
tmp_origin_zip = "#{config.backup_folder}/#{compact_original(:all)}"
|
153
73
|
tables = ActiveRecord::Base.connection.tables
|
154
74
|
is_empty = true
|
155
75
|
|
156
|
-
File.open("#{backup_folder}/#{nvf}", "wb") do |f|
|
76
|
+
File.open("#{config.backup_folder}/#{nvf}", "wb") do |f|
|
157
77
|
ZIPLIB.open(tmp_origin_zip) do |zf|
|
158
78
|
zf.entries.each do |zf_entry|
|
159
79
|
|
160
80
|
if zf_entry.name == "schema.rb"
|
161
|
-
puts "-- backup_schema\n"
|
162
81
|
t_benchmark = Benchmark.ms do
|
163
82
|
tables.delete "schema_migrations"
|
164
83
|
this_diff = diff_files zf.read(zf_entry.name), schema_rb
|
@@ -172,7 +91,7 @@ module TinyBackup
|
|
172
91
|
f.write "\n\n"
|
173
92
|
end
|
174
93
|
end
|
175
|
-
puts " -> #{'%.4f' % (t_benchmark/1000)}s\n"
|
94
|
+
puts "-- backup_schema\n -> #{'%.4f' % (t_benchmark/1000)}s\n" if !config.silent
|
176
95
|
else
|
177
96
|
|
178
97
|
table = zf_entry.name.split(".").first
|
@@ -186,14 +105,13 @@ module TinyBackup
|
|
186
105
|
rows = []
|
187
106
|
query_index = 0
|
188
107
|
|
189
|
-
puts "-- backup_table(\"#{table}\")\n"
|
190
108
|
t_benchmark = Benchmark.ms do
|
191
109
|
loop do
|
192
110
|
break if query_index >= query_count
|
193
|
-
query = ActiveRecord::Base.connection.execute("SELECT * FROM #{table} LIMIT #{per_page} OFFSET #{query_index}")
|
111
|
+
query = ActiveRecord::Base.connection.execute("SELECT * FROM #{table} LIMIT #{config.per_page} OFFSET #{query_index}")
|
194
112
|
rows << add_query(query.fields) if query_index == 0
|
195
113
|
query.each { |row| rows << add_query(row) }
|
196
|
-
query_index += per_page
|
114
|
+
query_index += config.per_page
|
197
115
|
end
|
198
116
|
|
199
117
|
this_diff = diff_files zf.read(zf_entry.name), rows.join
|
@@ -207,7 +125,7 @@ module TinyBackup
|
|
207
125
|
f.write "\n\n"
|
208
126
|
end
|
209
127
|
end
|
210
|
-
puts " -> #{'%.4f' % (t_benchmark/1000)}s\n"
|
128
|
+
puts "-- backup_table(\"#{table}\")\n -> #{'%.4f' % (t_benchmark/1000)}s\n" if !config.silent
|
211
129
|
end
|
212
130
|
|
213
131
|
end
|
@@ -224,15 +142,14 @@ module TinyBackup
|
|
224
142
|
rows = []
|
225
143
|
query_index = 0
|
226
144
|
|
227
|
-
puts "-- backup_table(\"#{table}\")\n"
|
228
145
|
t_benchmark = Benchmark.ms do
|
229
146
|
loop do
|
230
147
|
break if query_index >= query_count
|
231
|
-
query = ActiveRecord::Base.connection.execute("SELECT * FROM #{table} LIMIT #{per_page} OFFSET #{query_index}")
|
148
|
+
query = ActiveRecord::Base.connection.execute("SELECT * FROM #{table} LIMIT #{config.per_page} OFFSET #{query_index}")
|
232
149
|
|
233
150
|
rows << add_query(query.fields) if query_index == 0
|
234
151
|
query.each { |row| rows << add_query(row) }
|
235
|
-
query_index += per_page
|
152
|
+
query_index += config.per_page
|
236
153
|
end
|
237
154
|
|
238
155
|
this_diff = diff_files "", rows.join
|
@@ -246,22 +163,22 @@ module TinyBackup
|
|
246
163
|
f.write "\n\n"
|
247
164
|
end
|
248
165
|
end
|
249
|
-
puts " -> #{'%.4f' % (t_benchmark/1000)}s\n"
|
166
|
+
puts "-- backup_table(\"#{table}\")\n -> #{'%.4f' % (t_benchmark/1000)}s\n" if !config.silent
|
250
167
|
end
|
251
168
|
end
|
252
169
|
|
253
170
|
File.delete tmp_origin_zip
|
254
|
-
File.delete("#{backup_folder}/#{nvf}") if is_empty
|
171
|
+
File.delete("#{config.backup_folder}/#{nvf}") if is_empty
|
255
172
|
end
|
256
173
|
|
257
174
|
# keep max versions
|
258
|
-
version_files = Dir.glob("#{backup_folder}/#{version_prefix}*").sort
|
259
|
-
if max_versions < version_files.length
|
175
|
+
version_files = Dir.glob("#{config.backup_folder}/#{config.version_prefix}*").sort
|
176
|
+
if config.max_versions < version_files.length
|
260
177
|
# throw files to garbage
|
261
|
-
|
262
|
-
|
178
|
+
tmp_files << Dir.glob("#{config.backup_folder}/#{config.zip_prefix}*").first
|
179
|
+
tmp_files << version_files.first
|
263
180
|
|
264
|
-
File.rename "#{backup_folder}/#{compact_original(1)}", dup_file("#{backup_folder}/#{zip_prefix}_#{Time.now.strftime(date_format)}.zip")
|
181
|
+
File.rename "#{config.backup_folder}/#{compact_original(1)}", dup_file("#{config.backup_folder}/#{config.zip_prefix}_#{Time.now.strftime(config.date_format)}.zip")
|
265
182
|
end
|
266
183
|
|
267
184
|
# delete temporary files before method exit
|
@@ -269,62 +186,62 @@ module TinyBackup
|
|
269
186
|
@method_error = e
|
270
187
|
ensure
|
271
188
|
unlock if locked_by_this_method
|
272
|
-
|
189
|
+
tmp_files.each { |i| File.delete(i) rescue nil } if tmp_files.present?
|
273
190
|
raise @method_error if @method_error.present?
|
274
191
|
return true
|
275
192
|
end
|
276
193
|
|
277
|
-
# Merge into the .zip file and delete all the .diff files to clear the space
|
278
|
-
#
|
279
|
-
# *Lock* *until* *is* *done*
|
194
|
+
# Merge into the .zip file and delete all the .diff files to clear the space.
|
280
195
|
def compact_all
|
281
196
|
lock # if the resource is locked, we skip to ensure block
|
282
197
|
locked_by_this_method = true
|
283
198
|
|
284
|
-
|
285
|
-
|
286
|
-
|
199
|
+
tmp_files = []
|
200
|
+
tmp_files += Dir.glob("#{config.backup_folder}/#{config.zip_prefix}*")
|
201
|
+
tmp_files += Dir.glob("#{config.backup_folder}/#{config.version_prefix}*")
|
287
202
|
|
288
203
|
# make the temporary zip be the original and apply the updated_at time-stamp
|
289
|
-
File.rename "#{backup_folder}/#{compact_original(:all)}", dup_file("#{backup_folder}/#{zip_prefix}_#{Time.now.strftime(date_format)}.zip")
|
204
|
+
File.rename "#{config.backup_folder}/#{compact_original(:all)}", dup_file("#{config.backup_folder}/#{config.zip_prefix}_#{Time.now.strftime(config.date_format)}.zip")
|
290
205
|
|
291
206
|
# delete temporary files before method exit
|
292
207
|
rescue => e
|
293
208
|
@method_error = e
|
294
209
|
ensure
|
295
210
|
unlock if locked_by_this_method
|
296
|
-
|
211
|
+
tmp_files.each { |i| File.delete(i) rescue nil } if tmp_files.present?
|
297
212
|
raise @method_error if @method_error.present?
|
298
213
|
return true
|
299
214
|
end
|
300
215
|
|
301
216
|
# Change the database to match the selected integer +version_number+.
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
|
306
|
-
|
217
|
+
# @param version_number [Integer or Symbol] can be :all to restore all backup data or 0 to restore only the data collected in the .zip file
|
218
|
+
# @param just_temporary [Boolean] if is false, the unused version files will be deleted and the latest backup version will be synchronized with the database data.
|
219
|
+
# @param with_backup [Boolean] if is false, the attempt to backup not saved data is canceled
|
220
|
+
def restore_db version_number, just_temporary=true, with_backup=true
|
221
|
+
# do this before deleting the database and lose data
|
222
|
+
backup_now if just_temporary && with_backup
|
223
|
+
|
307
224
|
lock # if the resource is locked, we skip to ensure block
|
308
225
|
locked_by_this_method = true
|
309
226
|
|
310
|
-
|
227
|
+
tmp_files = []
|
311
228
|
|
312
229
|
if just_temporary && version_number != :all
|
313
|
-
puts "you want to restore just temporary: DO NOT start a backup BEFORE calling TinyBackup.restore_db(:all)\n"
|
230
|
+
puts "you want to restore just temporary: DO NOT start a backup BEFORE calling TinyBackup.restore_db(:all)\n" if !config.silent
|
314
231
|
end
|
315
232
|
|
316
|
-
version_files = Dir.glob("#{backup_folder}/#{version_prefix}*")
|
233
|
+
version_files = Dir.glob("#{config.backup_folder}/#{config.version_prefix}*")
|
317
234
|
if version_number == :all
|
318
235
|
version_count = version_files.length
|
319
236
|
else
|
320
|
-
good_versions = version_files.find_all { |i| i.gsub("#{backup_folder}/#{version_prefix}", "").split("_").first.to_i <= version_number.to_i }
|
237
|
+
good_versions = version_files.find_all { |i| i.gsub("#{config.backup_folder}/#{config.version_prefix}", "").split("_").first.to_i <= version_number.to_i }
|
321
238
|
version_count = good_versions.length
|
322
|
-
|
239
|
+
tmp_files += version_files - good_versions if !just_temporary
|
323
240
|
end
|
324
241
|
|
325
242
|
tmp_origin_zip = compact_original version_count
|
326
|
-
|
327
|
-
|
243
|
+
tmp_files << "#{config.backup_folder}/#{tmp_origin_zip}"
|
244
|
+
tmp_files << "#{config.backup_folder}/schema_tmp.rb"
|
328
245
|
|
329
246
|
db_name = Rails.configuration.database_configuration[Rails.env]["database"]
|
330
247
|
db_collation = ActiveRecord::Base.connection.collation
|
@@ -333,31 +250,33 @@ module TinyBackup
|
|
333
250
|
ActiveRecord::Base.connection.reconnect!
|
334
251
|
|
335
252
|
# prepare the structure
|
336
|
-
ZIPLIB.open("#{backup_folder}/#{tmp_origin_zip}") do |zf|
|
253
|
+
ZIPLIB.open("#{config.backup_folder}/#{tmp_origin_zip}") do |zf|
|
337
254
|
zf.entries.each do |zf_entry|
|
338
255
|
if zf_entry.name == "schema.rb"
|
339
|
-
File.open("#{backup_folder}/schema_tmp.rb", "wb") { |f| f.write zf.read(zf_entry.name) }
|
256
|
+
File.open("#{config.backup_folder}/schema_tmp.rb", "wb") { |f| f.write zf.read(zf_entry.name) }
|
340
257
|
break
|
341
258
|
end
|
342
259
|
end
|
343
260
|
end
|
344
|
-
ActiveRecord::Schema.
|
261
|
+
schema_verbose = ActiveRecord::Schema.verbose
|
262
|
+
ActiveRecord::Schema.verbose = !config.silent
|
263
|
+
ActiveRecord::Schema.load("#{config.backup_folder}/schema_tmp.rb")
|
264
|
+
ActiveRecord::Schema.verbose = schema_verbose
|
345
265
|
|
346
266
|
# add the data
|
347
|
-
ZIPLIB.open("#{backup_folder}/#{tmp_origin_zip}") do |zf|
|
267
|
+
ZIPLIB.open("#{config.backup_folder}/#{tmp_origin_zip}") do |zf|
|
348
268
|
zf.entries.each do |zf_entry|
|
349
269
|
next if zf_entry.name == "schema.rb"
|
350
270
|
table_rows = zf.read(zf_entry.name).split("\n")
|
351
271
|
table_header = table_rows.shift
|
352
272
|
table_name = zf_entry.name.split(".").first
|
353
273
|
|
354
|
-
puts "-- insert_data(\"#{table_name}\")\n"
|
355
274
|
t_benchmark = Benchmark.ms do
|
356
|
-
table_rows.in_groups_of(per_page, false) do |tr_group|
|
275
|
+
table_rows.in_groups_of(config.per_page, false) do |tr_group|
|
357
276
|
ActiveRecord::Base.connection.execute insert_row(table_name, table_header, tr_group)
|
358
277
|
end
|
359
278
|
end
|
360
|
-
puts " -> #{'%.4f' % (t_benchmark/1000)}s\n"
|
279
|
+
puts "-- insert_data(\"#{table_name}\")\n -> #{'%.4f' % (t_benchmark/1000)}s\n" if !config.silent
|
361
280
|
end
|
362
281
|
end
|
363
282
|
|
@@ -366,7 +285,7 @@ module TinyBackup
|
|
366
285
|
@method_error = e
|
367
286
|
ensure
|
368
287
|
unlock if locked_by_this_method
|
369
|
-
|
288
|
+
tmp_files.each { |i| File.delete(i) rescue nil } if tmp_files.present?
|
370
289
|
raise @method_error if @method_error.present?
|
371
290
|
return true
|
372
291
|
end
|
@@ -374,20 +293,24 @@ module TinyBackup
|
|
374
293
|
# YOU SHALL NOT PASS -----------------------------------------------------------------
|
375
294
|
private
|
376
295
|
|
377
|
-
#
|
296
|
+
# Create an SQL INSERT query.
|
297
|
+
# @param table_name [String]
|
298
|
+
# @param table_header [String] contains column names delimited by " and separated by , (like '"col1", "col2", "col3"')
|
299
|
+
# @param table_rows [Array] each row is a *String* with the same format as table_header
|
300
|
+
# @return [String] the query used to insert a row of data
|
378
301
|
def insert_row(table_name, table_header, table_rows)
|
379
302
|
"INSERT INTO #{table_name} (#{table_header.gsub("\"", "`")}) VALUES " +
|
380
303
|
table_rows.map { |i| "(#{i})" }.join(",")
|
381
304
|
end
|
382
305
|
|
383
|
-
# Merge X versions starting with the lowest +version_number+ into a temporary .zip file that looks like the original zip
|
384
|
-
#
|
385
|
-
#
|
306
|
+
# Merge X versions starting with the lowest +version_number+ into a temporary .zip file that looks like the original zip.
|
307
|
+
# @param versions [Integer or Symbol] can be :all to compact all the versions and delete all the .diff files
|
308
|
+
# @return [String] the path of the temporary zip file
|
386
309
|
def compact_original versions
|
387
|
-
versions = Dir.glob("#{backup_folder}/*").length if versions == :all
|
388
|
-
|
389
|
-
tmp_filename = "tmp_#{zip_prefix}_#{Time.now.to_i}.zip"
|
390
|
-
origin_zip = Dir.glob("#{backup_folder}/#{zip_prefix}*").first
|
310
|
+
versions = Dir.glob("#{config.backup_folder}/*").length if versions == :all
|
311
|
+
tmp_files = []
|
312
|
+
tmp_filename = "tmp_#{config.zip_prefix}_#{Time.now.to_i}.zip"
|
313
|
+
origin_zip = Dir.glob("#{config.backup_folder}/#{config.zip_prefix}*").first
|
391
314
|
|
392
315
|
# add all files from original zip to a big hash
|
393
316
|
zip_files = {}
|
@@ -396,7 +319,7 @@ module TinyBackup
|
|
396
319
|
end
|
397
320
|
|
398
321
|
# modify the hash on every version
|
399
|
-
Dir.glob("#{backup_folder}/#{version_prefix}*").sort.first(versions).each do |version_file|
|
322
|
+
Dir.glob("#{config.backup_folder}/#{config.version_prefix}*").sort.first(versions).each do |version_file|
|
400
323
|
diff_hash = prepare_diff version_file
|
401
324
|
|
402
325
|
zip_files.each do |k, v|
|
@@ -422,20 +345,26 @@ module TinyBackup
|
|
422
345
|
end
|
423
346
|
|
424
347
|
# save the big hash
|
425
|
-
ZIPLIB.open("#{backup_folder}/#{tmp_filename}", ZIPLIB::CREATE) do |f|
|
348
|
+
ZIPLIB.open("#{config.backup_folder}/#{tmp_filename}", ZIPLIB::CREATE) do |f|
|
426
349
|
zip_files.each do |k, v|
|
427
350
|
f.get_output_stream(k) do |ff|
|
428
351
|
ff.write v
|
429
|
-
|
352
|
+
tmp_files << ff if ZIPOLD
|
430
353
|
end if v != "\n"
|
431
354
|
end
|
432
355
|
end
|
433
356
|
|
434
|
-
|
435
|
-
|
357
|
+
# delete temporary files before method exit
|
358
|
+
rescue => e
|
359
|
+
@method_error = e
|
360
|
+
ensure
|
361
|
+
tmp_files.each { |i| File.delete(i) rescue nil } if tmp_files.present?
|
362
|
+
raise @method_error if @method_error.present?
|
363
|
+
return tmp_filename
|
436
364
|
end
|
437
365
|
|
438
|
-
#
|
366
|
+
# @param diff_path [String]
|
367
|
+
# @return [Hash] of the parsed .diff file where each key is a file to be changed and the value is an *Array* or *String* of the .diff file lines
|
439
368
|
def prepare_diff diff_path
|
440
369
|
diff_hash = {}
|
441
370
|
|
@@ -454,9 +383,10 @@ module TinyBackup
|
|
454
383
|
diff_hash
|
455
384
|
end
|
456
385
|
|
457
|
-
# It happens if there are two files with the same date(after using the date_format)
|
458
|
-
#
|
459
|
-
#
|
386
|
+
# It happens if there are two files with the same date(after using the date_format).
|
387
|
+
# *Does* *not* *create* *a* *file!*
|
388
|
+
# @param filename [String]
|
389
|
+
# @return [String] a new filename that will fix the duplicated filename issue by appending an index
|
460
390
|
def dup_file filename
|
461
391
|
return filename unless File.exist? filename
|
462
392
|
index = 1
|
@@ -469,7 +399,9 @@ module TinyBackup
|
|
469
399
|
end
|
470
400
|
end
|
471
401
|
|
472
|
-
#
|
402
|
+
# @param file [String] content of the original file
|
403
|
+
# @param diff_lines [Array] each item is a line *String* of the diff file
|
404
|
+
# @return [String] of the updated file after applying the diff
|
473
405
|
def update_file file, diff_lines
|
474
406
|
file = file.split "\n"
|
475
407
|
current_operation = nil
|
@@ -524,14 +456,16 @@ module TinyBackup
|
|
524
456
|
file.join("\n") + "\n"
|
525
457
|
end
|
526
458
|
|
527
|
-
#
|
459
|
+
# @param operation [String] operation as seen in the .diff file
|
460
|
+
# @param sign [String] one of these values "a", "c" or "d" meaning add, change or delete operation
|
461
|
+
# @return [Array] of two *Integer* values of the range interval in the diff operation
|
528
462
|
def diff_operation operation, sign
|
529
463
|
val1, val2 = operation.split sign
|
530
464
|
return diff_range(val1), diff_range(val2)
|
531
465
|
end
|
532
466
|
|
533
|
-
|
534
|
-
#
|
467
|
+
# @param value [String] a single line number like "3" or a range of lines separated by , like "3,6"
|
468
|
+
# @return [Array] that contains the starting position of the change and how many operations it will take
|
535
469
|
def diff_range value
|
536
470
|
if value.include? ","
|
537
471
|
l, r = value.split(",")
|
@@ -542,10 +476,11 @@ module TinyBackup
|
|
542
476
|
end
|
543
477
|
|
544
478
|
# Two temporary files will be created, but deleted shortly after the operation is finished
|
545
|
-
#
|
546
|
-
#
|
479
|
+
# @param file1 [String] contents of the file
|
480
|
+
# @param file2 [String] contents of the file
|
481
|
+
# @eturn [Array] that contains lines of the diff between +file1+ and +file2+
|
547
482
|
def diff_files file1, file2
|
548
|
-
diff_filename = "#{backup_folder}/diff"
|
483
|
+
diff_filename = "#{config.backup_folder}/diff"
|
549
484
|
|
550
485
|
File.open("#{diff_filename}1", "wb") { |f| f.write file1 }
|
551
486
|
File.open("#{diff_filename}2", "wb") { |f| f.write file2 }
|
@@ -560,21 +495,22 @@ module TinyBackup
|
|
560
495
|
diff_lines
|
561
496
|
end
|
562
497
|
|
563
|
-
#
|
498
|
+
# @return [String] of the current file created by backup_now function
|
564
499
|
def new_version_filename
|
565
|
-
backup_folder_files = Dir.glob("#{backup_folder}/*").map { |i| i.gsub("#{backup_folder}/", "") }
|
500
|
+
backup_folder_files = Dir.glob("#{config.backup_folder}/*").map { |i| i.gsub("#{config.backup_folder}/", "") }
|
566
501
|
|
567
|
-
zip_file = backup_folder_files.find { |i| i.starts_with?(zip_prefix) && i.ends_with?("zip") }
|
568
|
-
return "#{zip_prefix}_#{Time.now.strftime(date_format)}.zip" if zip_file.nil?
|
502
|
+
zip_file = backup_folder_files.find { |i| i.starts_with?(config.zip_prefix) && i.ends_with?("zip") }
|
503
|
+
return "#{config.zip_prefix}_#{Time.now.strftime(config.date_format)}.zip" if zip_file.nil?
|
569
504
|
|
570
|
-
version_files = backup_folder_files.find_all { |i| i.starts_with?(version_prefix) && i.ends_with?("diff") }.sort
|
571
|
-
return "#{version_prefix}1_#{Time.now.strftime(date_format)}.diff" if version_files.blank?
|
505
|
+
version_files = backup_folder_files.find_all { |i| i.starts_with?(config.version_prefix) && i.ends_with?("diff") }.sort
|
506
|
+
return "#{config.version_prefix}1_#{Time.now.strftime(config.date_format)}.diff" if version_files.blank?
|
572
507
|
|
573
|
-
last_version = version_files.last.gsub(version_prefix, "").split("_").first.to_i
|
574
|
-
return "#{version_prefix}#{last_version + 1}_#{Time.now.strftime(date_format)}.diff"
|
508
|
+
last_version = version_files.last.gsub(config.version_prefix, "").split("_").first.to_i
|
509
|
+
return "#{config.version_prefix}#{last_version + 1}_#{Time.now.strftime(config.date_format)}.diff"
|
575
510
|
end
|
576
511
|
|
577
|
-
#
|
512
|
+
# @param values [Array] csv row
|
513
|
+
# @return [String] of parsed csv row to be inserted into database
|
578
514
|
def add_query values
|
579
515
|
values.map do |val|
|
580
516
|
if val.nil?
|
@@ -594,10 +530,10 @@ module TinyBackup
|
|
594
530
|
@logger = ActiveRecord::Base.logger
|
595
531
|
ActiveRecord::Base.logger = nil
|
596
532
|
|
597
|
-
if File.exist?("#{backup_folder}/.lock")
|
598
|
-
raise "Another operation is running. More info you can find in the '#{backup_folder}/.lock' file"
|
533
|
+
if File.exist?("#{config.backup_folder}/.lock")
|
534
|
+
raise "Another operation is running. More info you can find in the '#{config.backup_folder}/.lock' file"
|
599
535
|
else
|
600
|
-
File.open("#{backup_folder}/.lock", "wb") do |f|
|
536
|
+
File.open("#{config.backup_folder}/.lock", "wb") do |f|
|
601
537
|
f.write caller.join("\n") + "\n"
|
602
538
|
end
|
603
539
|
end
|
@@ -607,7 +543,7 @@ module TinyBackup
|
|
607
543
|
def unlock
|
608
544
|
ActiveRecord::Base.logger = @logger
|
609
545
|
|
610
|
-
File.delete("#{backup_folder}/.lock") if File.exist?("#{backup_folder}/.lock")
|
546
|
+
File.delete("#{config.backup_folder}/.lock") if File.exist?("#{config.backup_folder}/.lock")
|
611
547
|
end
|
612
548
|
end
|
613
549
|
include TinyBackup
|
data/lib/version.rb
ADDED
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tiny_backup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Razvan Pavel
|
@@ -18,30 +17,31 @@ executables: []
|
|
18
17
|
extensions: []
|
19
18
|
extra_rdoc_files: []
|
20
19
|
files:
|
20
|
+
- lib/configure.rb
|
21
21
|
- lib/tiny_backup.rb
|
22
|
+
- lib/version.rb
|
22
23
|
homepage: http://rubygems.org/gems/tiny_backup
|
23
24
|
licenses:
|
24
25
|
- MIT
|
26
|
+
metadata: {}
|
25
27
|
post_install_message:
|
26
28
|
rdoc_options: []
|
27
29
|
require_paths:
|
28
30
|
- lib
|
29
31
|
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
-
none: false
|
31
32
|
requirements:
|
32
|
-
- -
|
33
|
+
- - ">="
|
33
34
|
- !ruby/object:Gem::Version
|
34
35
|
version: '0'
|
35
36
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
-
none: false
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
requirements: []
|
42
42
|
rubyforge_project:
|
43
|
-
rubygems_version:
|
43
|
+
rubygems_version: 2.2.2
|
44
44
|
signing_key:
|
45
|
-
specification_version:
|
45
|
+
specification_version: 4
|
46
46
|
summary: Backup your database on S3 or local, optimized for small disk space
|
47
47
|
test_files: []
|