X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=bin%2Frbotdb;h=b77939eb0647f162aebbe254c9753151ff5f3b68;hb=HEAD;hp=5f7491a73465ce822e71cbb2987290dd25863fca;hpb=a0f6dde4a4eb9d32f6363713b5bf78cf12fade0c;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/bin/rbotdb b/bin/rbotdb index 5f7491a7..b77939eb 100755 --- a/bin/rbotdb +++ b/bin/rbotdb @@ -2,11 +2,11 @@ #-- vim:sw=2:et #++ # -# :title: RBot Registry Export, Import and Migration Script. +# :title: RBot Registry Backup, Restore and Migration Script. # # You can use this script to, -# - export the rbot registry in a format that is platform/engine independent -# - import these backups in supported formats (dbm, daybreak) +# - backup the rbot registry in a format that is platform/engine independent +# - restore these backups in supported formats (dbm, daybreak) # - migrate old rbot registries bdb (ruby 1.8) and tokyocabinet. # # For more information, just execute the script without any arguments! @@ -17,13 +17,12 @@ begin; require 'rubygems'; rescue Exception; end -# old registry formats: +# load registry formats: begin; require 'bdb'; rescue Exception; end begin; require 'tokyocabinet'; rescue Exception; end - -# new formats: begin; require 'dbm'; rescue Exception; end begin; require 'daybreak'; rescue Exception; end +begin; require 'sqlite3'; rescue Exception; end puts 'RBot Registry Backup/Restore/Migrate' puts '[%s]' % ['Ruby: ' + RUBY_VERSION, @@ -31,70 +30,72 @@ puts '[%s]' % ['Ruby: ' + RUBY_VERSION, 'BDB: ' + (BDB::VERSION rescue '-'), 'TokyoCabinet: ' + (TokyoCabinet::VERSION rescue '-'), 'Daybreak: ' + (Daybreak::VERSION rescue '-'), + 'SQLite: ' + (SQLite3::VERSION rescue '-'), ].join(' | ') require 'date' require 'optparse' -TYPES = [:bdb, :tc, :dbm, :daybreak, :auto] +TYPES = [:bdb, :tc, :dbm, :daybreak, :sqlite] options = { :profile => '~/.rbot', - :dbfile => './%s.rbot' % DateTime.now.strftime('export_%Y-%m-%d_%H%M%S'), - :type => :auto + :registry => nil, + :dbfile => './%s.rbot' % DateTime.now.strftime('backup_%Y-%m-%d_%H%M%S'), + :type => nil } opt_parser = OptionParser.new do |opt| opt.banner = 'Usage: rbotdb COMMAND [OPTIONS]' opt.separator '' opt.separator 'Commands:' - opt.separator ' export: store rbot registry platform-independently in a file.' - opt.separator ' import: restore rbot registry from such a file.' + opt.separator ' backup: store rbot registry platform-independently in a file.' + opt.separator ' restore: restore rbot registry from such a file.' opt.separator '' opt.separator 'Options:' + opt.on('-t', '--type TYPE', TYPES, 'format to backup/restore. Values: %s.' % [TYPES.join(', ')]) do |type| + options[:type] = type + end + opt.on('-p', '--profile [PROFILE]', 'rbot profile directory. Defaults to: %s.' % options[:profile]) do |profile| options[:profile] = profile end - opt.on('-f', '--file [DBFILE]', 'cross-platform file to export to/import from. Defaults to: %s.' % options[:dbfile]) do |dbfile| - options[:dbfile] = dbfile + opt.on('-r', '--registry [REGISTRY]', 'registry-path to read/write, Optional, defaults to: /registry_.') do |profile| + options[:registry] = profile end - opt.on('-t', '--type TYPE', TYPES, 'format to export/import. Values: %s. Defaults to %s.' % [TYPES.join(', '), options[:type]]) do |type| - options[:type] = type + opt.on('-f', '--file [DBFILE]', 'cross-platform file to backup to/restore from. Defaults to: %s.' % options[:dbfile]) do |dbfile| + options[:dbfile] = dbfile end opt.separator '' end -class ExportRegistry - def initialize(profile, type) +class BackupRegistry + def initialize(profile, type, registry) @profile = File.expand_path profile @type = type - puts 'Using type=%s profile=%s' % [@type, @profile] + @registry = registry + puts 'Using type=%s profile=%s registry=%s' % [@type, @profile, @registry.inspect] end # returns a hash with the complete registry data - def export + def backup listings = search - puts 'Found registry types: bdb=%d tc=%d dbm=%d daybreak=%d' % [ + puts 'Found registry types: bdb=%d tc=%d dbm=%d daybreak=%d sqlite=%d' % [ listings[:bdb].length, listings[:tc].length, - listings[:dbm].length, listings[:daybreak].length + listings[:dbm].length, listings[:daybreak].length, listings[:sqlite].length ] - if @type == :auto - @type = :bdb if listings[:bdb].length > 0 - @type = :tc if listings[:tc].length > 0 - @type = :dbm if listings[:dbm].length > 0 - @type = :daybreak if listings[:daybreak].length > 0 - end - if @type == :auto or listings[@type].empty? + if listings[@type].empty? puts 'No suitable registry found!' - return + exit end puts 'Using registry type: %s' % @type read(listings[@type]) end def read(listing) + print "~Reading... (this might take a moment)\r" data = {} count = 0 listing.each do |file| @@ -108,6 +109,8 @@ class ExportRegistry read_dbm(file) when :daybreak read_daybreak(file) + when :sqlite + read_sqlite(file) end count += data[file.key].length rescue @@ -125,7 +128,11 @@ class ExportRegistry def read_bdb(file) data = {} - db = BDB::Hash.open(file.abs, nil, 'r') + begin + db = BDB::Hash.open(file.abs, nil, 'r') + rescue BDB::Fatal + db = BDB::Btree.open(file.abs, nil, 'r') + end db.each do |key, value| data[key] = value end @@ -161,16 +168,37 @@ class ExportRegistry data end + def read_sqlite(file) + data = {} + db = SQLite3::Database.new(file.abs) + res = db.execute('SELECT key, value FROM data') + res.each do |row| + key, value = row + data[key] = value + end + db.close + data + end + # searches in profile directory for existing registry formats def search { - :tc => list(File.join(@profile, 'registry'), '*.tdb'), - :bdb => list(File.join(@profile, 'registry'), '*.db'), - :dbm => list(File.join(@profile, 'registry_dbm'), '*.*'), - :daybreak => list(File.join(@profile, 'registry_daybreak'), '*.db'), + :bdb => list(get_registry, '*.db'), + :tc => list(get_registry('_tc'), '*.tdb'), + :dbm => list(get_registry('_dbm'), '*.*'), + :daybreak => list(get_registry('_daybreak'), '*.db'), + :sqlite => list(get_registry('_sqlite'), '*.db'), } end + def get_registry(suffix='') + if @registry + File.expand_path(@registry) + else + File.join(@profile, 'registry'+suffix) + end + end + class RegistryFile def initialize(folder, name) @folder = folder @@ -196,27 +224,33 @@ class ExportRegistry end end -class ImportRegistry - def initialize(profile, type) +class RestoreRegistry + def initialize(profile, type, registry) @profile = File.expand_path profile - @type = (type == :auto) ? :dbm : type + @registry = registry ? File.expand_path(registry) : nil + @type = type puts 'Using type=%s profile=%s' % [@type, @profile] end - def import(data) + def restore(data) puts 'Using registry type: %s' % @type folder = create_folder + print "~Restoring... (this might take a moment)\r" data.each do |file, hash| file = File.join(folder, file) create_subdir(file) case @type when :dbm write_dbm(file, hash) + when :tc + write_tc(file, hash) when :daybreak write_daybreak(file, hash) + when :sqlite + write_sqlite(file, hash) end end - puts 'Import completed.' + puts 'Restore successful! ' end def write_dbm(file, data) @@ -227,6 +261,19 @@ class ImportRegistry db.close end + def write_tc(file, data) + db = TokyoCabinet::BDB.new + db.open(file + '.tdb', + TokyoCabinet::BDB::OREADER | + TokyoCabinet::BDB::OCREAT | + TokyoCabinet::BDB::OWRITER) + data.each_pair do |key, value| + db[key] = value + end + db.optimize + db.close + end + def write_daybreak(file, data) db = Daybreak::DB.new(file + '.db') data.each_pair do |key, value| @@ -235,21 +282,27 @@ class ImportRegistry db.close end + def write_sqlite(file, data) + db = SQLite3::Database.new(file + '.db') + db.execute('CREATE TABLE data (key PRIMARY_KEY, value)') + data.each_pair do |key, value| + db.execute('INSERT INTO data VALUES (?, ?)', + key, value) + end + db.close + end + def create_folder - folder = @profile - case @type - when :dbm - folder = File.join(folder, 'registry_dbm') - when :daybreak - folder = File.join(folder, 'registry_daybreak') + Dir.mkdir(@profile) unless File.directory?(@profile) + if @registry + folder = @registry else - puts 'ERROR: Unsupported import type: %s' % @type - exit + folder = File.join(@profile, 'registry_%s' % [@type.to_s]) end Dir.mkdir(folder) unless File.directory?(folder) if File.directory?(folder) and Dir.glob(File.join(folder, '**')).select{|f|File.file? f}.length>0 - puts 'ERROR: Unable to import!' - puts 'Import folder exists and is not empty: ' + folder + puts 'ERROR: Unable to restore!' + puts 'Restore folder exists and is not empty: ' + folder exit end folder @@ -268,17 +321,22 @@ class ImportRegistry end opt_parser.parse! +if ARGV.length > 0 and options[:type].nil? + puts opt_parser + puts 'Missing Argument: -t [type]' + exit +end case ARGV[0] -when 'export' +when 'backup' if File.exists? options[:dbfile] - puts 'Export file already exists.' + puts 'Backup file already exists.' exit end - reg = ExportRegistry.new(options[:profile], options[:type]) + reg = BackupRegistry.new(options[:profile], options[:type], options[:registry]) - data = reg.export + data = reg.backup if not data.empty? File.open(options[:dbfile], 'w') do |f| @@ -287,17 +345,17 @@ when 'export' puts 'Written registry to ' + options[:dbfile] end -when 'import' +when 'restore' unless File.exists? options[:dbfile] - puts 'Import file does not exist.' + puts 'Backup file does not exist.' exit end - reg = ImportRegistry.new(options[:profile], options[:type]) + reg = RestoreRegistry.new(options[:profile], options[:type], options[:registry]) data = Marshal.load File.read(options[:dbfile]) - puts 'Read %d registry files from import file.' % data.length - reg.import data + puts 'Read %d registry files from backup file.' % data.length + reg.restore data else puts opt_parser