5 # :title: RBot Registry Backup, Restore and Migration Script.
7 # You can use this script to,
8 # - backup the rbot registry in a format that is platform/engine independent
9 # - restore 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 # load registry formats:
21 begin; require 'bdb'; rescue Exception; end
22 begin; require 'tokyocabinet'; rescue Exception; end
23 begin; require 'dbm'; rescue Exception; end
24 begin; require 'daybreak'; rescue Exception; end
25 begin; require 'sqlite3'; rescue Exception; end
27 puts 'RBot Registry Backup/Restore/Migrate'
28 puts '[%s]' % ['Ruby: ' + RUBY_VERSION,
29 'DBM: ' + (DBM::VERSION rescue '-'),
30 'BDB: ' + (BDB::VERSION rescue '-'),
31 'TokyoCabinet: ' + (TokyoCabinet::VERSION rescue '-'),
32 'Daybreak: ' + (Daybreak::VERSION rescue '-'),
33 'SQLite: ' + (SQLite3::VERSION rescue '-'),
39 TYPES = [:bdb, :tc, :dbm, :daybreak, :sqlite]
41 :profile => '~/.rbot',
43 :dbfile => './%s.rbot' % DateTime.now.strftime('backup_%Y-%m-%d_%H%M%S'),
46 opt_parser = OptionParser.new do |opt|
47 opt.banner = 'Usage: rbotdb COMMAND [OPTIONS]'
49 opt.separator 'Commands:'
50 opt.separator ' backup: store rbot registry platform-independently in a file.'
51 opt.separator ' restore: restore rbot registry from such a file.'
53 opt.separator 'Options:'
55 opt.on('-t', '--type TYPE', TYPES, 'format to backup/restore. Values: %s.' % [TYPES.join(', ')]) do |type|
59 opt.on('-p', '--profile [PROFILE]', 'rbot profile directory. Defaults to: %s.' % options[:profile]) do |profile|
60 options[:profile] = profile
63 opt.on('-r', '--registry [REGISTRY]', 'registry-path to read/write, Optional, defaults to: <PROFILE>/registry_<TYPE>.') do |profile|
64 options[:registry] = profile
67 opt.on('-f', '--file [DBFILE]', 'cross-platform file to backup to/restore from. Defaults to: %s.' % options[:dbfile]) do |dbfile|
68 options[:dbfile] = dbfile
75 def initialize(profile, type, registry)
76 @profile = File.expand_path profile
79 puts 'Using type=%s profile=%s registry=%s' % [@type, @profile, @registry.inspect]
82 # returns a hash with the complete registry data
85 puts 'Found registry types: bdb=%d tc=%d dbm=%d daybreak=%d sqlite=%d' % [
86 listings[:bdb].length, listings[:tc].length,
87 listings[:dbm].length, listings[:daybreak].length, listings[:sqlite].length
89 if listings[@type].empty?
90 puts 'No suitable registry found!'
93 puts 'Using registry type: %s' % @type
98 print "~Reading... (this might take a moment)\r"
101 listing.each do |file|
103 data[file.key] = case @type
115 count += data[file.key].length
117 puts 'ERROR: <%s> %s' % [$!.class, $!]
119 puts 'Keep in mind that, even minor version differences of'
120 puts 'Barkeley DB or Tokyocabinet make files unreadable. Use this'
121 puts 'script on the exact same platform rbot was running!'
125 puts 'Read %d registry files, with %d entries.' % [data.length, count]
131 db = BDB::Hash.open(file.abs, nil, 'r')
132 db.each do |key, value|
141 db = TokyoCabinet::BDB.new
142 db.open(file.abs, TokyoCabinet::BDB::OREADER)
143 db.each do |key, value|
151 db = DBM.open(file.abs.gsub(/\.[^\.]+$/,''), 0666, DBM::READER)
157 def read_daybreak(file)
159 db = Daybreak::DB.new(file.abs)
160 db.each do |key, value|
167 def read_sqlite(file)
169 db = SQLite3::Database.new(file.abs)
170 res = db.execute('SELECT key, value FROM data')
179 # searches in profile directory for existing registry formats
182 :bdb => list(get_registry, '*.db'),
183 :tc => list(get_registry('_tc'), '*.tdb'),
184 :dbm => list(get_registry('_dbm'), '*.*'),
185 :daybreak => list(get_registry('_daybreak'), '*.db'),
186 :sqlite => list(get_registry('_sqlite'), '*.db'),
190 def get_registry(suffix='')
192 File.expand_path(@registry)
194 File.join(@profile, 'registry'+suffix)
199 def initialize(folder, name)
202 @key = name.gsub(/\.[^\.]+$/,'')
204 attr_reader :folder, :name, :key
206 File.expand_path(File.join(@folder, @name))
213 def list(folder, ext='*.db')
214 return [] if not File.directory? folder
216 Dir.glob(File.join('**', ext)).map do |name|
217 RegistryFile.new(folder, name) if File.exists?(name)
223 class RestoreRegistry
224 def initialize(profile, type, registry)
225 @profile = File.expand_path profile
226 @registry = registry ? File.expand_path(registry) : nil
228 puts 'Using type=%s profile=%s' % [@type, @profile]
232 puts 'Using registry type: %s' % @type
233 folder = create_folder
234 print "~Restoring... (this might take a moment)\r"
235 data.each do |file, hash|
236 file = File.join(folder, file)
240 write_dbm(file, hash)
244 write_daybreak(file, hash)
246 write_sqlite(file, hash)
249 puts 'Restore successful! '
252 def write_dbm(file, data)
253 db = DBM.open(file, 0666, DBM::WRCREAT)
254 data.each_pair do |key, value|
260 def write_tc(file, data)
261 db = TokyoCabinet::BDB.new
262 db.open(file + '.tdb',
263 TokyoCabinet::BDB::OREADER |
264 TokyoCabinet::BDB::OCREAT |
265 TokyoCabinet::BDB::OWRITER)
266 data.each_pair do |key, value|
273 def write_daybreak(file, data)
274 db = Daybreak::DB.new(file + '.db')
275 data.each_pair do |key, value|
281 def write_sqlite(file, data)
282 db = SQLite3::Database.new(file + '.db')
283 db.execute('CREATE TABLE data (key PRIMARY_KEY, value)')
284 data.each_pair do |key, value|
285 db.execute('INSERT INTO data VALUES (?, ?)',
295 folder = File.join(@profile, 'registry_%s' % [@type.to_s])
297 Dir.mkdir(folder) unless File.directory?(folder)
298 if File.directory?(folder) and Dir.glob(File.join(folder, '**')).select{|f|File.file? f}.length>0
299 puts 'ERROR: Unable to restore!'
300 puts 'Restore folder exists and is not empty: ' + folder
306 # used to create subregistry folders
307 def create_subdir(path)
308 dirs = File.dirname(path).split('/')
309 dirs.length.times { |i|
310 dir = dirs[0,i+1].join("/")+"/"
311 unless File.exist?(dir)
319 if ARGV.length > 0 and options[:type].nil?
321 puts 'Missing Argument: -t [type]'
327 if File.exists? options[:dbfile]
328 puts 'Backup file already exists.'
332 reg = BackupRegistry.new(options[:profile], options[:type], options[:registry])
337 File.open(options[:dbfile], 'w') do |f|
338 f.write(Marshal.dump(data))
340 puts 'Written registry to ' + options[:dbfile]
344 unless File.exists? options[:dbfile]
345 puts 'Backup file does not exist.'
349 reg = RestoreRegistry.new(options[:profile], options[:type], options[:registry])
350 data = Marshal.load File.read(options[:dbfile])
352 puts 'Read %d registry files from backup file.' % data.length