229 lines
6.4 KiB
Plaintext
229 lines
6.4 KiB
Plaintext
|
#!/usr/bin/env ruby
|
||
|
|
||
|
require 'optparse'
|
||
|
|
||
|
OPTIONS = {
|
||
|
:instiki_root => nil,
|
||
|
:storage => nil,
|
||
|
:database => 'mysql'
|
||
|
}
|
||
|
|
||
|
ARGV.options do |opts|
|
||
|
script_name = File.basename($0)
|
||
|
opts.banner = "Usage: ruby #{script_name} [options]"
|
||
|
|
||
|
opts.separator ""
|
||
|
|
||
|
opts.on("-t", "--storage /full/path/to/storage", String,
|
||
|
"Full path to your storage, ",
|
||
|
"such as /home/joe/instiki/storage/2500",
|
||
|
"It should be the directory that ",
|
||
|
"contains .snapshot files.") do |storage|
|
||
|
OPTIONS[:storage] = storage
|
||
|
end
|
||
|
|
||
|
opts.separator ""
|
||
|
|
||
|
opts.on("-i", "--instiki /full/path/to/instiki", String,
|
||
|
"Full path to your Instiki 0.10 installation, ",
|
||
|
"such as /home/joe/instiki-0.10.2") do |instiki|
|
||
|
OPTIONS[:instiki] = instiki
|
||
|
end
|
||
|
|
||
|
opts.separator ""
|
||
|
|
||
|
opts.on("-o", "--outfile /full/path/to/output_file", String,
|
||
|
"Full path (including filename!) to where ",
|
||
|
"you want the SQL output placed, such as ",
|
||
|
"/home/joe/instiki.sql") do |outfile|
|
||
|
OPTIONS[:outfile] = outfile
|
||
|
end
|
||
|
|
||
|
opts.on("-d", "--database {mysql|sqlite|postgres}", String,
|
||
|
"Target database (they have slightly different syntax)",
|
||
|
"default: mysql") do |database|
|
||
|
OPTIONS[:database] = database
|
||
|
end
|
||
|
|
||
|
opts.separator ""
|
||
|
|
||
|
opts.on_tail("-h", "--help",
|
||
|
"Show this help message.") { puts opts; exit }
|
||
|
|
||
|
opts.parse!
|
||
|
end
|
||
|
|
||
|
if OPTIONS[:instiki].nil? or OPTIONS[:storage].nil? or OPTIONS[:outfile].nil?
|
||
|
$stderr.puts "Please specify full paths to Instiki 0.10 installation and storage,"
|
||
|
$stderr.puts "as well as the path to the output file"
|
||
|
$stderr.puts
|
||
|
puts ARGV.options
|
||
|
exit -1
|
||
|
end
|
||
|
|
||
|
if FileTest.exists? OPTIONS[:outfile]
|
||
|
$stderr.puts "Output file #{OPTIONS[:outfile]} already exists!"
|
||
|
$stderr.puts "Please specify a new file"
|
||
|
$stderr.puts
|
||
|
puts ARGV.options
|
||
|
exit -1
|
||
|
end
|
||
|
|
||
|
raise "Directory #{OPTIONS[:instiki]} not found" unless File.directory?(OPTIONS[:instiki])
|
||
|
raise "Directory #{OPTIONS[:storage]} not found" unless File.directory?(OPTIONS[:storage])
|
||
|
|
||
|
expected_page_rb_path = File.join(OPTIONS[:instiki], 'app/models/page.rb')
|
||
|
raise "Instiki installation not found in #{OPTIONS[:instiki]}" unless File.file?(expected_page_rb_path)
|
||
|
|
||
|
expected_snapshot_pattern = File.join(OPTIONS[:storage], '*.snapshot')
|
||
|
raise "No snapshots found in #{expected_snapshot_pattern}" if Dir[expected_snapshot_pattern].empty?
|
||
|
|
||
|
INSTIKI_ROOT = File.expand_path(OPTIONS[:instiki])
|
||
|
|
||
|
ADDITIONAL_LOAD_PATHS = %w(
|
||
|
app/models
|
||
|
lib
|
||
|
vendor/madeleine-0.7.1/lib
|
||
|
vendor/RedCloth-3.0.3/lib
|
||
|
vendor/RedCloth-3.0.4/lib
|
||
|
vendor/rubyzip-0.5.8/lib
|
||
|
).map { |dir| "#{File.expand_path(File.join(INSTIKI_ROOT, dir))}"
|
||
|
}.delete_if { |dir| not File.exist?(dir) }
|
||
|
|
||
|
# Prepend to $LOAD_PATH
|
||
|
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) }
|
||
|
|
||
|
require 'webrick'
|
||
|
require 'wiki_service'
|
||
|
|
||
|
# substitute an extremely expensive method with something cheap.
|
||
|
class Revision
|
||
|
alias :__display_content :display_content
|
||
|
def display_content
|
||
|
return self
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Time
|
||
|
def ansi
|
||
|
strftime('%Y-%m-%d %H:%M:%S')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def sql_insert(table, hash)
|
||
|
columns = hash.keys
|
||
|
|
||
|
values = columns.map { |column| hash[column] }
|
||
|
values = values.map do |value|
|
||
|
if value.nil?
|
||
|
'NULL'
|
||
|
else
|
||
|
if (value == false or value == true) and OPTIONS[:database] == 'mysql'
|
||
|
value = value ? '1' : '0'
|
||
|
end
|
||
|
|
||
|
case OPTIONS[:database]
|
||
|
when 'mysql', 'postgres'
|
||
|
value = value.to_s.gsub("'", "\\\\'")
|
||
|
when 'sqlite'
|
||
|
value = value.to_s.gsub("'", "''")
|
||
|
else
|
||
|
raise "Unsupported database option #{OPTIONS[:database]}"
|
||
|
end
|
||
|
"'#{value.gsub("\r\n", "\n")}'"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
output = "INSERT INTO #{table} ("
|
||
|
output << columns.join(", ")
|
||
|
|
||
|
output << ") VALUES ("
|
||
|
output << values.join(", ")
|
||
|
output << ");"
|
||
|
output
|
||
|
end
|
||
|
|
||
|
def delete_all(outfile)
|
||
|
%w(wiki_references revisions pages system webs).each { |table| outfile.puts "DELETE FROM #{table};" }
|
||
|
end
|
||
|
|
||
|
def next_id(key)
|
||
|
$ids ||= {}
|
||
|
if $ids[key].nil?
|
||
|
$ids[key] = 1
|
||
|
else
|
||
|
$ids[key] = $ids[key] + 1
|
||
|
end
|
||
|
$ids[key]
|
||
|
end
|
||
|
|
||
|
def current_id(key)
|
||
|
$ids[key] or raise "No curent ID for #{key.inspect}"
|
||
|
end
|
||
|
|
||
|
WikiService.storage_path = OPTIONS[:storage]
|
||
|
wiki = WikiService.instance
|
||
|
|
||
|
File.open(OPTIONS[:outfile], 'w') { |outfile|
|
||
|
|
||
|
outfile.puts "BEGIN;"
|
||
|
delete_all(outfile)
|
||
|
outfile.puts "COMMIT;"
|
||
|
|
||
|
wiki.webs.each_pair do |web_name, web|
|
||
|
outfile.puts "BEGIN;"
|
||
|
outfile.puts sql_insert(:webs, {
|
||
|
:id => next_id(:web),
|
||
|
:name => web.name,
|
||
|
:address => web.address,
|
||
|
:password => web.password,
|
||
|
:additional_style => web.additional_style,
|
||
|
:allow_uploads => web.allow_uploads,
|
||
|
:published => web.published,
|
||
|
:count_pages => web.count_pages,
|
||
|
:markup => web.markup,
|
||
|
:color => web.color,
|
||
|
:max_upload_size => web.max_upload_size,
|
||
|
:safe_mode => web.safe_mode,
|
||
|
:brackets_only => web.brackets_only,
|
||
|
:created_at => web.pages.values.map { |p| p.revisions.first.created_at }.min.ansi,
|
||
|
:updated_at => web.pages.values.map { |p| p.revisions.last.created_at }.max.ansi
|
||
|
})
|
||
|
outfile.puts "COMMIT;"
|
||
|
|
||
|
puts "Web #{web_name} has #{web.pages.keys.size} pages"
|
||
|
web.pages.each_pair do |page_name, page|
|
||
|
|
||
|
outfile.puts "BEGIN;"
|
||
|
|
||
|
outfile.puts sql_insert(:pages, {
|
||
|
:id => next_id(:page),
|
||
|
:web_id => current_id(:web),
|
||
|
:locked_by => page.locked_by,
|
||
|
:name => page.name,
|
||
|
:created_at => page.revisions.first.created_at.ansi,
|
||
|
:updated_at => page.revisions.last.created_at.ansi
|
||
|
})
|
||
|
|
||
|
puts " Page #{page_name} has #{page.revisions.size} revisions"
|
||
|
page.revisions.each_with_index do |rev, i|
|
||
|
|
||
|
outfile.puts sql_insert(:revisions, {
|
||
|
:id => next_id(:revision),
|
||
|
:page_id => current_id(:page),
|
||
|
:content => rev.content,
|
||
|
:author => rev.author.to_s,
|
||
|
:ip => (rev.author.is_a?(Author) ? rev.author.ip : 'N/A'),
|
||
|
:created_at => rev.created_at.ansi,
|
||
|
:updated_at => rev.created_at.ansi,
|
||
|
:revised_at => rev.created_at.ansi
|
||
|
})
|
||
|
puts " Revision #{i} created at #{rev.created_at.ansi}"
|
||
|
end
|
||
|
|
||
|
outfile.puts "COMMIT;"
|
||
|
|
||
|
end
|
||
|
end
|
||
|
}
|