5 # :title: RBot Registry Export, Import and Migration Script.
7 # You can use this script to,
8 # - export the rbot registry in a format that is platform/engine independent
9 # - import these backups in supported formats (dbm, daybreak)
10 # - migrate old rbot registries bdb (ruby 1.8) and tokyocabinet.
12 # For more information, just execute the script without any arguments!
14 # Author:: apoc (Matthias Hecker) <apoc@geekosphere.org>
15 # Copyright:: (C) 2014 Matthias Hecker
18 begin; require 'rubygems'; rescue Exception; end
20 # old registry formats:
21 begin; require 'bdb'; rescue Exception; end
22 begin; require 'tokyocabinet'; rescue Exception; end
25 begin; require 'dbm'; rescue Exception; end
26 begin; require 'daybreak'; rescue Exception; end
28 puts 'RBot Registry Backup/Restore/Migrate'
29 puts '[%s]' % ['Ruby: ' + RUBY_VERSION,
30 'DBM: ' + (DBM::VERSION rescue '-'),
31 'BDB: ' + (BDB::VERSION rescue '-'),
32 'TokyoCabinet: ' + (TokyoCabinet::VERSION rescue '-'),
33 'Daybreak: ' + (Daybreak::VERSION rescue '-'),
39 TYPES = [:bdb, :tc, :dbm, :daybreak, :auto]
41 :profile => '~/.rbot',
42 :dbfile => './%s.rbot' % DateTime.now.strftime('export_%Y-%m-%d_%H%M%S'),
45 opt_parser = OptionParser.new do |opt|
46 opt.banner = 'Usage: rbotdb COMMAND [OPTIONS]'
48 opt.separator 'Commands:'
49 opt.separator ' export: store rbot registry platform-independently in a file.'
50 opt.separator ' import: restore rbot registry from such a file.'
52 opt.separator 'Options:'
54 opt.on('-p', '--profile [PROFILE]', 'rbot profile directory. Defaults to: %s.' % options[:profile]) do |profile|
55 options[:profile] = profile
58 opt.on('-f', '--file [DBFILE]', 'cross-platform file to export to/import from. Defaults to: %s.' % options[:dbfile]) do |dbfile|
59 options[:dbfile] = dbfile
62 opt.on('-t', '--type TYPE', TYPES, 'format to export/import. Values: %s. Defaults to %s.' % [TYPES.join(', '), options[:type]]) do |type|
70 def initialize(profile, type)
71 @profile = File.expand_path profile
73 puts 'Using type=%s profile=%s' % [@type, @profile]
76 # returns a hash with the complete registry data
79 puts 'Found registry types: bdb=%d tc=%d dbm=%d daybreak=%d' % [
80 listings[:bdb].length, listings[:tc].length,
81 listings[:dbm].length, listings[:daybreak].length
84 @type = :bdb if listings[:bdb].length > 0
85 @type = :tc if listings[:tc].length > 0
86 @type = :dbm if listings[:dbm].length > 0
87 @type = :daybreak if listings[:daybreak].length > 0
89 if @type == :auto or listings[@type].empty?
90 puts 'No suitable registry found!'
93 puts 'Using registry type: %s' % @type
100 listing.each do |file|
102 data[file.key] = case @type
112 count += data[file.key].length
114 puts 'ERROR: <%s> %s' % [$!.class, $!]
116 puts 'Keep in mind that, even minor version differences of'
117 puts 'Barkeley DB or Tokyocabinet make files unreadable. Use this'
118 puts 'script on the exact same platform rbot was running!'
122 puts 'Read %d registry files, with %d entries.' % [data.length, count]
128 db = BDB::Hash.open(file.abs, nil, 'r')
129 db.each do |key, value|
138 db = TokyoCabinet::BDB.new
139 db.open(file.abs, TokyoCabinet::BDB::OREADER)
140 db.each do |key, value|
148 db = DBM.open(file.abs.gsub(/\.[^\.]+$/,''), 0666, DBM::READER)
154 def read_daybreak(file)
156 db = Daybreak::DB.new(file.abs)
157 db.each do |key, value|
164 # searches in profile directory for existing registry formats
167 :tc => list(File.join(@profile, 'registry'), '*.tdb'),
168 :bdb => list(File.join(@profile, 'registry'), '*.db'),
169 :dbm => list(File.join(@profile, 'registry_dbm'), '*.*'),
170 :daybreak => list(File.join(@profile, 'registry_daybreak'), '*.db'),
175 def initialize(folder, name)
178 @key = name.gsub(/\.[^\.]+$/,'')
180 attr_reader :folder, :name, :key
182 File.expand_path(File.join(@folder, @name))
189 def list(folder, ext='*.db')
190 return [] if not File.directory? folder
192 Dir.glob(File.join('**', ext)).map do |name|
193 RegistryFile.new(folder, name) if File.exists?(name)
200 def initialize(profile, type)
201 @profile = File.expand_path profile
202 @type = (type == :auto) ? :dbm : type
203 puts 'Using type=%s profile=%s' % [@type, @profile]
207 puts 'Using registry type: %s' % @type
208 folder = create_folder
209 data.each do |file, hash|
210 file = File.join(folder, file)
214 write_dbm(file, hash)
216 write_daybreak(file, hash)
219 puts 'Import completed.'
222 def write_dbm(file, data)
223 db = DBM.open(file, 0666, DBM::WRCREAT)
224 data.each_pair do |key, value|
230 def write_daybreak(file, data)
231 db = Daybreak::DB.new(file + '.db')
232 data.each_pair do |key, value|
242 folder = File.join(folder, 'registry_dbm')
244 folder = File.join(folder, 'registry_daybreak')
246 puts 'ERROR: Unsupported import type: %s' % @type
249 Dir.mkdir(folder) unless File.directory?(folder)
250 if File.directory?(folder) and Dir.glob(File.join(folder, '**')).select{|f|File.file? f}.length>0
251 puts 'ERROR: Unable to import!'
252 puts 'Import folder exists and is not empty: ' + folder
258 # used to create subregistry folders
259 def create_subdir(path)
260 dirs = File.dirname(path).split('/')
261 dirs.length.times { |i|
262 dir = dirs[0,i+1].join("/")+"/"
263 unless File.exist?(dir)
274 if File.exists? options[:dbfile]
275 puts 'Export file already exists.'
279 reg = ExportRegistry.new(options[:profile], options[:type])
284 File.open(options[:dbfile], 'w') do |f|
285 f.write(Marshal.dump(data))
287 puts 'Written registry to ' + options[:dbfile]
291 unless File.exists? options[:dbfile]
292 puts 'Import file does not exist.'
296 reg = ImportRegistry.new(options[:profile], options[:type])
297 data = Marshal.load File.read(options[:dbfile])
299 puts 'Read %d registry files from import file.' % data.length