nearline 0.0.1

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.
@@ -0,0 +1,224 @@
1
+ module Nearline
2
+ module Models
3
+
4
+ # Represents file metadata and possible related FileContent
5
+ # for a single file on a single system
6
+ class ArchivedFile < ActiveRecord::Base
7
+ belongs_to :file_content
8
+ has_and_belongs_to_many :manifests
9
+
10
+ def self.create_for(system_name, file_path, manifest)
11
+
12
+ file_information = FileInformation.new(system_name, file_path, manifest)
13
+
14
+ # The path doesn't actually exist and fails a File.stat
15
+ return nil if file_information.path_hash.nil?
16
+
17
+ # If we find an exising entry, use it
18
+ hit = self.find_by_path_hash(file_information.path_hash)
19
+ return hit unless hit.nil?
20
+
21
+ # We need to create a record for either a directory or file
22
+ archived_file = ArchivedFile.new(
23
+ file_information.archived_file_parameters
24
+ )
25
+
26
+ # Find a new directory
27
+ if (file_information.is_directory)
28
+ archived_file.save!
29
+ return archived_file
30
+ end
31
+
32
+ # Find a new file that needs persisted
33
+ archived_file.file_content.file_size =
34
+ [file_information.stat.size].pack('Q').unpack('L').first # HACK for Windows
35
+ archived_file.persist(manifest)
36
+ archived_file.save!
37
+ archived_file
38
+
39
+ # TODO: Symbolic links, block devices, ...?
40
+ end
41
+
42
+ class FileInformation
43
+ attr_reader :path_hash, :stat, :is_directory, :archived_file_parameters
44
+ def initialize(system_name, file_path, manifest)
45
+ @manifest = manifest
46
+ @stat = read_stat(file_path)
47
+ @is_directory = File.directory?(file_path)
48
+ @path_hash = generate_path_hash(system_name, file_path)
49
+ @archived_file_parameters = build_parameters(system_name, file_path)
50
+ end
51
+
52
+ def read_stat(file_path)
53
+ stat = nil
54
+ begin
55
+ stat = File.stat(file_path)
56
+ rescue
57
+ @manifest.add_log("File not found on stat: #{file_path}")
58
+ end
59
+ stat
60
+ end
61
+
62
+ def generate_path_hash(system_name, file_path)
63
+ return nil if @stat.nil?
64
+ target = [system_name,
65
+ file_path,
66
+ @stat.uid,
67
+ @stat.gid,
68
+ @stat.mtime.to_i,
69
+ @stat.mode].join(':')
70
+ Digest::SHA1.hexdigest(target)
71
+ end
72
+
73
+ def file_content_entry_for_files_only
74
+ return FileContent.fresh_entry unless @is_directory
75
+ return nil
76
+ end
77
+
78
+ def build_parameters(system_name, file_path)
79
+ return nil if @stat.nil?
80
+ {
81
+ :system_name => system_name,
82
+ :path => file_path,
83
+ :path_hash => @path_hash,
84
+ :file_content => file_content_entry_for_files_only,
85
+ :uid => @stat.uid,
86
+ :gid => @stat.gid,
87
+ :mtime => @stat.mtime.to_i,
88
+ :mode => @stat.mode,
89
+ :is_directory => @is_directory
90
+ }
91
+ end
92
+
93
+ end
94
+
95
+ def restore(*args)
96
+ @options = args.extract_options!
97
+ if (self.is_directory)
98
+ FileUtils.mkdir_p option_override(:path)
99
+ restore_metadata
100
+ return
101
+ end
102
+ target_path = File.dirname(option_override(:path))
103
+ if (!File.exist? target_path)
104
+ FileUtils.mkdir_p target_path
105
+ end
106
+ f = File.open(option_override(:path), "wb")
107
+ self.file_content.restore_to(f)
108
+ f.close
109
+ restore_metadata
110
+ return
111
+ end
112
+
113
+ def option_override(key)
114
+ if (@options.has_key?(key))
115
+ return @options[key]
116
+ end
117
+ return self.send(key.to_s)
118
+ end
119
+
120
+ def restore_metadata
121
+ path = option_override(:path)
122
+ mtime = option_override(:mtime)
123
+ uid = option_override(:uid)
124
+ gid = option_override(:gid)
125
+ mode = option_override(:mode)
126
+ File.utime(0,Time.at(mtime),path)
127
+ File.chown(uid, gid, path)
128
+ File.chmod(mode, path)
129
+ end
130
+
131
+ def before_destroy
132
+ self.file_content.orphan_check if !self.file_content.nil?
133
+ end
134
+
135
+ def orphan_check
136
+ if self.manifests.size == 1
137
+ self.destroy
138
+ end
139
+ end
140
+
141
+ # Actually persist the file to the repository
142
+ # It has already been determined that a new ArchivedFile record is
143
+ # necessary and the file requires persisting
144
+ #
145
+ # But, the content may be identical to something else, and we
146
+ # won't know that until we complete the process and have to
147
+ # clean up our mess.
148
+ def persist(manifest)
149
+ whole_file_hash = Digest::SHA1.new
150
+ file_size = 0
151
+ begin
152
+ file_size = read_file_counting_bytes(whole_file_hash)
153
+ rescue
154
+ manifest.add_log "Got error '#{$!}' on path: #{self.path}"
155
+ self.orphan_check
156
+ return nil
157
+ end
158
+
159
+ size_check(file_size, manifest)
160
+
161
+ # Do we have a unique sequence?
162
+ key = whole_file_hash.hexdigest
163
+ return self if unique_sequence_processed?(key, manifest)
164
+
165
+ # Handle the case where the sequence is not unique...
166
+ clean_up_duplicate_content
167
+ replace_content(key)
168
+ self
169
+ end
170
+
171
+ def read_file_counting_bytes(whole_file_hash)
172
+ sequencer = FileSequencer.new(self.file_content)
173
+ file_size = 0
174
+ buffer = ""
175
+ File.open(self.path, "rb") do |io|
176
+ while (!io.eof) do
177
+ io.read(Block::MAX_SIZE, buffer)
178
+ file_size += buffer.size
179
+ whole_file_hash.update(buffer)
180
+ block = Block.for_content(buffer)
181
+ sequencer.preserve_block(block)
182
+ end
183
+ end
184
+ return file_size
185
+ end
186
+
187
+ def size_check(file_size, manifest)
188
+ if file_size != self.file_content.file_size
189
+ manifest.add_log "recorded file length #{file_size} " +
190
+ "does not match #{self.file_content.file_size} " +
191
+ "reported by the file system on path: #{self.path}"
192
+ end
193
+ end
194
+
195
+ def verify_content(manifest)
196
+ unless (self.file_content.verified?)
197
+ manifest.add_log "failed verification on path: #{self.path}"
198
+ end
199
+ end
200
+
201
+ def unique_sequence_processed?(key,manifest)
202
+ if self.file_content.unique_fingerprint?(key)
203
+ self.file_content.fingerprint = key
204
+ self.file_content.save!
205
+ verify_content(manifest)
206
+ return true
207
+ end
208
+ false
209
+ end
210
+
211
+ def clean_up_duplicate_content
212
+ Sequence.delete_all("file_content_id=#{self.file_content.id}")
213
+ self.file_content.orphan_check
214
+ end
215
+
216
+ def replace_content(key)
217
+ self.file_content = FileContent.find_by_fingerprint(key)
218
+ self.save!
219
+ end
220
+
221
+ end
222
+
223
+ end
224
+ end
@@ -0,0 +1,56 @@
1
+ require 'active_record'
2
+
3
+ module Nearline
4
+ module Models
5
+
6
+ # Represents a unit of file content which may be
7
+ # freely shared across the repository
8
+ # Its sole responsibility is to preserve and provide
9
+ # content access
10
+ class Block < ActiveRecord::Base
11
+ require "zlib"
12
+
13
+ has_many :sequences
14
+
15
+ MAX_SIZE = (64 * 1024)-1
16
+
17
+ 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)
21
+ if candidate_content.length < self.bulk_content.length
22
+ self.is_compressed = true
23
+ self.bulk_content = candidate_content
24
+ end
25
+ end
26
+
27
+ def calculate_fingerprint
28
+ self.fingerprint = Digest::SHA1.hexdigest(content)
29
+ end
30
+
31
+ def content
32
+ if (self.is_compressed)
33
+ return Zlib::Inflate.inflate(self.bulk_content)
34
+ end
35
+ self.bulk_content
36
+ end
37
+
38
+ def self.for_content(x)
39
+ block = Models::Block.new(:bulk_content => x)
40
+ block.calculate_fingerprint
41
+ found = find_by_fingerprint(block.fingerprint)
42
+ return found if !found.nil?
43
+ block.attempt_compression
44
+ block.save!
45
+ block
46
+ end
47
+
48
+ def orphan_check
49
+ if self.sequences.size == 0
50
+ self.destroy
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,85 @@
1
+ module Nearline
2
+ module Models
3
+
4
+ # Has the responsibility of identifying and
5
+ # verifying content
6
+ class FileContent < ActiveRecord::Base
7
+ has_many :sequences
8
+ has_many :archived_files
9
+
10
+ def self.fresh_entry
11
+ file_content = FileContent.new
12
+ file_content.save!
13
+ file_content
14
+ end
15
+
16
+ def restore_to(io)
17
+ sequencer = FileSequencer.new(self)
18
+ sequencer.restore_blocks(io)
19
+ end
20
+
21
+ def verified?
22
+ sequencer = FileSequencer.new(self)
23
+ sequencer.verified?
24
+ end
25
+
26
+ def orphan_check
27
+ if (self.archived_files.size == 1)
28
+ sequences.each do |s|
29
+ s.destroy
30
+ s.block.orphan_check
31
+ end
32
+ self.destroy
33
+ end
34
+ end
35
+
36
+ def unique_fingerprint?(key)
37
+ hit = FileContent.connection.select_one(
38
+ "select id from file_contents where fingerprint='#{key}'"
39
+ )
40
+ return hit.nil?
41
+ end
42
+
43
+ end
44
+
45
+ # Has the responsibility of preserving
46
+ # cardinality of stored blocks
47
+ class Sequence < ActiveRecord::Base
48
+ belongs_to :block
49
+ belongs_to :file_content
50
+ end
51
+
52
+ class FileSequencer
53
+ def initialize(file_content)
54
+ @inc = 0
55
+ @file_content = file_content
56
+ end
57
+
58
+ def preserve_block(block)
59
+ @inc += 1
60
+ sequence = Sequence.new(
61
+ :sequence => @inc,
62
+ :file_content_id => @file_content.id,
63
+ :block_id => block.id
64
+ )
65
+ sequence.save!
66
+ sequence
67
+ end
68
+
69
+ def restore_blocks(io)
70
+ @file_content.sequences.each do |seq|
71
+ io.write(seq.block.content)
72
+ end
73
+ end
74
+
75
+ def verified?
76
+ whole_file_hash = Digest::SHA1.new
77
+ @file_content.sequences.each do |seq|
78
+ whole_file_hash.update(seq.block.content)
79
+ end
80
+ @file_content.fingerprint == whole_file_hash.hexdigest
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,11 @@
1
+ module Nearline
2
+ module Models
3
+
4
+ # A simple message log for reporting problems during the creation
5
+ # of a Manifest
6
+ class Log < ActiveRecord::Base
7
+
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,106 @@
1
+ module Nearline
2
+ module Models
3
+
4
+ # Recuses paths and finds the files to back up
5
+ class FileFinder
6
+ require 'find'
7
+ def self.recurse(paths, exclusions)
8
+ paths.each do |path|
9
+ Find.find(path) do |f|
10
+ exclusions.each do |exclusion|
11
+ Find.prune if f =~ /#{exclusion}/
12
+ end
13
+ yield f
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # A Manifest represents the corpus of ArchivedFiles and
20
+ # set of Log messages resulting from a backup attempt
21
+ class Manifest < ActiveRecord::Base
22
+
23
+ has_and_belongs_to_many :archived_files
24
+ has_many :logs
25
+
26
+ # Just needed when you create a manifest
27
+ attr_accessor :backup_paths
28
+ # Just needed when you create a manifest
29
+ attr_accessor :backup_exclusions
30
+
31
+ # Underlying implementation of Nearline.backup
32
+ def self.backup(system_name, backup_paths, backup_exclusions)
33
+ manifest = self.new(:system_name => system_name)
34
+ manifest.save!
35
+
36
+ FileFinder.recurse(backup_paths, backup_exclusions) do |file_name|
37
+ af = ArchivedFile.create_for(system_name, file_name, manifest)
38
+ if (!af.nil?)
39
+ manifest.archived_files << af
40
+ $stdout.write "#{af.path} #{Time.at(af.mtime).asctime}"
41
+ if (!af.file_content.nil?)
42
+ $stdout.write" (#{af.file_content.file_size} bytes)"
43
+ end
44
+ $stdout.write("\n")
45
+ end
46
+ end
47
+
48
+ manifest.completed_at = Time.now
49
+ manifest.save!
50
+ manifest
51
+ end
52
+
53
+ # Find the latest Manifest for a system
54
+ def self.latest_for(system_name)
55
+ m_result = self.connection.select_one("select id from manifests where system_name='#{system_name}' order by created_at desc")
56
+ raise "No manifest found" if m_result.nil?
57
+ self.find(m_result["id"])
58
+ end
59
+
60
+ # Find all Manifest entries which have never finished.
61
+ #
62
+ # They are:
63
+ # * Currently under-way
64
+ # * Have failed in some untimely way
65
+ def self.incomplete_manifests
66
+ self.find_all_by_completed_at(nil)
67
+ end
68
+
69
+ def self.restore_all_missing(system_name)
70
+ manifest = latest_for(system_name)
71
+ manifest.restore_all_missing
72
+ end
73
+
74
+ # Restore all missing files from this manifest back to the filesystem
75
+ def restore_all_missing
76
+ files_restored = []
77
+ self.archived_files.each do |af|
78
+ begin
79
+ File.stat(af.path)
80
+ rescue
81
+ af.restore
82
+ files_restored << af.path
83
+ end
84
+ end
85
+ return files_restored
86
+ end
87
+
88
+ def add_log(message)
89
+ puts message
90
+ log = Nearline::Models::Log.new({:message => message, :manifest_id => self.id})
91
+ log.save!
92
+ end
93
+
94
+ def before_destroy
95
+ archived_files.each do |af|
96
+ af.orphan_check
97
+ end
98
+ logs.each do |log|
99
+ log.destroy
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,98 @@
1
+ module Nearline
2
+ module_function
3
+
4
+
5
+ # Establishes the ActiveRecord connection
6
+ #
7
+ # Accepts a Hash to establish the connection or
8
+ # a String referring to an entry in config/database.yml.
9
+ #
10
+ # Will establish the Nearline database tables if they are absent.
11
+ #
12
+ # Stomps on any ActiveRecord::Base.establish_connection you might
13
+ # have already established.
14
+ #
15
+ # === Examples
16
+ # Nearline.connect!({:adapter => 'sqlite3', :database => 'data/sqlite.db'})
17
+ #
18
+ # Nearline.connect! 'production'
19
+ #
20
+ def connect!(config="development")
21
+ if (config.class.to_s == 'String')
22
+ ActiveRecord::Base.establish_connection(YAML.load_file("config/database.yml")[config])
23
+ end
24
+
25
+ if (config.class.to_s == 'Hash')
26
+ ActiveRecord::Base.establish_connection(config)
27
+ end
28
+
29
+ unless Nearline::Models::Block.table_exists?
30
+ Nearline::Models.generate_schema
31
+ end
32
+ Nearline::Models::Block.connected?
33
+ end
34
+
35
+ # Establishes a connection only to the Nearline ActiveDirectory models
36
+ #
37
+ # Will not change the ActiveRecord::Base connection
38
+ #
39
+ # Will not establish Nearline tables in the database
40
+ #
41
+ # Accepts a Hash to establish the connection or
42
+ # a String referring to an entry in config/database.yml.
43
+ # === Examples
44
+ # Nearline.connect({:adapter => 'sqlite3', :database => 'data/sqlite.db'})
45
+ #
46
+ # Nearline.connect 'production'
47
+ #
48
+ def connect(config="development")
49
+ # These are the ActiveRecord models in place
50
+ # Each one needs an explicit establish_connection
51
+ # if you don't want it running though ActiveRecord::Base
52
+ models = [
53
+ Nearline::Models::ArchivedFile,
54
+ Nearline::Models::Block,
55
+ Nearline::Models::FileContent,
56
+ Nearline::Models::Manifest,
57
+ Nearline::Models::Sequence,
58
+ Nearline::Models::Log
59
+ ]
60
+ if (config.class.to_s == 'String')
61
+ hash = YAML.load_file("config/database.yml")[config]
62
+ else
63
+ hash = config
64
+ end
65
+
66
+ models.each do |m|
67
+ m.establish_connection(hash)
68
+ end
69
+ Nearline::Models::Block.connected?
70
+ end
71
+
72
+ # Performs a backup labeled for system_name,
73
+ # Recursing through an array of backup_paths,
74
+ # Excluding any path matching any of the regular
75
+ # expressions in the backup_exclusions array.
76
+ #
77
+ # Expects the Nearline database connection has already
78
+ # been established
79
+ #
80
+ # Returns a Manifest for the backup
81
+ def backup(system_name, backup_paths,backup_exclusions= [])
82
+ Nearline::Models::Manifest.backup(system_name, backup_paths, backup_exclusions)
83
+ end
84
+
85
+ # Restore all missing files from the latest backup
86
+ # for system_name
87
+ #
88
+ # All updated or existing files are left alone
89
+ #
90
+ # Expects the Nearline database connection has already
91
+ # been established
92
+ #
93
+ # Returns an Array of paths restored
94
+ def restore(system_name)
95
+ Nearline::Models::Manifest.restore_all_missing(system_name)
96
+ end
97
+
98
+ end
@@ -0,0 +1,90 @@
1
+ module Nearline
2
+ module Models
3
+
4
+ module_function
5
+
6
+ def destroy_schema
7
+ ActiveRecord::Schema.define do
8
+ drop_table :blocks
9
+ drop_table :file_contents
10
+ drop_table :sequences
11
+ drop_table :archived_files
12
+ drop_table :manifests
13
+ drop_table :archived_files_manifests
14
+ drop_table :logs
15
+ end
16
+ end
17
+
18
+ def empty_schema
19
+ Nearline::Models::Manifest.destroy_all
20
+ end
21
+
22
+ def generate_schema
23
+ ActiveRecord::Schema.define do
24
+
25
+ create_table :blocks do |t|
26
+ t.column :fingerprint, :string, :length => 40, :null => false
27
+ t.column :bulk_content, :binary
28
+ t.column :is_compressed, :boolean, :default => false
29
+ end
30
+
31
+ add_index :blocks, [:fingerprint], :unique => true
32
+
33
+ create_table :file_contents do |t|
34
+ t.column :fingerprint, :string, :length => 40
35
+ t.column :file_size, :integer, :default => 0
36
+ end
37
+
38
+ create_table :sequences do |t|
39
+ t.column :sequence, :integer, :null => false
40
+ t.column :block_id, :integer, :null => false
41
+ t.column :file_content_id, :integer, :null => false
42
+ end
43
+
44
+ add_index :sequences, [:sequence, :file_content_id], :unique => true,
45
+ :name => "sequence_jn_index"
46
+
47
+ create_table :archived_files do |t|
48
+ t.column :system_name, :string, :null => false
49
+ t.column :path, :text, :null => false
50
+ t.column :path_hash, :string, :null => false, :length => 40
51
+ t.column :file_content_id, :integer
52
+ t.column :uid, :integer, :default => -1
53
+ t.column :gid, :integer, :default => -1
54
+ t.column :mtime, :integer, :default => 0
55
+ t.column :mode, :integer, :default => 33206 # "chmod 100666"
56
+ t.column :is_directory, :boolean
57
+ end
58
+
59
+ add_index :archived_files, [:path_hash], :unique => true
60
+
61
+ # Manifests are the reference to a collection of archived files
62
+ create_table :manifests do |t|
63
+ t.column :system_name, :string
64
+ t.column :created_at, :datetime
65
+ t.column :completed_at, :datetime
66
+ end
67
+
68
+ # Joins archived files across manifests so file references may be recycled
69
+ create_table :archived_files_manifests, :id => false do |t|
70
+ t.column :archived_file_id, :integer
71
+ t.column :manifest_id, :integer
72
+ end
73
+
74
+ add_index :archived_files_manifests,
75
+ [:archived_file_id, :manifest_id], {
76
+ :unique => true,
77
+ :name => "manifest_jn_index"
78
+ }
79
+
80
+ # Keeps a record of problems during backup related to a manifest
81
+ create_table :logs do |t|
82
+ t.column :manifest_id, :integer, :null => false
83
+ t.column :message, :text
84
+ t.column :created_at, :datetime
85
+ end
86
+ end
87
+ end
88
+
89
+ end
90
+ end
data/lib/nearline.rb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Nearline
4
+ # Copyright (C) 2008 Robert J. Osborne
5
+
6
+ require 'nearline/module_methods'
7
+
8
+ # ActiveRecord database definitions
9
+ require 'nearline/schema'
10
+
11
+ # ActiveRecord models
12
+ require 'nearline/block'
13
+ require 'nearline/file_content'
14
+ require 'nearline/archived_file'
15
+ require 'nearline/log'
16
+ require 'nearline/manifest'
data/tasks/clean.rake ADDED
@@ -0,0 +1,3 @@
1
+ require 'rake/clean'
2
+
3
+ CLEAN.include("data","html","pkg","coverage", "temp")
@@ -0,0 +1,25 @@
1
+ require 'rake'
2
+ require 'rake/gempackagetask'
3
+
4
+ SPEC = Gem::Specification.new do |s|
5
+ s.name = "nearline"
6
+ s.version = "0.0.1"
7
+ s.author = "Robert J. Osborne"
8
+ s.email = "rjo1970@gmail.com"
9
+ s.summary = "Nearline is a near-line backup and recovery solution"
10
+ s.description = %{
11
+ Nearline is a library to make managing near-line file repositories
12
+ simple and eleant in pure Ruby.
13
+ }
14
+ s.rubyforge_project = "nearline"
15
+ s.files = FileList["{tests,lib,doc,tasks}/**/*"].exclude("rdoc").to_a
16
+ s.add_dependency("activerecord", '>= 2.0.2')
17
+ s.require_path = "lib"
18
+ s.autorequire = "nearline"
19
+ s.test_file = "test/nearline_test.rb"
20
+ s.has_rdoc = true
21
+ end
22
+
23
+ Rake::GemPackageTask.new(SPEC) do |pkg|
24
+ pkg.need_tar = true
25
+ end
data/tasks/rcov.rake ADDED
@@ -0,0 +1,13 @@
1
+ task :rcov => [:clean] do
2
+ begin
3
+ require 'rcov/rcovtask'
4
+
5
+ Rcov::RcovTask.new do |t|
6
+ t.libs << "test"
7
+ t.rcov_opts = ['--text-report']
8
+ t.test_files = FileList['test/nearline_test.rb']
9
+ t.verbose = true
10
+ end
11
+ rescue LoadError => no_rcov
12
+ end
13
+ end
data/tasks/test.rake ADDED
@@ -0,0 +1,4 @@
1
+ desc "Test nearline"
2
+ task :test => [:clean] do
3
+ require 'test/nearline_test'
4
+ end
@@ -0,0 +1,17 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "..", "test")
2
+ # This is the suite of tests to run against Nearline
3
+ require 'test/unit'
4
+
5
+ require 'utilities'
6
+
7
+ $data_path = File.join(File.dirname(__FILE__), "..", "data")
8
+ unless File.exist?($data_path)
9
+ FileUtils.mkdir $data_path
10
+ end
11
+
12
+ require 'schema_test'
13
+ require 'block_test'
14
+ require 'nearline_module_test'
15
+ require 'file_content_test'
16
+ require 'archived_file_test'
17
+ require 'manifest_test'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: nearline
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2008-04-03 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 eleant 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:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Robert J. Osborne
31
+ files:
32
+ - lib/nearline
33
+ - lib/nearline/archived_file.rb
34
+ - lib/nearline/block.rb
35
+ - lib/nearline/file_content.rb
36
+ - lib/nearline/log.rb
37
+ - lib/nearline/manifest.rb
38
+ - lib/nearline/module_methods.rb
39
+ - lib/nearline/schema.rb
40
+ - lib/nearline.rb
41
+ - tasks/clean.rake
42
+ - tasks/gemspec.rake
43
+ - tasks/rcov.rake
44
+ - tasks/test.rake
45
+ test_files:
46
+ - test/nearline_test.rb
47
+ rdoc_options: []
48
+
49
+ extra_rdoc_files: []
50
+
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ requirements: []
56
+
57
+ dependencies:
58
+ - !ruby/object:Gem::Dependency
59
+ name: activerecord
60
+ version_requirement:
61
+ version_requirements: !ruby/object:Gem::Version::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 2.0.2
66
+ version: