nearline 0.0.4 → 0.0.5

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/lib/nearline.rb CHANGED
@@ -15,5 +15,8 @@ require 'nearline/archived_file'
15
15
  require 'nearline/log'
16
16
  require 'nearline/manifest'
17
17
 
18
+ # Non-AR model
19
+ require 'nearline/file_sequencer'
20
+
18
21
  # Static methods on Nearline
19
22
  require 'nearline/module_methods'
@@ -4,106 +4,43 @@ module Nearline
4
4
  # Represents file metadata and possible related FileContent
5
5
  # for a single file on a single system
6
6
  class ArchivedFile < ActiveRecord::Base
7
+ require 'digest/sha1'
7
8
  require 'fileutils'
8
9
 
9
10
  belongs_to :file_content
10
11
  belongs_to :system
11
12
  has_and_belongs_to_many :manifests
12
13
 
13
-
14
- def self.create_for(file_path, manifest)
15
- file_information = FileInformation.new(file_path, manifest)
16
-
17
- # The path doesn't actually exist and fails a File.stat
14
+
15
+ def self.create_for(file_information)
16
+ # The path doesn't actually exist and fails a File.lstat
18
17
  return nil if file_information.path_hash.nil?
19
-
20
- # If we find an exising entry, use it
21
- hash = manifest.system.archived_file_lookup_hash
22
- hit = hash[file_information.path_hash]
23
-
24
- unless hit.nil?
25
- af = ArchivedFile.find(hit)
26
- manifest.archived_files << af
27
- return af
28
- end
29
18
 
30
19
  # We need to create a record for either a directory or file
31
20
  archived_file = ArchivedFile.new(
32
21
  file_information.archived_file_parameters
33
22
  )
34
-
23
+
35
24
  # Find a new directory
36
25
  if (file_information.is_directory)
37
26
  archived_file.save!
38
- manifest.archived_files << archived_file
27
+ file_information.manifest.archived_files << archived_file
39
28
  return archived_file
40
29
  end
41
30
 
42
- # Find a new file that needs persisted
31
+ # Find a new file that needs persisted
43
32
  archived_file.file_content.file_size =
44
33
  [file_information.stat.size].pack('Q').unpack('L').first # HACK for Windows
45
- archived_file = archived_file.persist(manifest)
46
- archived_file.save! unless archived_file.nil?
47
- manifest.archived_files << archived_file
34
+ archived_file = archived_file.persist(file_information.manifest)
35
+ unless archived_file.nil? || archived_file.frozen?
36
+ archived_file.save!
37
+ file_information.manifest.archived_files << archived_file
38
+ end
48
39
  archived_file
49
40
 
50
41
  # TODO: Symbolic links, block devices, ...?
51
42
  end
52
-
53
- class FileInformation
54
- attr_reader :path_hash, :stat, :is_directory, :archived_file_parameters
55
- def initialize(file_path, manifest)
56
- @manifest = manifest
57
- @file_path = file_path
58
- @stat = read_stat
59
- @is_directory = File.directory?(file_path)
60
- @path_hash = generate_path_hash
61
- @archived_file_parameters = build_parameters
62
- end
63
-
64
- def read_stat
65
- stat = nil
66
- begin
67
- stat = File.stat(@file_path)
68
- rescue
69
- @manifest.add_log("File not found on stat: #{@file_path}")
70
- end
71
- stat
72
- end
73
-
74
- def generate_path_hash
75
- return nil if @stat.nil?
76
- target = [@manifest.system.name,
77
- @file_path,
78
- @stat.uid,
79
- @stat.gid,
80
- @stat.mtime.to_i,
81
- @stat.mode].join(':')
82
- Digest::SHA1.hexdigest(target)
83
- end
84
-
85
- def file_content_entry_for_files_only
86
- return FileContent.new unless @is_directory
87
- return nil
88
- end
89
-
90
- def build_parameters
91
- return nil if @stat.nil?
92
- {
93
- :system => @manifest.system,
94
- :path => @file_path,
95
- :path_hash => @path_hash,
96
- :file_content => file_content_entry_for_files_only,
97
- :uid => @stat.uid,
98
- :gid => @stat.gid,
99
- :mtime => @stat.mtime.to_i,
100
- :mode => @stat.mode,
101
- :is_directory => @is_directory
102
- }
103
- end
104
-
105
- end
106
-
43
+
107
44
  def restore(*args)
108
45
  @options = args.extract_options!
109
46
  if (self.is_directory)
@@ -158,20 +95,20 @@ module Nearline
158
95
  # won't know that until we complete the process and have to
159
96
  # clean up our mess.
160
97
  def persist(manifest)
161
- whole_file_hash = Digest::SHA1.new
162
- file_size = 0
98
+ seq = nil
163
99
  begin
164
- file_size = read_file_counting_bytes(whole_file_hash)
100
+ seq = read_file
165
101
  rescue
166
- manifest.add_log "Got error '#{$!}' on path: #{self.path}"
102
+ error = "Got error '#{$!}' on path: #{self.path}"
103
+ manifest.add_log error
167
104
  self.orphan_check
168
105
  return nil
169
106
  end
170
-
171
- size_check(file_size, manifest)
107
+
108
+ size_check(seq.file_size, manifest)
172
109
 
173
110
  # Do we have a unique sequence?
174
- key = whole_file_hash.hexdigest
111
+ key = seq.fingerprint
175
112
  return self if unique_sequence_processed?(key, manifest)
176
113
 
177
114
  # Handle the case where the sequence is not unique...
@@ -180,19 +117,14 @@ module Nearline
180
117
  self
181
118
  end
182
119
 
183
- def read_file_counting_bytes(whole_file_hash)
184
- sequencer = FileSequencer.new(self.file_content)
185
- file_size = 0
186
- buffer = ""
120
+ def read_file
187
121
  File.open(self.path, "rb") do |io|
188
- while (!io.eof) do
189
- io.read(Block::MAX_SIZE, buffer)
190
- file_size += buffer.size
191
- whole_file_hash.update(buffer)
192
- sequencer.preserve_content(buffer)
122
+ seq = FileSequencer.new(io, self.file_content)
123
+ while (!io.eof)
124
+ seq.persist_segment
193
125
  end
126
+ return seq
194
127
  end
195
- return file_size
196
128
  end
197
129
 
198
130
  def size_check(file_size, manifest)
@@ -222,10 +154,15 @@ module Nearline
222
154
  false
223
155
  end
224
156
 
157
+ # In the special case of an identical sequence existing,
158
+ # we can safely delete all related sequences and then destroy
159
+ # the file content object without the (far slower) orphan checking
160
+ # process
225
161
  def clean_up_duplicate_content
226
- self.file_content.orphan_check
162
+ Sequence.delete_all "file_content_id = #{self.file_content.id}"
163
+ self.file_content.destroy
227
164
  end
228
-
165
+
229
166
  def replace_content(key)
230
167
  self.file_content = FileContent.find_by_fingerprint(key)
231
168
  self.save!
@@ -12,12 +12,21 @@ module Nearline
12
12
 
13
13
  has_many :sequences
14
14
 
15
- MAX_SIZE = (64 * 1024)-1
15
+ # Maximum block size in bytes
16
+ @@max_block_size = (64 * 1024)-1
17
+ cattr_accessor :max_block_size
18
+
19
+ # Level of block compression attempted
20
+ # 0 = skip compression entirely
21
+ @@block_compression_level = 5
22
+ cattr_accessor :block_compression_level
16
23
 
17
24
  def attempt_compression
18
- return if (self.is_compressed)
19
- # TODO: Have a bump-the-compression option, here?
20
- candidate_content = Zlib::Deflate.deflate(self.bulk_content)
25
+ return if (self.is_compressed || @@block_compression_level == 0)
26
+ candidate_content = Zlib::Deflate.deflate(
27
+ self.bulk_content,
28
+ @@block_compression_level
29
+ )
21
30
  if candidate_content.length < self.bulk_content.length
22
31
  self.is_compressed = true
23
32
  self.bulk_content = candidate_content
@@ -37,31 +46,7 @@ module Nearline
37
46
  end
38
47
  @content = self.bulk_content
39
48
  end
40
-
41
- def self.id_for_content(x)
42
- block = Block.new(:bulk_content => x)
43
- block.calculate_fingerprint
44
- hit = Block.connection.select_one(
45
- "select id from blocks where fingerprint='#{block.fingerprint}'"
46
- )
47
- unless hit.nil?
48
- return hit['id']
49
- end
50
- block.attempt_compression
51
- block.save!
52
- block.id
53
- end
54
-
55
- def self.for_content(x)
56
- block = Models::Block.new(:bulk_content => x)
57
- block.calculate_fingerprint
58
- found = find_by_fingerprint(block.fingerprint)
59
- return found if !found.nil?
60
- block.attempt_compression
61
- block.save!
62
- block
63
- end
64
-
49
+
65
50
  def orphan_check
66
51
  if self.sequences.size == 0
67
52
  self.destroy
@@ -4,7 +4,7 @@ module Nearline
4
4
  # Has the responsibility of identifying, restoring and
5
5
  # verifying content
6
6
  class FileContent < ActiveRecord::Base
7
- has_many :sequences
7
+ has_many :sequences, :order => "sequence"
8
8
  has_many :archived_files
9
9
 
10
10
  def orphan_check
@@ -23,22 +23,25 @@ module Nearline
23
23
  return hit.nil?
24
24
  end
25
25
 
26
- def restore_to(io)
26
+ private
27
+
28
+ def each_sequence
27
29
  sequences.each do |seq|
28
30
  block = Block.find(seq.block_id)
29
- io.write(block.content)
31
+ yield block
30
32
  end
31
33
  end
34
+
35
+ public
36
+
37
+ def restore_to(io)
38
+ each_sequence { |block| io.write(block.content) }
39
+ end
32
40
 
33
41
  def verified?
34
- if (!self.verified_at.nil?)
35
- return true
36
- end
42
+ return true if (!self.verified_at.nil?)
37
43
  whole_file_hash = Digest::SHA1.new
38
- sequences.each do |seq|
39
- block = Block.find(seq.block_id)
40
- whole_file_hash.update(block.content)
41
- end
44
+ each_sequence { |block| whole_file_hash.update(block.content) }
42
45
  if fingerprint == whole_file_hash.hexdigest
43
46
  self.verified_at = Time.now
44
47
  self.save!
@@ -47,7 +50,6 @@ module Nearline
47
50
  false
48
51
  end
49
52
 
50
-
51
53
  end
52
54
 
53
55
  # Has the responsibility of preserving
@@ -61,26 +63,5 @@ module Nearline
61
63
  end
62
64
  end
63
65
 
64
- class FileSequencer
65
- def initialize(file_content)
66
- @inc = 0
67
- @file_content = file_content
68
- @file_content.save!
69
- end
70
-
71
- def preserve_content(content)
72
- @inc += 1
73
- block_id = Block.id_for_content(content)
74
- sequence = Sequence.new(
75
- :sequence => @inc,
76
- :file_content_id => @file_content.id,
77
- :block_id => block_id
78
- )
79
- sequence.save!
80
- sequence
81
- end
82
-
83
- end
84
-
85
66
  end
86
67
  end
@@ -0,0 +1,130 @@
1
+ module Nearline
2
+ module Models
3
+
4
+ # Used for mass block entry and sequencing
5
+ class FileSequencer
6
+ attr_reader :file_size
7
+
8
+ # Number of blocks to serialize in a batch
9
+ @@max_blocks = 500;
10
+ cattr_accessor :max_blocks
11
+
12
+ def initialize(io, file_content)
13
+ @io = io
14
+ @file_content = file_content
15
+ if (@file_content.id.nil?)
16
+ @file_content.save!
17
+ end
18
+ @s = []
19
+ @b = []
20
+ @file_size = 0
21
+ @offset = 0
22
+ @whole_file_hash = Digest::SHA1.new
23
+ end
24
+
25
+ def fingerprint
26
+ @whole_file_hash.hexdigest
27
+ end
28
+
29
+ def persist_segment
30
+ pull_blocks
31
+ sequence_known_blocks
32
+ attempt_compression_of_remaining_blocks
33
+ insert_new_blocks
34
+ sequence_known_blocks
35
+ insert_sequences
36
+ clear_for_next_persist
37
+ end
38
+
39
+ private
40
+
41
+ def clear_for_next_persist
42
+ @s = []
43
+ @b = []
44
+ @offset += @@max_blocks
45
+ end
46
+
47
+ def sequence_known_blocks
48
+ f = found_fingerprint_map
49
+ add_sequence_entries_clearing_blocks(f)
50
+ end
51
+
52
+ def found_fingerprint_map
53
+ f = {}
54
+ fp_raw = []
55
+ @b.each {|a| fp_raw << a.fingerprint unless a.nil?}
56
+ return f if fp_raw.size == 0
57
+ fingerprints = fp_raw.collect {|fp| "'#{fp}'"}.join(', ')
58
+ query = "select distinct id, fingerprint from blocks "+
59
+ "where fingerprint in (#{fingerprints})"
60
+ r = Nearline::Models::Block.connection.select_all(query)
61
+ r.each { |e| f[e["fingerprint"]] = e["id"] }
62
+ f
63
+ end
64
+
65
+ def add_sequence_entries_clearing_blocks(f)
66
+ @b.size.times do |i|
67
+ block = @b[i]
68
+ unless block.nil?
69
+ if f[block.fingerprint]
70
+ @s.push(Sequence.new(
71
+ :sequence => i + @offset + 1,
72
+ :block_id => f[block.fingerprint],
73
+ :file_content_id => @file_content.id
74
+ ))
75
+ @b[i] = nil
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def insert_sequences
82
+ @s.each do |s|
83
+ s.save!
84
+ end
85
+ end
86
+
87
+ def attempt_compression_of_remaining_blocks
88
+ f = {}
89
+ @b.each do |block|
90
+ unless block.nil? or f[block.fingerprint]
91
+ block.attempt_compression
92
+ f[block.fingerprint] = true
93
+ end
94
+ end
95
+ end
96
+
97
+ def insert_new_blocks
98
+ f = {}
99
+ @b.each do |b|
100
+ unless b.nil? || f[b.fingerprint]
101
+ b.save!
102
+ f[b.fingerprint] = true
103
+ end
104
+ end
105
+ end
106
+
107
+ def pull_blocks
108
+ count = 0
109
+ while (!@io.eof && count < @@max_blocks)
110
+ count += 1
111
+
112
+ # Move to Block
113
+ buffer = @io.read(Block.max_block_size)
114
+
115
+ @file_size += buffer.size
116
+
117
+ # Move to Block
118
+ blk = Block.new(:bulk_content => buffer)
119
+ @whole_file_hash.update(buffer)
120
+
121
+ # Move to Block
122
+ blk.calculate_fingerprint
123
+ @b << blk
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
@@ -5,10 +5,7 @@ module Nearline
5
5
  class FileFinder
6
6
  require 'find'
7
7
  def self.recurse(paths, exclusions)
8
- regex_exclusions = []
9
- for exclusion in exclusions
10
- regex_exclusions << /#{exclusion}/
11
- end
8
+ regex_exclusions = exclusion_regexes(exclusions)
12
9
  paths.each do |path|
13
10
  Find.find(path) do |f|
14
11
  regex_exclusions.each do |ex|
@@ -18,6 +15,72 @@ module Nearline
18
15
  end
19
16
  end
20
17
  end
18
+
19
+ def self.exclusion_regexes(exclusions)
20
+ regex_exclusions = []
21
+ for exclusion in exclusions
22
+ regex_exclusions << /#{exclusion}/
23
+ end
24
+ regex_exclusions
25
+ end
26
+ end
27
+
28
+ # Handles file paths and metadata for a file in a manifest
29
+ class FileInformation
30
+ attr_reader :path_hash, :stat, :is_directory,
31
+ :archived_file_parameters, :manifest, :file_path
32
+
33
+ def initialize(file_path, manifest)
34
+ @manifest = manifest
35
+ @file_path = file_path
36
+ @stat = read_stat
37
+ @is_directory = File.directory?(file_path)
38
+ @path_hash = generate_path_hash
39
+ @archived_file_parameters = build_parameters
40
+ end
41
+
42
+ def read_stat
43
+ stat = nil
44
+ begin
45
+ # TODO: change to lstat when we handle links
46
+ stat = File.stat(@file_path)
47
+ rescue
48
+ @manifest.add_log("File not found on stat: #{@file_path}")
49
+ end
50
+ stat
51
+ end
52
+
53
+ def generate_path_hash
54
+ return nil if @stat.nil?
55
+ target = [@manifest.system.name,
56
+ @file_path,
57
+ @stat.uid,
58
+ @stat.gid,
59
+ @stat.mtime.to_i,
60
+ @stat.mode].join(':')
61
+ Digest::SHA1.hexdigest(target)
62
+ end
63
+
64
+ def file_content_entry_for_files_only
65
+ return FileContent.new unless @is_directory
66
+ return nil
67
+ end
68
+
69
+ def build_parameters
70
+ return nil if @stat.nil?
71
+ {
72
+ :system => @manifest.system,
73
+ :path => @file_path,
74
+ :path_hash => @path_hash,
75
+ :file_content => file_content_entry_for_files_only,
76
+ :uid => @stat.uid,
77
+ :gid => @stat.gid,
78
+ :mtime => @stat.mtime.to_i,
79
+ :mode => @stat.mode,
80
+ :is_directory => @is_directory
81
+ }
82
+ end
83
+
21
84
  end
22
85
 
23
86
  # A Manifest represents the corpus of ArchivedFiles and
@@ -32,7 +95,11 @@ module Nearline
32
95
  attr_accessor :backup_paths
33
96
  # Just needed when you create a manifest
34
97
  attr_accessor :backup_exclusions
35
-
98
+
99
+ # Maximum number of files to stat and process in a batch
100
+ @@max_files_cached = 10000
101
+ cattr_accessor :max_files_cached
102
+
36
103
  def self.new_for_name(system_name)
37
104
  system = System.for_name(system_name)
38
105
  system.manifests << m = Nearline::Models::Manifest.new
@@ -43,10 +110,44 @@ module Nearline
43
110
  def self.backup(system, backup_paths, backup_exclusions)
44
111
  manifest = self.new(:system => system)
45
112
  manifest.save!
113
+ manifest.backup(backup_paths, backup_exclusions)
114
+ end
115
+
116
+ def backup(backup_paths, backup_exclusions)
117
+ FileFinder.recurse(backup_paths, backup_exclusions) do |file_path|
118
+ handle_file_path(file_path)
119
+ end
120
+ finish_remaining_file_infos
46
121
 
47
- FileFinder.recurse(backup_paths, backup_exclusions) do |file_name|
48
- $stdout.write file_name + " "
49
- af = ArchivedFile.create_for(file_name, manifest)
122
+ self.completed_at = Time.now
123
+ self.save!
124
+ self
125
+ end
126
+
127
+ def handle_file_path(file_path)
128
+ @file_infos = @file_infos || []
129
+ @file_infos << FileInformation.new(file_path, self)
130
+
131
+ if @file_infos.size > @@max_files_cached
132
+ process_file_infos
133
+ end
134
+ end
135
+
136
+ def finish_remaining_file_infos
137
+ process_file_infos
138
+ end
139
+
140
+ def process_file_infos
141
+ return if @file_infos.size == 0
142
+
143
+ lookup = existing_archived_file_lookup
144
+ @file_infos.each do |file_info|
145
+ $stdout.write file_info.file_path + " "
146
+ if (af = lookup[file_info.path_hash])
147
+ self.archived_files << af
148
+ else
149
+ af = ArchivedFile.create_for(file_info)
150
+ end
50
151
  if (!af.nil?)
51
152
  $stdout.write "#{Time.at(af.mtime).asctime}"
52
153
  if (!af.file_content.nil?)
@@ -55,12 +156,18 @@ module Nearline
55
156
  $stdout.write("\n")
56
157
  end
57
158
  end
58
-
59
- manifest.completed_at = Time.now
60
- manifest.save!
61
- manifest
159
+ @file_infos = []
62
160
  end
63
161
 
162
+ def existing_archived_file_lookup
163
+ return {} if @file_infos.size == 0
164
+ path_hashes = @file_infos.collect {|e| "'#{e.path_hash}'"}.join(", ")
165
+ conditions = "path_hash in (#{path_hashes})"
166
+ hits = ArchivedFile.find(:all, :conditions => conditions)
167
+ existing_files = {}
168
+ hits.each { |e| existing_files[e.path_hash] = e }
169
+ existing_files
170
+ end
64
171
 
65
172
  # Find all Manifest entries (across all Systems) which have never finished.
66
173
  #
@@ -73,17 +180,24 @@ module Nearline
73
180
 
74
181
  def self.restore_all_missing(system, latest_date_time = Time.now)
75
182
  manifest = system.latest_manifest_as_of(latest_date_time)
76
- manifest.restore_all_missing
183
+ manifest.iterate_all_missing do |af|
184
+ af.restore
185
+ end
77
186
  end
78
187
 
79
- # Restore all missing files from this manifest back to the filesystem
80
- def restore_all_missing
188
+ def self.what_would_restore(system, latest_date_time = Time.now)
189
+ manifest = system.latest_manifest_as_of(latest_date_time)
190
+ manifest.iterate_all_missing {}
191
+ end
192
+
193
+ # Iterate all missing files from this manifest, yielding each
194
+ def iterate_all_missing
81
195
  files_restored = []
82
196
  self.archived_files.each do |af|
83
197
  begin
84
198
  File.stat(af.path)
85
199
  rescue
86
- af.restore
200
+ yield af
87
201
  files_restored << af.path
88
202
  end
89
203
  end
@@ -97,14 +211,83 @@ module Nearline
97
211
  end
98
212
 
99
213
  def before_destroy
100
- archived_files.each do |af|
214
+ destroy_archived_files_with_content
215
+ destroy_archived_files_without_content
216
+ destroy_archived_files_manifests
217
+ destroy_logs
218
+ self.destroy_without_habtm_shim_for_archived_files
219
+ end
220
+
221
+ private
222
+
223
+ def archived_file_content_query(op)
224
+ <<-END_SQL
225
+ select distinct fc.id
226
+ from archived_files af,
227
+ archived_files_manifests afm, file_contents fc
228
+ where
229
+ afm.manifest_id #{op} #{self.id} and
230
+ afm.archived_file_id = af.id and
231
+ af.file_content_id = fc.id
232
+ END_SQL
233
+ end
234
+
235
+ def destroy_archived_files_with_content
236
+
237
+ fc_in = self.connection.select_all(archived_file_content_query("=")).collect{|e| e["id"]}
238
+ fc_out = self.connection.select_all(archived_file_content_query("!=")).collect{|e| e["id"]}
239
+ fc_to_destroy = (fc_in - fc_out).join ", "
240
+
241
+ if (fc_to_destroy.size > 0)
242
+ af_to_destroy = self.connection.select_all(<<-END_QUERY
243
+ select af.id from archived_files af, archived_files_manifests afm
244
+ where afm.manifest_id=#{self.id} and afm.archived_file_id = af.id and
245
+ af.file_content_id in (#{fc_to_destroy})
246
+ END_QUERY
247
+ ).collect{|e| e["id"]}
248
+ else
249
+ af_to_destroy = []
250
+ end
251
+
252
+ Nearline::Models::ArchivedFile.find(af_to_destroy).each do |af|
253
+ af.orphan_check
254
+ end
255
+ end
256
+
257
+ def archived_files_query(op)
258
+ <<-END_QUERY
259
+ select distinct af.id
260
+ from archived_files af,
261
+ archived_files_manifests afm
262
+ where af.file_content_id is null and
263
+ af.id = afm.archived_file_id and
264
+ afm.manifest_id #{op} #{self.id}
265
+ END_QUERY
266
+ end
267
+
268
+ def destroy_archived_files_without_content
269
+ af_in = self.connection.select_all(archived_files_query("=")).collect{|e| e["id"]}
270
+ af_out = self.connection.select_all(archived_files_query("!=")).collect{|e| e["id"]}
271
+
272
+ af_to_destroy = Nearline::Models::ArchivedFile.find(af_in - af_out)
273
+ af_to_destroy.each do |af|
101
274
  af.orphan_check
102
275
  end
276
+ end
277
+
278
+
279
+ def destroy_archived_files_manifests
280
+ self.connection.delete("delete from archived_files_manifests where manifest_id=#{self.id}")
281
+ end
282
+
283
+ def destroy_logs
103
284
  logs.each do |log|
104
285
  log.destroy
105
- end
286
+ end
106
287
  end
107
288
 
289
+ public
290
+
108
291
  def total_size
109
292
  size = 0
110
293
  archived_files.each do |af|
@@ -1,8 +1,10 @@
1
1
  module Nearline
2
2
  module_function
3
3
 
4
- # VERSION of the software
5
- VERSION = "0.0.4"
4
+ # Version of the software
5
+ VERSION = "0.0.5"
6
+ # Last version that changed the database structure
7
+ DB_VERSION = "0.0.4"
6
8
 
7
9
  # Array of every Nearline Model using an ActiveRecord connection
8
10
  AR_MODELS = Nearline::Models.constants.map do |m|
@@ -21,6 +23,8 @@ module Nearline
21
23
  # Stomps on any ActiveRecord::Base.establish_connection you might
22
24
  # have already established.
23
25
  #
26
+ # ***NOTE: MYSQL is the only recommended database at this time.***
27
+ #
24
28
  # === Examples
25
29
  # Nearline.connect!({:adapter => 'sqlite3', :database => 'data/sqlite.db'})
26
30
  #
@@ -49,6 +53,9 @@ module Nearline
49
53
  #
50
54
  # Accepts a Hash to establish the connection or
51
55
  # a String referring to an entry in config/database.yml.
56
+ #
57
+ # ***NOTE: MYSQL is the only recommended database at this time.***
58
+ #
52
59
  # === Examples
53
60
  # Nearline.connect({:adapter => 'sqlite3', :database => 'data/sqlite.db'})
54
61
  #
@@ -90,9 +97,7 @@ module Nearline
90
97
  # Nearline.backup('my_laptop', ['/home/me', '/var/svn']
91
98
  #
92
99
  def backup(system_name, backup_paths,backup_exclusions= [])
93
- unless version_check?
94
- raise SchemaVersionException.for_version(schema_version)
95
- end
100
+ raise_failing_version_check
96
101
  Nearline::Models::System.backup(
97
102
  system_name,
98
103
  Utilities.string_to_array(backup_paths),
@@ -120,13 +125,17 @@ module Nearline
120
125
  #
121
126
  # Returns an Array of paths restored
122
127
  def restore(system_name, latest_date_time = Time.now)
123
- unless version_check?
124
- raise SchemaVersionException.for_version(schema_version)
125
- end
128
+ raise_failing_version_check
126
129
  Nearline::Models::System.restore_all_missing(system_name, latest_date_time)
127
130
  end
128
131
 
129
-
132
+ # Returns an array of paths that would be restored given the provided
133
+ # parameters
134
+ def what_would_restore(system_name, latest_date_time = Time.now)
135
+ raise_failing_version_check
136
+ Nearline::Models::System.what_would_restore(system_name, latest_date_time)
137
+ end
138
+
130
139
  # Returns the nearline version of the database
131
140
  def schema_version
132
141
  begin
@@ -138,15 +147,23 @@ module Nearline
138
147
  end
139
148
  end
140
149
 
150
+ def raise_failing_version_check
151
+ unless version_check?
152
+ raise SchemaVersionException.for_version(schema_version)
153
+ end
154
+ end
155
+
141
156
  # Returns true only if the Nearline version matches the schema
142
157
  def version_check?
143
- Nearline::VERSION == schema_version()
158
+ Nearline::DB_VERSION == schema_version()
144
159
  end
145
160
 
146
161
  class SchemaVersionException < Exception
147
162
  def self.for_version(v)
148
- SchemaVersionException.new("Schema #{v} is not the same "+
149
- "version as nearline #{Nearline::VERSION}!")
163
+ SchemaVersionException.new(<<-END_ERROR)
164
+ Schema #{v} is not the same version as nearline database
165
+ version #{Nearline::DB_VERSION} used in Nearline #{Nearline::VERSION}!
166
+ END_ERROR
150
167
  end
151
168
  end
152
169
 
@@ -18,7 +18,7 @@ module Nearline
18
18
  end
19
19
 
20
20
  def empty_schema
21
- Nearline::Models::Manifest.destroy_all
21
+ Nearline::Models::System.destroy_all
22
22
  end
23
23
 
24
24
  def generate_schema
@@ -99,7 +99,7 @@ module Nearline
99
99
  t.column :version, :string
100
100
  end
101
101
 
102
- execute "insert into nearline_version (version) values ('#{Nearline::VERSION}')"
102
+ execute "insert into nearline_version (version) values ('#{Nearline::DB_VERSION}')"
103
103
  end
104
104
  end
105
105
 
@@ -52,15 +52,15 @@ module Nearline
52
52
  Manifest.restore_all_missing(self, latest_date_time)
53
53
  end
54
54
 
55
- def archived_file_lookup_hash
56
- return @lookup_hash if !@lookup_hash.nil?
57
- @lookup_hash = {}
58
- for af in self.archived_files
59
- @lookup_hash[af.path_hash] = af.id
60
- end
61
- @lookup_hash
55
+ def self.what_would_restore(system_name, latest_date_time)
56
+ system = self.for_name(system_name)
57
+ system.what_would_restore(latest_date_time)
62
58
  end
63
59
 
60
+ def what_would_restore(latest_date_time = Time.now)
61
+ Manifest.what_would_restore(self, latest_date_time)
62
+ end
63
+
64
64
  def before_destroy
65
65
  for manifest in self.manifests
66
66
  manifest.destroy
data/tasks/gemspec.rake CHANGED
@@ -9,6 +9,7 @@ SPEC = Gem::Specification.new do |s|
9
9
  s.version = Nearline::VERSION
10
10
  s.author = "Robert J. Osborne"
11
11
  s.email = "rjo1970@gmail.com"
12
+ s.homepage = "http://rubyforge.org/projects/nearline"
12
13
  s.summary = "Nearline is a near-line backup and recovery solution"
13
14
  s.description = %{
14
15
  Nearline is a library to make managing near-line file repositories
@@ -18,7 +19,6 @@ SPEC = Gem::Specification.new do |s|
18
19
  s.files = FileList["{tests,lib,doc,tasks}/**/*"].exclude("rdoc").to_a
19
20
  s.add_dependency("activerecord", '>= 2.0.2')
20
21
  s.require_path = "lib"
21
- s.autorequire = "nearline"
22
22
  s.test_file = "test/nearline_test.rb"
23
23
  s.has_rdoc = true
24
24
  end
metadata CHANGED
@@ -1,67 +1,75 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: nearline
5
3
  version: !ruby/object:Gem::Version
6
- version: 0.0.4
7
- date: 2008-04-18 00:00:00 -04:00
8
- summary: Nearline is a near-line backup and recovery solution
9
- require_paths:
10
- - lib
11
- email: rjo1970@gmail.com
12
- homepage:
13
- rubyforge_project: nearline
14
- description: Nearline is a library to make managing near-line file repositories simple and elegant in pure Ruby.
15
- autorequire: nearline
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
4
+ version: 0.0.5
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
7
  - Robert J. Osborne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-22 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.2
23
+ version:
24
+ description: Nearline is a library to make managing near-line file repositories simple and elegant in pure Ruby.
25
+ email: rjo1970@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
31
32
  files:
32
33
  - lib/nearline
33
- - lib/nearline/archived_file.rb
34
+ - lib/nearline/schema.rb
35
+ - lib/nearline/system.rb
36
+ - lib/nearline/module_methods.rb
37
+ - lib/nearline/file_sequencer.rb
34
38
  - lib/nearline/block.rb
35
- - lib/nearline/file_content.rb
36
39
  - lib/nearline/log.rb
40
+ - lib/nearline/archived_file.rb
41
+ - lib/nearline/file_content.rb
37
42
  - lib/nearline/manifest.rb
38
- - lib/nearline/module_methods.rb
39
- - lib/nearline/schema.rb
40
- - lib/nearline/system.rb
41
43
  - lib/nearline.rb
42
- - tasks/clean.rake
43
44
  - tasks/gemspec.rake
44
- - tasks/rcov.rake
45
+ - tasks/clean.rake
45
46
  - tasks/test.rake
46
- test_files:
47
- - test/nearline_test.rb
47
+ - tasks/rcov.rake
48
+ has_rdoc: true
49
+ homepage: http://rubyforge.org/projects/nearline
50
+ post_install_message:
48
51
  rdoc_options: []
49
52
 
50
- extra_rdoc_files: []
51
-
52
- executables: []
53
-
54
- extensions: []
55
-
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
56
67
  requirements: []
57
68
 
58
- dependencies:
59
- - !ruby/object:Gem::Dependency
60
- name: activerecord
61
- version_requirement:
62
- version_requirements: !ruby/object:Gem::Version::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: 2.0.2
67
- version:
69
+ rubyforge_project: nearline
70
+ rubygems_version: 1.1.1
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Nearline is a near-line backup and recovery solution
74
+ test_files:
75
+ - test/nearline_test.rb