Ruben Laguna’s blog

Merging Two TikiWiki’s

I’ve created a ruby script to merge the content of a TikiWiki into another one. This script will read the tiki_pages, tiki_history and tiki_links tables from the MySQL backend of the source TikiWiki and import the contents into the destination TikiWiki. The script is ‘safe’, meaning that it will not overwrite any page if it already exists in the destination. The history of the page will be merged as well if the page exists at the destination. The script doesn’t work with page attachments yet.

Here is the script, be sure to change the script parameters depending on your user/password on the databases (search oldtiki and newtiki variables in the script) and the charsets (search SET_CHARSET_NAME in the script).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
<span class='line'>#
</span><span class='line'># script to merge two tikiwiki s
</span><span class='line'>#
</span><span class='line'>require "rubygems"
</span><span class='line'>require "mysql"
</span><span class='line'>require "log4r"
</span><span class='line'>require "iconv"
</span><span class='line'>include Log4r
</span><span class='line'>
</span><span class='line'>def createInsert(dbh,  table,  row ) 
</span><span class='line'>      insertSt = "insert into #{table} "
</span><span class='line'>      col ="("
</span><span class='line'>      values = "VALUES("
</span><span class='line'>      
</span><span class='line'>      row.each { |key, value|
</span><span class='line'>        col += "#{key},"
</span><span class='line'>        if value and not value.empty?  then
</span><span class='line'>          values +=  "'#{dbh.escape_string(value)}',"
</span><span class='line'>        else 
</span><span class='line'>          values += "NULL,"
</span><span class='line'>        end    
</span><span class='line'>      }
</span><span class='line'>      col = col[0..-2] + ")"      
</span><span class='line'>      values = values[0..-2] + ")"
</span><span class='line'>      insertSt += col + " " + values
</span><span class='line'>      insertSt +";"
</span><span class='line'>      insertSt
</span><span class='line'>end
</span><span class='line'>
</span><span class='line'>$conflictNotif = {}
</span><span class='line'>def addToConflictNotifList (user, pagename) 
</span><span class='line'>  
</span><span class='line'>  pageList = $conflictNotif[user]
</span><span class='line'>  if not pageList then 
</span><span class='line'>    $conflictNotif[user] = []
</span><span class='line'>    pageList = $conflictNotif[user]
</span><span class='line'>  end
</span><span class='line'>  pageList &lt;&lt; pagename        
</span><span class='line'>end
</span><span class='line'>
</span><span class='line'>$notif = {}
</span><span class='line'>def addToNotifList (user, pagename) 
</span><span class='line'>  
</span><span class='line'>  pageList = $notif[user]
</span><span class='line'>  if not pageList then 
</span><span class='line'>    $notif[user] = []
</span><span class='line'>    pageList = $notif[user]
</span><span class='line'>  end
</span><span class='line'>  pageList &lt;&lt; pagename        
</span><span class='line'>end
</span><span class='line'>
</span><span class='line'>def printToFile (path, hash) 
</span><span class='line'>  fileConflicts   = File.open(path, File::WRONLY|File::TRUNC|File::CREAT) 
</span><span class='line'>  hash.each { |user, list|
</span><span class='line'>     fileConflicts.print "#{user}: "
</span><span class='line'>     fileConflicts.print list.join(',')
</span><span class='line'>     fileConflicts.print "\n"
</span><span class='line'>  }
</span><span class='line'>  fileConflicts.close
</span><span class='line'>end
</span><span class='line'>
</span><span class='line'>Log4r::Logger.root.level = Log4r::DEBUG
</span><span class='line'>
</span><span class='line'>l = Logger.new 'tiki_pages'
</span><span class='line'>l.outputters = Outputter.stdout,FileOutputter.new("tiki_pages", :filename => "tiki_pages.txt", :trunc => true, :level => Log4r::DEBUG)
</span><span class='line'>
</span><span class='line'>lh = Logger.new 'tiki_history'
</span><span class='line'>
</span><span class='line'>lh.outputters = Outputter.stdout,FileOutputter.new("tiki_history", :filename => "tiki_history.txt", :trunc => true, :level => Log4r::DEBUG)
</span><span class='line'>
</span><span class='line'>lm = Logger.new 'mysqlstatements'
</span><span class='line'>lm.outputters = FileOutputter.new("sqlfile", :filename => "commands.sql", :trunc => true, :level=>Log4r::DEBUG)
</span><span class='line'>
</span><span class='line'>l.debug "Starting migration script"
</span><span class='line'>
</span><span class='line'>oldtiki="sql1.example.com"
</span><span class='line'>olduser="root"
</span><span class='line'>olddbname="tiki"
</span><span class='line'>oldpwd="secret"
</span><span class='line'>
</span><span class='line'>newtiki="sql2.example.com"
</span><span class='line'>newuser="root"
</span><span class='line'>newdbname="rd_tiki_wiki"
</span><span class='line'>newpwd="secret"
</span><span class='line'>
</span><span class='line'>#select login,email from users_users;
</span><span class='line'>#mysql> select tiki_pages.pagename,users_users.email from tiki_pages,users_users where tiki_pages.user=users_users=login;
</span><span class='line'>
</span><span class='line'>
</span><span class='line'>begin
</span><span class='line'>  #connect to the MySQL server
</span><span class='line'>  l.debug "trying to connect..."
</span><span class='line'>  dbhold = Mysql.init
</span><span class='line'>  dbhold.options(Mysql::SET_CHARSET_DIR, "/root/tikiWikiScript/charsets/")
</span><span class='line'>  dbhold.options(Mysql::SET_CHARSET_NAME, "utf8")
</span><span class='line'>  dbhold.real_connect(oldtiki,olduser, oldpwd,olddbname)
</span><span class='line'>  # get server version string and display it
</span><span class='line'>  l.info "#{oldtiki} mysql version: " + dbhold.get_server_info
</span><span class='line'>
</span><span class='line'>  dbhnew = Mysql.init
</span><span class='line'>  dbhnew.options(Mysql::SET_CHARSET_DIR, "/root/tikiWikiScript/charsets/")
</span><span class='line'>  dbhnew.options(Mysql::SET_CHARSET_NAME, "latin1")
</span><span class='line'>  dbhnew.real_connect(newtiki,newuser,newpwd,newdbname,3307)
</span><span class='line'>  l.info "#{newtiki} mysql version: " + dbhnew.get_server_info
</span><span class='line'>
</span><span class='line'>  l.info "retrieving all pagenames from old tiki..."
</span><span class='line'>  
</span><span class='line'>  res= dbhold.query("select * from tiki_pages");
</span><span class='line'>  
</span><span class='line'>  num_pages_old_tiki = res.num_rows
</span><span class='line'>  l.info "number of pages in old tiki : #{num_pages_old_tiki}"
</span><span class='line'>  insertions = 0
</span><span class='line'>  conflicts = 0
</span><span class='line'>  history_updates = 0
</span><span class='line'>  history_version_conflicts = 0
</span><span class='line'>  while row = res.fetch_hash do
</span><span class='line'>    #l.debug row
</span><span class='line'>    pagename = row["pageName"]
</span><span class='line'>    lastModif = row["lastModif"]
</span><span class='line'>    user = row["user"]
</span><span class='line'>    creator = row["creator"]
</span><span class='line'>    
</span><span class='line'>    l.debug "checking pageName='#{pagename}'"
</span><span class='line'>    escapePageName = dbhnew.escape_string(pagename)
</span><span class='line'>    l.debug "escapePageName='#{escapePageName}'"
</span><span class='line'>    query = "select lastModif,user,creator from tiki_pages where pageName='#{escapePageName}'"
</span><span class='line'>    res2 = dbhnew.query(query)
</span><span class='line'>    
</span><span class='line'>    if (res2.num_rows == 0) then
</span><span class='line'>      pageid =  dbhnew.query("select max(page_id)+1 from tiki_pages").fetch_row[0]
</span><span class='line'>      
</span><span class='line'>      l.info "Creating page '#{pagename}' with pageid #{pageid}"        
</span><span class='line'>      
</span><span class='line'>      row["page_id"] = pageid;
</span><span class='line'>      insertSt = createInsert(dbhnew,  "tiki_pages", row ) 
</span><span class='line'>      lm.debug "#{insertSt}"   
</span><span class='line'>      
</span><span class='line'>      dbhnew.query "#{insertSt}"   
</span><span class='line'>      
</span><span class='line'>      addToNotifList( user, pagename);
</span><span class='line'>      addToNotifList( creator, pagename) unless user == creator
</span><span class='line'>      insertions += 1
</span><span class='line'>    else
</span><span class='line'>      if (res2.num_rows > 1) then
</span><span class='line'>        l.error "database invariant violated: entry for pagename #{pagename} not found in the new tiki"
</span><span class='line'>        fail
</span><span class='line'>      end
</span><span class='line'>      row2 = res2.fetch_hash
</span><span class='line'>      lastModif2 = row2["lastModif"]
</span><span class='line'>      l.debug "Comparing last modification of page #{pagename} in old tiki with same page in new tiki"
</span><span class='line'>      if (lastModif > lastModif2) then                
</span><span class='line'>        l.warn "pagename \"#{pagename}\" is newer in #{oldtiki} than in #{newtiki}"
</span><span class='line'>        l.warn "we should send an email to #{user} and #{creator}"
</span><span class='line'>        
</span><span class='line'>        addToConflictNotifList( user, pagename)
</span><span class='line'>        addToConflictNotifList( creator, pagename) unless user == creator
</span><span class='line'>        
</span><span class='line'>        conflicts += 1
</span><span class='line'>      end
</span><span class='line'>      
</span><span class='line'>    end
</span><span class='line'>    res2.free
</span><span class='line'>    historyRes =  dbhold.query("select * from tiki_history where pageName='#{escapePageName}' ORDER BY version ASC")
</span><span class='line'>    anyHistoryUpdate = false;
</span><span class='line'>    while oldTikiHistoryEntry = historyRes.fetch_hash do
</span><span class='line'>      m = oldTikiHistoryEntry["lastModif"]
</span><span class='line'>      historyRes2 = dbhnew.query("select pageName from tiki_history where pageName='#{escapePageName}' and lastModif='#{m}'")
</span><span class='line'>      if (historyRes2.num_rows == 0) then
</span><span class='line'>      # this history entry was not present we must add new entry
</span><span class='line'>        version = oldTikiHistoryEntry["version"]        
</span><span class='line'>        lh.info "adding version #{version} last updated on #{oldTikiHistoryEntry["lastModif"]} to tiki_history for page '#{pagename}'"
</span><span class='line'>        historyRes3 = dbhnew.query("select lastModif from tiki_history where pageName='#{escapePageName}' and version='#{version}'")
</span><span class='line'>        if (historyRes3.num_rows == 0) then
</span><span class='line'>          lh.debug "version: #{version} not present in #{newtiki}. Insert the entry"
</span><span class='line'>        else
</span><span class='line'>          lh.debug "version: #{version} of '#{pagename}' already exists in #{newtiki}. Now we have to insert the entry in the middle."
</span><span class='line'>          dbhnew.query("update tiki_history set version=version+1 where pageName='#{escapePageName}' and version >= '#{version}' ORDER BY version DESC")
</span><span class='line'>          history_version_conflicts += 1
</span><span class='line'>        end
</span><span class='line'>        insertSt = createInsert(dbhnew, "tiki_history",  oldTikiHistoryEntry ) 
</span><span class='line'>        lm.info "#{insertSt}"
</span><span class='line'>        dbhnew.query "#{insertSt}"
</span><span class='line'>        history_updates += 1
</span><span class='line'>        anyHistoryUpdate = true;
</span><span class='line'>      else 
</span><span class='line'>        lh.debug "The history entry with modification date #{m} of pageName=#{pagename} was already present in the tiki_history of #{newtiki}. Skipping it"
</span><span class='line'>      end
</span><span class='line'>    end
</span><span class='line'>    l.info "History of page #{pagename} updated." if anyHistoryUpdate
</span><span class='line'>    historyRes.free
</span><span class='line'>  end
</span><span class='line'>  l.info "number of pages in old tiki : #{num_pages_old_tiki}"
</span><span class='line'>  l.info "number of insertions in the new wiki #{insertions}"
</span><span class='line'>  l.info "number of conflicts in the new wiki #{conflicts}"
</span><span class='line'>  l.info "number of history updates #{history_updates}"
</span><span class='line'>  l.info "number of history version conflicts #{history_version_conflicts}"
</span><span class='line'>  
</span><span class='line'>  dateString = Time.now.to_s
</span><span class='line'>  filename = "conflicts"+dateString+".txt"
</span><span class='line'>  printToFile(filename, $conflictNotif)
</span><span class='line'>  l.info "#{filename} created"
</span><span class='line'>  filename = "notifications"+dateString+".txt"
</span><span class='line'>  printToFile(filename, $notif)
</span><span class='line'>  l.info "#{filename} created"
</span><span class='line'>  
</span><span class='line'>  res.free
</span><span class='line'>  
</span><span class='line'>  #Update tiki_links that is responsible for the backlinks feature
</span><span class='line'>  link_insertions = 0
</span><span class='line'>  l.debug "retrieve all links"
</span><span class='line'>  linkRes = dbhold.query "select * from tiki_links"
</span><span class='line'>  while linkRow = linkRes.fetch_hash do
</span><span class='line'>    fromPage = dbhnew.escape_string(linkRow["fromPage"])
</span><span class='line'>    toPage = dbhnew.escape_string(linkRow["toPage"])
</span><span class='line'>    #check if this link already exists
</span><span class='line'>    checkRes = dbhnew.query "select * from tiki_links where fromPage='#{fromPage}' and toPage='#{toPage}'"
</span><span class='line'>    if (checkRes.num_rows == 0) then 
</span><span class='line'>      insertSt = createInsert(dbhnew, "tiki_links", linkRow)
</span><span class='line'>      lm.info "#{insertSt}"
</span><span class='line'>      dbhnew.query insertSt
</span><span class='line'>      link_insertions += 1      
</span><span class='line'>    end    
</span><span class='line'>    checkRes.free
</span><span class='line'>  end
</span><span class='line'>  linkRes.free
</span><span class='line'>  l.info "number of link_insertions #{link_insertions}"
</span><span class='line'>
</span><span class='line'>
</span><span class='line'>rescue Mysql::Error => e
</span><span class='line'>  l.error "Error code: #{e.errno}"
</span><span class='line'>  l.error "Error message: #{e.error}"
</span><span class='line'>  l.error "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
</span><span class='line'>ensure
</span><span class='line'>  # disconnect from server
</span><span class='line'>  l.debug "disconnecting from database"
</span><span class='line'>  dbhold.close if dbhold
</span><span class='line'>  dbhnew.close if dbhnew
</span><span class='line'>end</span>

The code is also available as a gist

You’ll need to install rubygems and Mysql module gem first.

Comments

Copyright © 2015 - Ruben Laguna - Powered by Octopress