blob: 0ad5ea11652de6c52c4466d4dd43d868c4698adc [file] [log] [blame]
package com.atlassian.uwc.exporters;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import com.atlassian.uwc.prep.MoinMoinPreparation.PageDirFileFilter;
import com.atlassian.uwc.ui.FileUtils;
/**
* Exporter code based on MoinMoinPreparation class.
* This is partially a refactor, but also contains some new properties and improvements.
*/
public class MoinmoinExporter implements Exporter {
private boolean running = false;
Logger log = Logger.getLogger(this.getClass());
private Map properties;
private static final String CURRENT = "current";
private static final String REVISIONS = "revisions";
private static final String BADCONTENT = "BadContent";
private static final String CATEGORY = "CategoryRoot";
private static final String EXTENSION = ".txt";
public static final String REVLOG = "edit-log";
private class RevInfo{
public String userid;
public Date timestamp;
public String revComment;
public Integer revision;
public String origAuthor;
}
public void cancel() {
log.info("Cancelling Moinmoin Export");
this.running = false;
}
public void export(Map propertiesMap) throws ClassNotFoundException,
SQLException {
this.running = true;
log.info("Beginning Moinmoin Export");
this.properties = propertiesMap;
if (validDirectories()) {
exportPages();
}
if (this.running)
log.info("Moinmoin Export Complete");
this.running = false;
}
/**
* @return true if the src and out directories are valid directories
*/
protected boolean validDirectories() {
if (this.running == false) return false;
String src = getSrc();
String out = getOut();
if (src == null || out == null) {
log.error("src and out properties must be set in conf/exporter.moinmoin.properties");
return false;
}
File pagesDir = new File(src);
File destinationDir = new File(out);
if (pagesDir.isFile() || !pagesDir.exists()) {
log.error("src directory is not a valid directory: " + src);
return false;
}
if (destinationDir.isFile()) {
log.error("out property is not a directory: " + out);
return false;
}
if (!destinationDir.exists()) {
if (!destinationDir.mkdirs()) {
log.error("Impossible to create out directory: \"" + out + "\".");
return false;
}
}
log.debug("src and out directories are valid");
return true;
}
private String getSrc() {
if (this.properties == null) return "";
return (String) this.properties.get("src");
}
private String getOut() {
if (this.properties == null) return "";
return (String) this.properties.get("out");
}
/**
* export moinmoin pages to out directory
*/
private void exportPages() {
if (this.running == false) return;
log.debug("src directory: " + getSrc());
log.debug("out directory: " + getOut());
File pagesDir = new File(getSrc());
String[] pages = pagesDir.list(new PageDirFileFilter());
String current = null;
Map<String, String> usernames = getUserNameInfo(getSrc() + File.separator + ".." );
Map<String, String> orignames = getOrigNameInfo(getSrc() + File.separator + ".." );
for (int i = 0; i < pages.length; i++) {
if (!pages[i].startsWith(BADCONTENT) && !pages[i].startsWith(CATEGORY) ) { //ignore BADCONTENT page
log.debug("page: " + pages[i]);
if (exportHistory()) {
final String pagedir = getSrc() + File.separator + pages[i];
final String revisiondir = pagedir + File.separator + REVISIONS;
log.debug("revision dir: " + revisiondir );
String[] revisions = new File(revisiondir).list();
if (revisions == null) {
log.error("Revisions directory was null. Skipping.");
continue;
}
Map<Integer, RevInfo> revmap = readRevisionsInfo(pagedir, usernames, orignames);
for (String revision : revisions) {
if (!revision.matches("\\d+")) continue; //ignore non-numbers
int num = Integer.parseInt(revision);
try {
File srcfile = new File(getSrc() + File.separator + pages[i] +
File.separator + REVISIONS + File.separator + revision);
File outfile = new File(getOut() + File.separator + pages[i] + "-" + num + EXTENSION);
copyFile(srcfile,outfile);
//addTimestampData(outfile, new Date(srcfile.lastModified()));
addRevData(outfile, revmap.get(num));
addTitleData(outfile, pages[i]);
} catch (FileNotFoundException e) {
log.info("Page \"" + pages[i] + "\" has been deleted and will be ignored.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
else { //only export current
current = getCurrentRevision(getSrc() + File.separator + pages[i]);
log.debug(pages[i] + "\ncurrent revision: " + current);
if (current != null) {
try {
File srcfile = new File(getSrc() + File.separator + pages[i] +
File.separator + REVISIONS + File.separator + current);
File outfile = new File(getOut() + File.separator + pages[i] + EXTENSION);
copyFile(srcfile, outfile);
addTitleData(outfile, pages[i]);
} catch (FileNotFoundException e) {
log.info("Page \"" + pages[i] + "\" has been deleted and will be ignored.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
final Charset charset = Charset.forName("windows-1252");
private Map<String, String> getUserNameInfo(String srcDir) {
Map<String, String> res = new HashMap<String, String>();
String usersrc = srcDir + File.separator + "user";
log.debug("read UserInfo: " + usersrc);
Pattern namefinder = Pattern.compile("^aliasname=(\\S+)", Pattern.MULTILINE);
try{
File userdir = new File(usersrc);
for( File f : userdir.listFiles() ){
log.debug("reading userfile: " + f.getName());
// leave directories out
if( f.isDirectory() ) continue;
String key = f.getName();
String cont = FileUtils.readTextFile(f, charset);
Matcher m = namefinder.matcher(cont);
if(m.find()){
String name = m.group(1);
log.debug(String.format(" Key: %s \t Name %s", key, name));
res.put(key, name);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
private Map<String, String> getOrigNameInfo(String srcDir) {
Map<String, String> res = new HashMap<String, String>();
String usersrc = srcDir + File.separator + "user";
log.debug("read OrigNameInfo: " + usersrc);
Pattern origNameFinder = Pattern.compile("^name=(\\S+)", Pattern.MULTILINE);
try{
File userdir = new File(usersrc);
for( File f : userdir.listFiles() ){
log.debug("reading userfile: " + f.getName());
// leave directories out
if( f.isDirectory() ) continue;
String key = f.getName();
String cont = FileUtils.readTextFile(f, charset);
Matcher m = origNameFinder.matcher(cont);
if(m.find()){
String origName = m.group(1);
log.debug(String.format(" Key: %s \t OrigName %s", key, origName));
res.put(key, origName);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
private void addRevData(File outfile, RevInfo revInfo) {
final String tsData = "{timestamp:" + fm.format(revInfo.timestamp) + "}\n";
final String userid = "{userid:" + revInfo.userid + "}\n";
final String revComment = "{revcomment:" + revInfo.revComment + "}\n";
// If origAuthor is known, inject it as extra metadata into the exported page so it can be used during conversion
final String origAuthor = revInfo.origAuthor == null ? "" : "{origauthor:" + revInfo.origAuthor + "}\n";
String filecontents;
try {
filecontents = FileUtils.readTextFile(outfile);
} catch (IOException e) {
log.error("Could not read output file: " + outfile.getAbsolutePath());
e.printStackTrace();
return;
}
String newcontents = tsData + userid + revComment + origAuthor + filecontents;
FileUtils.writeFile(newcontents, outfile.getAbsolutePath());
}
private Map<Integer, RevInfo> readRevisionsInfo(String pagedir, Map<String, String> usermap, Map<String, String> origNameMap) {
final File revlogf = new File( pagedir + File.separator + REVLOG);
final Map<Integer, RevInfo> res = new HashMap<Integer, RevInfo>();
log.debug("Read reflog file: " + revlogf.getAbsolutePath() );
try {
final String filecontens = FileUtils.readTextFile(revlogf, charset);
for( String line : filecontens.split("\\n") ){
log.debug("read line of revisions file: " + line);
String[] s = line.split("\\t",9);
RevInfo r = new RevInfo();
r.revision = Integer.parseInt(s[1]);
res.put(r.revision, r);
if(usermap.containsKey(s[6])){
r.userid = usermap.get(s[6]);
} else {
// make empty, so we get SYSTEM as userid, since there is no useful name anyway
r.userid = "";
}
if(r.userid.trim().equals("")){
r.userid = "SYSTEM";
}
if (origNameMap.containsKey(s[6])) {
r.origAuthor = origNameMap.get(s[6]);
}
r.revComment = s[8].trim();
long time = Long.parseLong(s[0]) / 1000; //convert micro to milliseconds
r.timestamp = new Date(time);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return res;
}
static private SimpleDateFormat fm = new SimpleDateFormat("yyyyMMddHHmmss");
private void addTimestampData(File outfile, Date timestamp) {
String tsData = "{timestamp:" + fm.format(timestamp) + "}\n";
String filecontents;
try {
filecontents = FileUtils.readTextFile(outfile);
} catch (IOException e) {
log.error("Could not read output file: " + outfile.getAbsolutePath());
e.printStackTrace();
return;
}
String newcontents = tsData + filecontents;
FileUtils.writeFile(newcontents, outfile.getAbsolutePath());
}
protected void addTitleData(File outfile, String title) {
title = title.replaceAll("\\(2f\\)", "/");
title = title.replaceAll("\\(20\\)", " ");
title = title.replaceAll("\\(2d\\)", "-");
String titledata = "{orig-title:" + title + "}\n";
String filecontents;
try {
filecontents = FileUtils.readTextFile(outfile);
} catch (IOException e) {
log.error("Could not read output file: " + outfile.getAbsolutePath());
e.printStackTrace();
return;
}
String newcontents = titledata + filecontents;
FileUtils.writeFile(newcontents, outfile.getAbsolutePath());
}
private boolean exportHistory() {
if (this.properties == null) return false;
return Boolean.parseBoolean((String) this.properties.get("history"));
}
private boolean exportHistoryComments() {
if (this.properties == null) return false;
return Boolean.parseBoolean((String) this.properties.get("histcomment"));
}
/**
* copies file to newFile
* @param file
* @param newFile
* @throws FileNotFoundException
* @throws IOException
*/
protected void copyFile(File file, File newFile) throws FileNotFoundException, IOException {
if (!this.running) return;
log.debug("Copying '" + file.getAbsolutePath() + "' to '" + newFile.getAbsolutePath() + "'");
if (!file.exists()) log.error("File doesn't exist. Cannot copy: " + file.getAbsolutePath());
// Create channel on the source
FileChannel srcChannel = new FileInputStream(file.getAbsolutePath()).getChannel();
// Create channel on the destination
FileChannel dstChannel = new FileOutputStream(newFile.getAbsolutePath()).getChannel();
// Copy file contents from source to destination
int buffersize = -1;
try {
//see if the user specified a buffer size
String buffersizeStr = (String) this.properties.get("buffer-size");
if (buffersizeStr != null) {
try {
buffersize = Integer.parseInt(buffersizeStr);
}
catch (NumberFormatException en) {
log.error("Property buffer-size is not an integer. Using filesize.");
}
}
if (buffersize > 0) { //user set buffersize - see Michael Grove's code in UWC-349
long size = srcChannel.size();
long position = 0;
while (position < size) {
position += srcChannel.transferTo(position, buffersize, dstChannel);
}
}
else { //if no user specified buffer size, use filesize
dstChannel.transferFrom(srcChannel, 0, srcChannel.size());
}
} catch (FileNotFoundException e) {
throw e;
} catch (IOException e2) {
throw e2;
} catch (RuntimeException e3) {
throw e3;
} finally {
// Close the channels
srcChannel.close();
dstChannel.close();
if (!newFile.exists()) log.error("Copying file unsuccessful. New file does not exist: " + newFile.getAbsolutePath());
}
}
/**
* getCurrentRevision
*
* @param pagePath
* @return current revision filename
*/
private String getCurrentRevision(String pagePath) {
String current = null;
try {
BufferedReader br = new BufferedReader(new FileReader(pagePath + File.separator + CURRENT));
current = br.readLine();
} catch (FileNotFoundException e) {
log.info("Page \"" + pagePath + "\" has been deleted and will be ignored.");
} catch (IOException e) {
e.printStackTrace();
}
return current;
}
//used with unit testing
protected void setProperties(Map properties) {
this.properties = properties;
}
//used with unit testing
protected void setRunning(boolean running) {
this.running = running;
}
}